您的位置:首页 > 编程语言 > Java开发

Java并发包多线程总结

2017-09-04 12:54 274 查看
一、 多线程并发:

(一) Volatile和synchronize:

1. volatile如何保证原子性?

AtomicLong,它既解决了volatile的原子性没有保证的问题,又具有可见性。

2. volatile会不会影响指令重排(volatile禁止指令重排)

   volatile两大作用

1、保证内存可见性

2、防止指令重排 此外需注意volatile并不保证操作的原子性。

指令重排序包括编译器重排序和运行时重排序。

volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

3. volatile使用建议

   使用建议:在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。

由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

4. synchronized和volatile区别

1.Voletitle 是变量修饰符,而synchronized 是修饰一段代码或方法。

2.而且voletile 不能保证操作的原子性,只能保证他们操作的是同一块内存,但依然可能出现写入脏数据的情况,对任意单个volatitle变量的读/写具有原子性。

3.volatile不会进行加锁操作,不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

4.volatile变量作用类似于同步变量读写操作:

从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。

5. synchronized的可重入性和独占性(写个题给你做)  

自己可以再次获取自己的内部锁。Java里面内置锁(synchronized)和Lock (ReentrantLock) 都是可重入的。

synchronized可重入锁的实现:每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

6. ReentrantLock和synchronized的区别,功能上面的区别(公平和非公平)

ReentrantLock特点

1.某个线程在等待一个锁的控制权的这段时间需要中断   

2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程   

3.具有公平锁功能,每个到来的线程都将排队等候

7. 如果多个线程去修改被volatile修饰的变量,值会被修改吗?

当使用volatile修饰变量i时,某个线程改变i的值时,会写回到主内存区,从而每个线程在使用时会是最新的值。 但不能代替synchronized,因为当A线程中,B线程中 i变量都为1时,某个时间AB线程同时执行i++. 最终i的值会为2.因为没有锁。

8. 如何解决volatile的线程不安全问题??

要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

对变量的写操作不依赖于当前值。

该变量没有包含在具有其他变量的不变式中。

9. synchrosized在成员方法中和静态方法中用的有什么区别

1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

3. 修改一个静态方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

10. synicazed 和lock readlock 和writelock ,voaltitle 的区别使用场景

Lock和synchronized最大的区别就是当使用synchronized,一个线程抢占到锁资源,其他线程必须像SB一样得等待;而使用Lock,一个线程抢占到锁资源,其他的线程可以不等待或者设置等待时间,实在抢不到可以去做其他的业务逻辑。

1 ReentrantReadWriteLock :(读写锁、写写锁互斥,读读锁共享,常用于缓存如terrocotta)

2 ReentrantLock :(可轮询的、定时的、可中断 lock() unlock())结合Condition将线程放入相应阻塞队列,比较灵活

  Condition notFull  = lock.newCondition();

3 Synchronized (单一对象锁监视器) 只能单一线程阻塞队列

(二) ConcurrentHashMap:

11. concurrentHashMap的原理,为什么是线程安全的?

原理:一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable, 每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,性能无疑好于只有一个hashtable锁定的情况。

12. ConcurrentHashMap 怎么计算它的size。

先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。(使用modCount变量,在put , remove和clean方法里操作元素前都会将变量modCount进行加1,那么在统计size前后比较modCount是否发生变化,从而得知容器的大小是否发生变化。)

13. concurrenthashmap了解吗?它这个锁是用什么实现的?

Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。

一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素。

 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

14. ConcurrentHashMap的get操作需要加锁吗?

不加,除非读到的值是空的才会加锁重读。原因是它的get方法里将要使用的共享变量都定义成volatile,在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。之所以不会读到过期的值,是根据Java内存模型的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值,这是用volatile替换锁的经典应用场景。

15. java 知道有个utilconcurrent包吧,里头有哪些组件是你常用的,在什么场景下使用?

Callable:线程

Semaphore:一个计数信号量,主要用于控制多线程对共同资源库访问的限制。(场景:地下车位,要有空余才能放行)

ReentrantLock(ReentrantLock:可重入互斥锁)与Condition(此类是同步的条件对象,每个Condition实例绑定到一个ReetrantLock中,以便争用同一个锁的多线程之间可以通过Condition的状态来获取通知。)

BlockingQueue(这是一个阻塞的队列超类接口,concurrent包下很多架构都基于这个队列)

CountDownLatch:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。个人理解是CountDownLatch可以让一组线程同时执行,然后在这组线程全部执行完前,可以让另一个线程等待。

CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。

Exchanger(配对和交换的线程的同步点):两个线程A、B,各自有一个数据类型相同的变量a、b,A线程往a中填数据(生产),B线程从b中取数据(消费)。具体如何让a、b在内存发生关联,就由Exchanger完成。

16. 如何应对高并发

1、 HTML静态化

2、 图片服务器分离

3、 数据库集群和库表散列

4、 缓存

5、 负载均衡

17. 并发量和吞吐量,QPS等区别,

QPS(TPS):每秒钟request/事务 数量

并发数: 系统同时处理的request/事务数

QPS(TPS)= 并发数/平均响应时间

