多线程与高并发编程(三)

 atomic、sync、longAdder
 在大量、特别多并发线程情况下,atomic比sync效率高,longAdder比atomic效率高。
 sync慢是因为可能会去申请重量级锁
 longAdder内部用了分段锁,分组一起处理,最后合并(比如1000个线程都++,分成四段每段250++,最后四个合并)【只有在特别大并发的情况下才有优势,一般情况下可能还不如其他两种】

一、各种同步锁

  1. synchronized(可以重入,之前笔记有详细讲解这里不重复)
  2. ReentrantLock
     ①使用(可以一定程度上替代sync):
       Lock lock = new ReentrantLock();
    多线程与高并发编程(三)
       上图为申请的锁,在需要加锁的地方(syncronized的同样地方)lock.lock();即为加锁,加锁结束以后要lock.unlock()解锁,解锁代码一定写在finally块中,确保一定能解锁,sync是在自动解锁的大括号执行完自动解除。
    多线程与高并发编程(三)
     ②lock.tryLock(time, TimeUnit),time代表等待值,TimeUnit代表等待单位,返回值是一个boolean(是否拿到了锁),尝试锁之后也一定要unlock()解锁(如果拿到了)。
       若发现锁被征用,那就等待time时间,如果到时间锁解除了就继续自己的线程执行然后解锁,若没得到(上一个锁内的线程没执行完),则进行等待。(比sync好的一点是,sync的锁第二个线程来看到没释放就直接等待,这就是reentrant比sync好的地方)
     ③lock.lockInterruptibly() 设置可以对interrupt()方法做出响应并加锁(此时就不需要lock.lock()了,这就加了锁,不过解锁一定需要),可以响应别人的打断(sync中一旦wait了,只能让别人notify带能醒来,这也是reentrant比sync好的地方)
       若使用了lock.lockInterruptibly(),可以使用interrupt()打断等待。
     例:线程1lock.lock()加锁,中间代码为永远睡下去,最后解锁,线程2使用lock.lockInterruptibly()申请这种加锁方式(若2也用的lock.lock()那就只能等下去,跟sync一样了),那么就可以在下面线程2.interrupt(),来打断自己的等待。
    多线程与高并发编程(三)
     ④使用这种方法可以为公平锁(先来后到拿到锁)。(sync只有非公平锁)
     ⑤reentrantLock底层是cas,synchronized底层sync
    –背过!–(对生产者消费者多线程面试题的解答方案!):
     ⑥Condition producer = lock.newCondition() 这里的newCondition()相当于新增了一个等待队列,这个队列中的线程抢一把同步锁
     ⑦producer.await() producer这个等待队列所属的线程阻塞
     ⑧producer.singalAll() producer这个等待队列中的所有线程全部唤醒
     总结
    多线程与高并发编程(三)
  3. countDownLatch
     ①new countDownLatch(int),int代表门栓的值,每个线程调用countDown(),然后await()阻塞等待拴住了,最后为0的时候,一起释放
     ②latch.countDown() 当前门栓数量-1
     ③latch.await() 阻塞等待
     不一定几个阻塞,也不一定几个countDown()。
     注:latch.countDown()不会出现线程安全问题,因为锁在latch内部已经帮忙实现了,已经是原子性操作了。
     若int为100,这里的countDown()可能是一个线程一直在阻塞,其他,99个线程都没阻塞,最后等到过去了99个的countDown(),这里的countDown()也不一定是99个线程执行的,可能是第二个线程就循环99次countDown()了,改为0之后,这个线程才接触阻塞。(不一定是几个在阻塞,不是所有都在阻塞)!
  4. cyclicBarrier
     ①定义方式:new CyclicBarrier(parties, new Runnable(){}),parties代表满几个人发车,runnbale接口中是满了之后先干这个中的事再往下走
     ②定义方式:new CyclicBarrier(parties),满parties人,就会取消前面阻塞的状态,发车
     ③barrier.await() 阻塞住
     滚动发车,车满就走。全部阻塞,满了开车。
     注:与3的区别就是,4是parties线程一起在等,而3不一定。
  5. phaser 阶段
     ①继承自phaser类来声明,需要重写onAdvance(int phase, in registeredParties) 这个方法在所有线程都满足第N阶段就会自动调用,这个方法不用调用,是自动的,phase代表阶段(0开始),registeredParties目前阶段几个,返回值为boolean,若为false则整体没完事,返回true代表整个流程结束。
     ②phaser.arriveAndAwaitAdvance(),到达并等待着继续向前走
     ③phaser.arriveAndDeregister(),调用这个方法的,就此结束,再有阶段也不往后走了
      分段执行,多个线程可能有的只走到第一阶段,有的可能一直贯穿到最后阶段。(遗传算法中可能会使用,问的少)
  6. readWriteLock 共享锁(重点)
     ①ReadWriteLock readWriteLock = new ReentrantReadWriteLock();总声明
     ②Lock lock = readWriteLock.readLock();读锁
     ③Lock lock = readWriteLock.writeLock();写锁
     ④lock.lock();
     ⑤lock.unLock();
      读锁只锁除了读之外的数据,比如说当前获得了读锁,又有一个线程来访问发现是读则直接放行,发现是写就不让进入
      写锁只锁写,同理于读锁
      若加ReentrantLock那就是所有的操作只能顺序执行,所以这就是读写锁的优势。
  7. semaphore 允许几个
     ①new Semaphore(permits) 声明,permits允许的数量
      new Semaphore(permits, boolean) 声明,boolean若为true就代表是公平的(后来的线程在后面拍着)
     ②acquire() 取得,从semaphore取出来一个,上面会-1,若permits为0时则别人是再取不到的
     ③release() 我用完了,返回灯,上面+1
      顺序:acquire() - 业务代码 - release()
      所以semaphore是允许最多同时几个线程运行。
      限流!
  8. exchanger 两个线程之间交换
     ①new exchanger<>()声明
     ②exchange(object) 第一个线程调用的时候传入t1字符串,此线程阻塞,第二个线程调用的时候传入t2字符串,此时exchange调换第一个线程的字符串是t2,第二个线程的字符串是t1,然后阻塞取消,继续执行。
  9. LockSupport
     ①LockSupport.park() 当前线程阻塞
     ②LockSupport.unPark(Thread) Thread线程继续运行
      注:unpark可以先在park之前调用,那么park就不会停止了,先行放行
      优点:以前阻塞线程,wait之类的需要加在一种锁对象上才可以整个都停住,而LockSupport不需要;唤醒阻塞的线程以前需要notify,而且不能针对某个指定的线程,因为都在一个队列中,而LockSupport可以。
      总结:以上所有锁的核心都是AQS实现的。