Java并发编程的艺术 读后记要(2)

来源:互联网 发布:淘宝客服如何做到秒回 编辑:程序博客网 时间:2024/05/20 18:20

Java下的并发解决

传统的解决方法(JDK1.5之前)

传统的解决方法很简单。
* 如果是单个数据的话,对数据使用volatile关键字修饰,这个关键字可以保证数据不会停留在CPU的写缓存中,而是写入内存,下一次对该数据的读取一定读到的内存中的最新数据。也就是解决了可见性问题。但是volatile变量原子性还有问题,对volatile变量多线程下的自增自减操作,不是原子的。
* 如果是集合类,传统的做法是交给Collections.synchronizated*方法,得到一个同步的集合,但是这个集合的效率并不会很高。
* 如果是代码块或者数据结构,传统的解决方法是使用关键字synchronizated,这个关键字可以修饰类、方法、变量甚至代码段。这个关键字会告知JVM,当前线程得到了某一个对象(根据修饰的内容不同,对象不同)的锁,对当前代码的访问就和单线程下一样了。这个关键字对可见性和原子性都解决了。但是这种机制得到的锁是“重量锁”,也就是获取锁和释放锁都比较浪费时间,如果需要同步加锁的代码太多,这种方法效率较低。
* 如果需要多线程间通信同步,可以使用定义在Object上的waitnotify*方法,可以使线程在调用wait方法时进入等待状态,在调用notify*方法时被唤醒。

现在的解决方法(JDK1.5之后)

JDK1.5之后引入了很多并发的类,放到并发包下,再加上对原有机制的优化,并发的效率更高了。
* 如果是单个数据,依旧使用volatile关键字,在JDK5后,该关键字的能力得到了增强,有了获取锁释放锁的语义,也就是说如果是对单个数据加锁,完全可以用volatile替代。但是该关键字还是自增自减操作不是原子的。
* 对于需要原子操作的单个数据,并发包下有Atomic*的一系列类,实现了原子地修改、自增、自减一个变量。
* 如果是集合类,并发包下都有并发容器可供直接使用,就和使用一般的集合类一样,当然内部已经实现了同步,效率更高。
* 如果是代码块或者数据结构,可以使用并发包下的Lock的一系列实现类(目前只有可重入锁和读写锁),替代synchronizated关键字,优点是效率更高,缺点是需要显式地获取和释放锁。
* 如果需要线程间通信同步,可以使用并发包下的Condition接口,相当于信号量,可以实现在特定情况下等待或者继续执行,与Objectwaitnotify操作相同。
* 此外,JDk1.5后还提供并发工具类,方便应对各种兵法情况。当然,这些工具类的实现基础,还是上面提到的volatileLockCondition

Java解决并发方法深入

  • 对于volatile关键字:该关键字在底层就是用到了之前提到的内存屏障,确保对数据的修改一定是在其他操作之前(或之后),而且其他CPU可见(刷新缓存区),JDK1.5是增加了新的屏障,让该关键字拥有和锁一样的功能。
  • 对于synchronizated关键字:该关键字会获得一个Java对象的锁,Java每一个对象都有锁,这个锁称为监视器(Monitor),实现上,它存在于一个Java对象的对象头里的Mark Word部分,这部分包括是否有锁的2bit表示,以及指向锁具体信息的指针等。在JVM字节码的层次,该关键字的使用会产生两个指令:monitorentermonitorexit,这两个指令用来确保对对象的互斥访问。
  • Java并发包下的锁内部实现是通过一个FIFO的同步队列(AbstractQueuedSynchronizer,AQS),Condition内部也一样。AQS内维护一个int型的state,可以通过原子的操作初始化、get、set、CAS。需要同步的线程被包装成节点,竞争state,如果state归零,说明当前资源已经分配完,队列中的节点需要等待,如果state不为零,则根据FIFO的规则或其他规则唤醒节点,重新设置state。
  • 需要等待的线程,有两种等待策略:一种是通过自旋锁,即线程不断地询问执行的时机是否合适(通过循环),这种方法需要消耗CPU,但是线程反应快;另一种是将当前线程状态修改为等待,然后期待其他线程对它进行唤醒,这种方法不会空耗CPU,但是线程状态的修改调度,操作比较重。在Java的并发包下,AQS内在时间较短的情况下是通过自旋锁的,较长时间自旋锁内部还会通过LockSupport对线程进行阻塞,不断循环头节点是否可以执行。
  • JDK1.5之后,有了这样一个概念,任务的执行单元和工作单元相分离。在JDK1.5之前,工作单元和执行单元都是Runnable,工作通过重写run方法,执行通过包装成线程调用start方法。JDK1.5之后,工作单元还是Runnable,或者Callable(与RUnnable的区别是Callable有返回值),执行单元变成了Executor。Executor接口下实现了好几种线程池的类。
0 0
原创粉丝点击