吞吐量:单位时间内完成的指令数

(三) 锁:

18. 并发里用了哪些对象,常用的锁有哪些组件

Lock、Executors、Concurrent collections、Atomic变量

常用锁组件:ReentrantLock、ReadWriteLock、ReentrantReadWriteLock

19. 多线程锁,重入锁应用场景,读写锁,Thredlocal

ReentrantLock使用场景和实例:

场景一:如果发现该操作已经在执行中则不再执行

用在界面交互时点击执行较长时间请求操作时,防止多次点击导致后台重复执行(忽略重复触发)。

场景二:如果发现该操作已经在执行,等待一个一个执行

这种情况主要用于对资源的争抢,同步消息发送。默认不公平锁。

场景三:如果发现该操作已经在执行,则尝试等待一段时间,等待超时则不执行(尝试等待执行)

用来防止由于资源处理不当长时间占用导致死锁情况(大家都在等待资源,导致线程队列溢出)。

20. Synchronized与ReentrantLock的比较

这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

  1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。

2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

   3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。

21. java中锁也就是lock与sychromzed有什么区别

  同上

22. 说说重入锁和读写锁的原理,什么场景进行使用

可重入锁原理如19;

当多个线程试图对同一内容进行读写操作时适合使用读写锁。

(四) 线程池:

23. fork/join和使用Executors框架的区别

Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

24. 多线程你们系统怎么用的?考试系统抽题用到了forkjoinpool

你懂的

25. 线程池的使用,引用的包

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个

26. 线程池用过吗,有什么构造参数。ThreadPoolExcutor这个类

     corePoolSize:      线程池维护线程的最少数量 (core : 核心)

     maximumPoolSize:   线程池维护线程的最大数量 

     keepAliveTime:     线程池维护线程所允许的空闲时间

     unit:               线程池维护线程所允许的空闲时间的单位

     workQueue:          线程池所使用的缓冲队列

     handler:            线程池对拒绝任务的处理策略

27. 用过哪些线程池

1、newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。 

2、newCachedThreadPool创建一个可缓存的线程池。 

3、newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务 

4、newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

28. 线程池是怎么实现的

线程池的工作模型主要两部分组成,一部分是运行Runnable的Thread对象,另一部分就是阻塞队列。当Runnable对象的run方法执行完毕以后,Thread中的run方法又循环的从阻塞队列中获取下一个Runnable对象继续执行。这样就实现了Thread对象的重复利用,也就减少了创建线程和销毁线程所消耗的资源。

当需要向线程池提交任务时会调用阻塞队列的offer方法向队列的尾部添加任务。提交的任务实际上就是是Runnable对象或Callable对象。

(五) 线程

29. 如何让多个线程一块启动

30. 创建线程的方式和区别

31. 怎么保证线程安全?

32. 两个线程怎么保证A线程执行结束后执行B

33. 如果一个线程修改,另一个线程去读,如何保证修改后的数据对读的线程可见?

34. A B 两个线程执行完毕后,再执行c,使用一个java类库解决

35. 线程的join、sleep和wait有什么区别

wait():

等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。

调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

notify():

唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

notifyAll():

唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。

Join()

等待本线程结束后,下一个线程才可以运行

36. start和run的区别

start()方法来启动线程,真正实现了多线程运行

run()方法当作普通方法的方式调用

37. runable 和callable 有什么区别, 用过futuretask

Runnable和Callable的区别是,

(1)Callable规定的方法是call(),Runnable规定的方法是run().

