并发设计模式和锁优化以及jdk8并发新特性

1 设计模式编程

(1) 单例模式 设计模式

     保证一个类只能一个对象实现。正常的单例模式分为懒汉式和饿汉式,饿汉式就是把单例声明称static a=new A(),系统第一次调用的时候生成(包括调用该类的其余静态资源也会生成),懒汉式就是系统调用get函数的时候,加个锁判断单例对象是否存在,存在就返回不存在就声明一个。好一点的懒汉式应该把单例加一个静态内部类,第一次访问的类的时候静态内部类不会初始化,当调用的get方法的时候再实例化,这样不用加锁效率高一些,数组

 public class StaticSingleton {安全

   private StaticSingleton(){并发

     System.out.println("StaticSingleton is create");jvm

  }函数式编程

   private static class SingletonHolder {函数

     private static StaticSingleton instance = new StaticSingleton();高并发

   }性能

   public static StaticSingleton getInstance() {

     return SingletonHolder.instance;

   }

(2)不变模式  类和变量都声明为final,只要建立就不可变,常见的string Integer Double等都是不可变。

(3) future模式  客户端请求服务端数据,若是服务端处理时间较长,能够返回一个空值(相似代理),启动一个线程专门设值。客户端能够先干别的,当想要试用这个值时,能够从代理里拿,若是代理值已经设置好直接返回,若是没设置好则wait,等设置好了的时候notify。  能够向excutor里提交一个实现了Collable的对象,会返回一个Future,而后使用这个future.get()拿值。

(4) 生产者消费者模式  专门有生产者生产数据,消费者消费数据,中间靠线程安全的队列做为公共区域,各线程都从这个区域里写值和读值。各个线程无需了解对存在,只要负责本身的事情便可,也符合开闭原则。

2 锁优化 

   具体思路:减小锁持有时间,减少锁粒度,锁分离,锁粗化,锁消除。

    (1)减小锁持有时间  尽可能少的加锁代码,例如用具体代码段代替方法加锁。 

  (2)减少锁粒度   把大对象尽可能改为小对象,增长并行度减小锁竞争。同时有利于偏向锁,轻量级锁。例如ConcurrentHashMap

  (3)锁分离   读写分离,读读可重入,读写互斥,写写互斥。另外一种分离,例如 LinkedBlockingQueue ,存数据和取数据从队列两端操做,两端各自加锁控制便可,两端的锁互不影响。

    (4)锁粗化 若是一段程序要屡次请求锁,锁之间的代码执行时间比较少,就应该整合成一个锁,前提是不用同步的部分执行时间短。例如for循环里面申请锁,若是for循环时间不长,能够在for外面加锁。

    (5)锁消除 编译器级别的操做,若是jdk发现锁不可能被共享,会擦除这个锁。原理是逃逸分析,例如stringbuffer,自己操做是加锁的,若是只在局部使用不存在并发访问,那么会擦除锁,若是对象逃逸出去例如赋值给全局变量等,面临并发访问,就不会擦除锁。能够经过jvm参数来指定是否使用锁消除。

3 jdk的锁优化  sychronized的优化,由虚拟机完成 

  (1)偏向锁  在竞争比较少的状况下,会使用偏向锁来提升性能。

       *对象头 markword,共32位,存hash,锁信息(指向锁的指针),垃圾回收标志(偏向锁id),年龄信息,偏向锁线程id,monitor信息等。

 

  一个线程争取到对象资源时,对象会在对象头中标记为偏向,而且将线程id写入到对象头中,下次若是这个线程再来能够不经过锁竞争直接进入同步块。当其余线程访问的时候,偏向结束,升级为轻量级锁。因此在竞争激烈的场景下偏向锁会增长系统负担,jvm默认是开启偏向锁的,能够经过jvm参数设置取消偏向锁   

      *偏向锁只须要在置换ThreadID的时候依赖一次CAS原子指令,在只有一个线程执行同步块时进一步提升性能。

  (2)轻量级锁 轻量级锁所适应的场景是线程交替执行同步块的状况,若是存在同一时间访问同一锁的状况,就会致使轻量级锁膨胀为重量级锁。

   轻量级锁的加锁过程 :

      1)在代码进入同步块的时候,若是同步对象锁状态为无锁状态(偏向锁也是无锁),虚拟机首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。

      2)拷贝对象头中的Mark Word复制到锁记录中。

      3)拷贝成功后,虚拟机将使用CAS操做尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。

      4)若是这个更新动做成功了,那么这个线程就拥有了该对象的锁,而且对象Mark Word的锁标志位设置为处于轻量级锁定状态。

     5)若是这个更新操做失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,若是是就说明当前线程已经拥有了这个对象的锁,那就能够直接进入同步块继续执行。不然说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了避免让线程阻塞,而采用循环去获取锁的过程。

      轻量级锁解锁时,把复制的对象头替换回去(cas)若是替换成功,锁结束,若是失败,说明有竞争,升级为重量级锁(先会自旋一下等等看),notify 唤醒其余等待线程。

  * 轻量级锁是为了在线程交替执行同步块时提升性能。         

  (3)自旋锁

          轻量级锁加锁失败之后,可能先自旋一段时间,尝试得到轻量级锁,不会着急升级为重量级锁挂起。若是自旋过多,会形成cpu资源浪费,JDK采用了适应性自旋,简单来讲就是一开始设置固定自旋次数,线程若是自旋成功了,则下次自旋的次数会更多,若是自旋失败了,则自旋的次数就会减小。

       *自旋若是成功,能够省略线程挂起的时间。jdk7之后默认使用。

3 jdk8新特性

 (1)LongAdder  相似automicLong, 可是提供了“热点分离”。过程以下:若是并发不激烈,则与automicLong 同样,cas赋值。若是出现并发操做,则使用数组,数组的各元素之和为真实value,让操做分散在数组各个元素上,把并发操做压力分散,一遇到并发就扩容数组,最后达到高效率。通常cas若是遇到高并发,可能一直赋值失败致使不断循环,热点分离能够解决这个问题。有点相似concurrenthashmap,分而治之。

 (2)completableFuture 对Future进行加强,支持函数式编程的流式调用。提供更多功能,压缩编码量。

 (3)stampedLock 改进读写锁,读不阻塞写。若是读的时候,发生了写,应该从新读,不是阻塞写。解决了通常读写锁读太多致使写一直阻塞的问题,读线程发现数据不一致时触发从新读操做。 原理是维护了一个stamp标记,在添加写锁的释放写锁的时候,stamp都会改变(好比++),代码在加读锁的时候,能够先获得stamp,读完数据释放读锁的时候,调用validate方法,检验刚才stamp和如今stamp是否相同,若是相同,说明读的过程当中没有修改,读取成功,若是不相同,则说明读的时候发生了写,那么接下来两种策略,一个是继续用当前stamp为初试,继续读,读完比较stamp,是乐观的办法;另外一种直接调用readlock(),升级为正常的读锁,是悲观办法。

相关文章
相关标签/搜索