(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得

(3)call方法可以抛出异常,run方法不可以

(4)运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。

38. Runable如何获取线程执行结果,如何抛出异常。

在Java中提供了一个多线程的框架 Executor,用它将Runnable包装成一个Callable,这样就有返回值和能够抛出异常了。

Public static <T> Callable<T> callable(Runnable task,T result)

FutureTask.get() = result (Callable2)

39. wait/nodify原理

wait()

u  调用时自动释放当前锁,请求OS将自己挂起

u  内置条件队列上的条件发生后被唤醒

u  被唤醒后与其他线程竞争重新获得锁

notify()

u  通知唤醒当前获得锁的对象的内置条件队列上的任意一个等待的线程

u  发出通知后尽快释放当前获得的锁确保等待的线程能够获取

notifyAll()

u  通知唤醒当前获得锁的对象的内置条件队列上的所有等待的线程

u  发出通知后尽快释放当前获得的锁确保等待的线程能够获取

u  只有一个被唤醒的线程能够获得锁,先竞争到锁的线程执行完退出Synchronized块之后其他被唤醒的线程重新竞争直到所有被唤醒的线程执行完毕

40. 实现线程同步的方式;countDownLatch 和join 的区别

CountDownLatch与join的区别:调用thread.join() 方法必须等thread 执行完毕,当前线程才能继续往下执行,而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。

41. 线程状态变更图

二、 集合

42. hashmap实现原理

首先map中有一个table名称大小为16的Entry数组,Entry是map接口中的一个内部接口,用来维护map类型key-value的键值,因此每当向map中存放一个key-value对的时候,都会实例化成Entry对象,这个Entry对象就会存储到上面的table数组里面,存放的时候,根据key的hashcode计算出来的hash值,进行数组内的索引值。

存放的时候首先对key进行检查,key如果是null值的话,则放置在table为0的位置上,因为null值的hashcode为0。

如果在索引的位置上没有元素则直接把entry对象放置到那个对应的索引上面,如果有的话,则调用key的equals方法进行内容的比较,相同的话,则直接进行覆盖,不相同的话,则代表hash冲突了,hashmap内部是通过链表的形式来进行解决的,在这个元素上进行迭代,直到找到Entry->next是null的,则把当前的对象放置在该节点上。

43. Hashmap实现同步的方法

你可以使用Collections.synchronizedMap(HashMap)来包装HashMap作为同步容器,这时它的作用几乎与Hashtable一样,当每次对Map做修改操作的时候都会锁住这个Map对象

44. hashmap并发下有什么缺点

多线程put后可能导致get死循环。CPU利用率过高一般是因为出现了出现了死循环,导致部分线程一直运行,占用cpu时间。问题原因就是HashMap是非线程安全的,多个线程put的时候造成了某个key值Entry key List的死循环,问题就这么产生了。

45. Hashmap用的是什么链表

线性链表。HashMap采取数组加链表的存储方式来实现。HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,急需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

46. hashmap的实现原理,数组为什么快,查找复杂度是多少

查找复杂度:访问一个数组元素的时间复杂度为 O(1)

在内存中,数组的数据连续存放,数据长度固定,这样知道数组开头位置和偏移量就可以直接算出数据地址。

47. hashmap和arraylist的读写效率比较

Hash 集合的性能比任何 List 的性能都要高

48. hashmap和linkdList的读写效率比较

对于并不那么注重访问性能的较小集合而言,List 则是合理的选择。ArrayList 和 LinkedList 集合的性能大体相同,但其内存占用完全不同:ArrayList 的每条目大小要比 LinkedList 小得多,但它不是准确设置大小的。List 要使用的正确实现是 ArrayList 还是LinkedList 取决于 List 长度的可预测性。如果长度未知,那么正确的选择可能是 LinkedList,因为集合包含的空白空间更少。如果大小已知,那么 ArrayList 的内存开销会更低一些。

49. hashmap原理,二次哈希如何实现

java 中hashmap的解决办法就是采用的链地址法

50. hashMap的散列?equal和hashcode?hashcode内部实现?为什么要有hashcode?

通过散列算法,变换成固定长度的输出,该输出就是散列值。

两个obj,如果equals()相等,hashCode()一定相等。

两个obj,如果hashCode()相等,equals()不一定相等

equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。

hashCode()是一个本地方法,它的实现是根据本地机器相关的。

51. concruenthashmap和hashtable线程安全的比较

ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

52. Hashmap和concurrenthashmap哪个性能高?

Concurrenthashmap. 效率提升N倍,默认提升16倍。

53. 为什么hashmap会出现线程不安全;

因为HashMap在扩容是导致了重新进行hash计算。很显然在Map的容量(table.length,数组的大小)有变化时就会导致此处计算偏移变化。这样每次读的时候就不一定能获取到目标索引了。

54. TreeMap he hashmap的区别 ,hash算法简单谈一下

HashMap:底层是哈希表数据结构。线程不同步。

TreeMap:底层是二叉树数据结构,线程不同步,可用于给Map集合中的键进行排序

55. 常用集合有哪些

List、map、set

56. treemap 和hashmap ,hashtable的线程安全通过什么进行控制的

Hashtable是线程安全的,因为它的所有CRUD操作都被synchronized修饰,这种实现是十分缓慢的。

57. List   set   map 的介绍 以及接口实现

三、 NIO:

1. nio 为什么快

2. nio bio aio的区别

3. nio使用

4. 哪些框架用到了NIO

5. 如何实现异步通信

6. NIO的线程模型

四、 其他基础类:

58. string与stringbuffer的区别

三者在执行速度方面的比较:StringBuilder >  StringBuffer  >  String

      对于三者使用的总结: 

1.如果要操作少量的数据用 = String

2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder

3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer

59. 怎么找出两个list中相同的数据

60. linklist arraylist对比

ArrayList:底层数据结构是数组结构,你就可以把它看成是一个可变大小的且只能装对象的数组。因为数组有索引(角标)所以ArrayList的查询速度快,而添加删除元素速度稍慢。因为,你每删除或者添加一个元素,你都要移动所添加或删除元素后面的所有数据,该集合是线程不同步的。

LinkedList:底层数据结构是链表结构。链表数据结构是没有索引的,该数据结构的特点是,增加删除快,而查询比较慢,因为增加删除只 需要找到当前元素,然后断掉当前元素与它前一个和后一个元素的关联即可,和数组比,链表不用重复大部分的数据移动工作,但是因为没有索引所以链表数据结构 要一个一个的查询数据,所以LinkedList的查询速度稍慢。

Vector:底层数据结构也是数组结构的,除了线程安全问题上不同于ArrayList之外,其余几乎一样。

61. “==”和equal的区别

equal是用来判断一个字符串的

==是用来判断数字的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: