多线程进阶(锁策略,自旋+CAS,Synchronized,JUC,信号量)

1:锁策略

锁策略:多线程对共享变量的操作,存在线程冲突的安全问题,引入锁策略解决这种问题。

  1. 乐观锁VS悲观锁
    • 悲观锁:以悲观的心态看待线程冲突的问题,总是认为有其它线程要操作同一个共享变量,每次操作前都加锁
    • 乐观锁:以乐观的心态看待线程冲突的问题,总是认为没有线程会操作同一个共享变量,每次操作都不加锁直接操作共享变量(程序层面不加锁,CPU和操作系统实际有加锁)
  2. 自旋锁
    • 循环,结合CAS不断尝试修改变量,直到修改成功才退出循环。
  3. 读写锁
    • Java中的读写锁:ReentrantReadWriteLock
    • 获取读锁:reentrantReadWriteLock.readLock()
    • 获取写锁:reentrantReadWriteLock.writeLock()
    • 写操作加/放锁:writeLock.lock(); writeLock.unlock()
    • 读操作加/放锁:readLock.lock(); readLock.unlock()
  4. 公平锁VS非公平锁
    • 非公平锁: 竞争的方式获取锁
      • 优点:效率更高不考虑执行顺序
      • 缺点:可能出现线程饥饿(某些线程得不到执行)的现象
    • 公平锁: 以申请锁的顺序来获取锁
      • 优点:公平
      • 缺点:效率差
  5. 可重入锁VS不可不可重入锁
    • Java中基本上都是可重入锁:lock,synchronized
    • 一个线程是否可以多次获取同一个锁,就是可重入锁
  6. 重量级锁VS轻量级锁
    • 重量级锁代价大
    • 轻量级锁代价小
  7. 独占锁VS共享锁
    1. 独占锁:Synchronized,ReentrantLock都属于独占锁
    2. 共享锁:多个线程使用的锁,满足一定条件,就可以并发并行的执行,不满足条件则等待

2:CAS实现乐观锁,自旋锁

Cas:Compare and Swap(比较和交换):线程安全的无锁操作,尝试修改(成功/失败)

public boolean CompareAndSwap (修改一个变量的值){
	尝试修改操作(不会有线程安全问题)
	return 尝试修改的结果
}

在这里插入图片描述

自旋锁:

do{
	获取变量值
}while(!Compare and Swap(变量值)){
   尝试修改变量的值:如果修改成功则退出循环向下执行
        		:如果修改失败则再次循环
}

CAS存在问题:自旋+CAS实现的乐观锁,在修改变量的值时需要判断:

  1. 如果从内存中获取的值,和即将修改时再次获取的值一样则可以修改,反之操作失败,等待下一次消费
  2. 这样的问题为ABA问题,如果两次修改,值又变为原来的值,则会认为没有被修改从而出现问题,如反复充值等
  3. 引入版本号来解决这一问题,修改前和修改后比较版本号,版本号一致则修改,不一致不修改

如何解决ABA问题:引入版本号,不仅比较值,还要比较版本号。

AtomicStampedReference<V> : java提供的类通过版本号解决aba问题

AtomicReferenceAtomicStampedReferenceJUC下的atomic包提供的原子类:

  • AtomicReference不考虑版本号,会发生ABA问题
  • AtomicStampedReference考虑版本号,不会发生ABA问题

模拟三线程为一个账号充值20元且只充值一次,当此账号不足20充值20,一个人消费此账户,初始余额19元,每次消费10元。

如果使用AtomicReference即会发生ABA问题,当充值后,用户两次消费后余额又回到19元,则会进行重复充值。

使用AtomicStampedReference不会产生重复充值操作,即使金额相同,版本号却不同。

public class AtomicStampedReferenceTest {

 //初始金额19,版本号0
 private static AtomicStampedReference<Integer> money=new AtomicStampedReference<>(19,0);

 public static void main(String[] args) {

     for (int i = 0; i < 3; i++) {

         //获取版本号
         int stamp = money.getStamp();

         new Thread(new Runnable() {
             @Override
             public void run() {
                 //系统一直运作
                 while (true){

                     //自旋,充值成功或者不满足充值要求退出
                     while (true){
                         //获取金额
                         Integer reference = money.getReference();
                         //小于20进行充值
                         if(reference<20){
                             //金额+20,版本号+1
                             if(money.compareAndSet(reference, reference+20, stamp,stamp+1)){
                                 System.out.println("充值20元成功,余额为:"+money.getReference());
                                 break;//充值成功退出
                             }
                         }else {
                             break;//不满足充值要求退出
                         }
                     }
                 }
             }
         }).start();

     }


     //消费进程
     new Thread(new Runnable() {
         @Override
         public void run() {
             //一直消费
             while (true){
                 Integer reference = money.getReference();
                 if(reference>10){
                     money.compareAndSet(reference, reference-10, money.getStamp(), money.getStamp()+1);
                     System.out.println("账户余额为:"+money.getReference());
                 }else {
                     System.out.println("账户余额不足,余额为:"+money.getReference());
                     break;
                 }
             }
         }
     }).start();

 }
}

在这里插入图片描述

3:Synchronized

  1. 以对象头加锁的方式,实现线程安全:申请同一个对象锁时,多个线程是同步互斥的
  2. 对象存在于堆中,且有一个对象头,其中就有一个状态的字段表示具体锁类型(无锁,偏向锁,轻量级锁,重量级锁)
  3. Synchronized申请锁成功,是后面的三种锁之一(偏向锁,轻量级锁,重量级锁)

字节码层面的Synchronized操作:

  • Synchronized被编译为字节码文件后,里面存在一个对象头的monitor机制(同步监视器)
  • monitor机制也存在计数器,一个线程可以反复申请一个锁,申请成功一次,计数器加1
    • Synchroniezd----->monitorenter
    • 执行同步代码
    • 正常/异常都会释放锁----->monitorexit(存在两个)
try{
	加锁Synchronized----->monitorenter
	同步代码
	释放锁----->monitorexit
}catch(Exception e){
	释放锁----->monitorexit
}

JVM对Synchroniezd的优化:对象头的锁升级

  1. 无锁:
  2. 偏向锁:只有一个线程申请对象锁,就可以使用偏向锁,因为代价小,同一个线程可重入的方式多次申请同一个对象锁,也使用偏向锁。
  3. 轻量级锁:自旋+CAS 线程冲突不严重的时候相对重量级锁代价低。
  4. 重量级锁:基于系统级别的mutex lock 锁,代价巨大的系统加锁,当线程冲突严重时则升级为重量级锁。

其它锁优化:

  • 锁消除:一个对象,不可能有其它线程持有引用就是线程安全的,可以不加锁

    • //StringBuffer是线程安全的,它的方法都是同步方法
       @Override
          @HotSpotIntrinsicCandidate
          public synchronized StringBuffer append(String str) {
              toStringCache = null;
              super.append(str);
              return this;
          }
          
      //此时StringBuffer为局部变量,不存在其它线程的竞争
      public static void main(String[] args) {
          	//StringBuffer线程安全
              StringBuffer stringBuffer=new StringBuffer();
          	//如果没有锁消除,则append方法会一直频繁加锁放锁,造成资源消耗
              stringBuffer.append("锁");
              stringBuffer.append("消");
              stringBuffer.append("除");
              System.out.println(stringBuffer.toString());
          }
      
  • 锁粗化:共享变量可能会被其它线程持有,但不停的执行加锁/放锁操作,会被优化为锁粗化

    •  	//共享变量
       	private static int count=0;
      
          public static void main(String[] args) throws InterruptedException {
      
              Runnable runnable=new Runnable() {
                  @Override
                  public void run() {
                      //频繁的加锁释放锁的操作,会被优化
                      //申请锁,执行for循环完成后,释放锁
                      for(int i=0;i<1000;i++){
                          synchronized (this){
                              count++;
                          }
                      }
                  }
              };
      
              for(int i=0;i<2;i++){
                  new Thread(runnable).start();
              }
      	}
      

4:JUC并发包

Java并发包:Java.Util.Concurrent,提供多线程安全的且效率高的API

  1. 原子性包Java.Util.Concurrent.Atomic
    在这里插入图片描述

    • AtomicInteger

      • 提供线程安全的原子操作

      • int getAndDecrement()   n--
        int getAndIncrement()   n++
        int incrementAndGet()   ++n
        int decrementAndGet()   --n
        int intValue() 返回当前值
        
    • 原子性并发包:执行这些操作无需加锁,底层原理为CAS+自旋

      •     private static AtomicInteger atomicInteger=new AtomicInteger();
        
            public static void main(String[] args) throws InterruptedException {
        
                    for(int i=0;i<10;i++){
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                for(int i=0;i<100;i++){
                                    atomicInteger.getAndIncrement();
                                }
                            }
                        }).start();
                    }
        
                    Thread.sleep(4000);
                    System.out.println(atomicInteger.intValue());
            }
        
    • AtomicReference和AtomicStampedReference

      • AtomicReference:

        • 构造方法:

          • public AtomicReference(V initialValue) {
                    value = initialValue;
                }
            
        • compareAndSet:

          •   public final boolean compareAndSet(V expectedValue, V newValue) {
                    return VALUE.compareAndSet(this, expectedValue, newValue);
                }
            
      • AtomicStampedReference:

        • 构造方法:

          •  public AtomicStampedReference(V initialRef, int initialStamp) {
                    pair = Pair.of(initialRef, initialStamp);
                }
            
        • getStamp(返回当前版本号):

          •   public int getStamp() {
                    return pair.stamp;
                }
            
        • getReference(返回当前值):

          • public V getReference() {
                    return pair.reference;
                }
            
        • compareAndSet:

          •   public boolean compareAndSet(V   expectedReference,
                                             V   newReference,
                                             int expectedStamp,
                                             int newStamp) {
                    Pair<V> current = pair;
                    return
                        expectedReference == current.reference &&
                        expectedStamp == current.stamp &&
                        ((newReference == current.reference &&
                          newStamp == current.stamp) ||
                         casPair(current, Pair.of(newReference, newStamp)));
                }
            
        • 在CAS和自旋中容易出现ABA问题,使用AtomicStampedReference会引入版本号,避免了ABA问题。

  2. Jva.Util.Concurrent.locks:Lock体系

在这里插入图片描述

  • ReentrantLock:

    • 构造方法:

      • 独占锁,可重入锁,根据构造方法提供的boolean类型参数决定公平或者非公平锁(无参构造器为非公平锁,flase也为非公平锁,true为公平锁)

      •  public ReentrantLock() {
                sync = new NonfairSync();
            }
         public ReentrantLock(boolean fair) {
                sync = fair ? new FairSync() : new NonfairSync();
            }
        
    • 加锁/释放锁:

      • lock():

           public void lock() {
                sync.acquire(1);
            }
        
      • unlock():

           public void unlock() {
                sync.release(1);
            }
        
    • 实例:

      •     private static Lock lock=new ReentrantLock();
        
            private static int count=0;
            
            public static void main(String[] args) throws InterruptedException {
                for(int i=0; i<10;i++){
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            lock.lock();
                            for(int i=0;i<100;i++){
                                count++;
                            }
                            lock.unlock();
                        }
                    }).start();
                }
                Thread.sleep(2000);
                System.out.println(count);
            }
        
  • ReentrantReadWriteLock:

    • 构造方法:

      • public ReentrantReadWriteLock() {
                this(false);
            }
         
        public ReentrantReadWriteLock(boolean fair) {
                sync = fair ? new FairSync() : new NonfairSync();
                readerLock = new ReadLock(this);
                writerLock = new WriteLock(this);
            }
        
    • 获取写锁或者读锁:

      • public static class WriteLock implements Lock
        
      •  public static class ReadLock implements Lock
        
    • 实例:

      • private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
        
        private static ReentrantReadWriteLock.WriteLock writeLock=null;
        
        private static ReentrantReadWriteLock.ReadLock readLock=null;
        
        private static int count=0;
        
        
        
        static {
            writeLock = readWriteLock.writeLock();
            readLock=readWriteLock.readLock();
        }
        
        public static void main(String[] args) throws InterruptedException {
        
            //写操作
            Runnable runnableWrite=new Runnable() {
                @Override
                public void run() {
                    writeLock.lock();
                    for(int i=0;i<100;i++){
                        count++;
                    }
                    writeLock.unlock();
                }
            };
        
            for(int i=0;i<10;i++){
                new Thread(runnableWrite).start();
            }
        
        
            //读操作
            Runnable runnableRead=new Runnable() {
                @Override
                public void run() {
                    readLock.lock();
                    System.out.println(count);
                    readLock.unlock();
                }
            };
        
            for(int i=0;i<10;i++){
                new Thread(runnableRead).start();
            }
        }
        

5:信号量Semaphore

Semaphore: 表示可用资源数,PV操作是原子操作,多线程环境直接使用。

  • 构造方法:

    • //初始资源数目
      public Semaphore(int permits) {
          sync = new NonfairSync(permits);
      }
      //初始资源数目+是否公平
      public Semaphore(int permits, boolean fair) {
          sync = fair ? new FairSync(permits) : new NonfairSync(permits);
      }
      
  • 获取/释放资源:

    • 获取:

          //获取一个资源
      	public void acquire() throws InterruptedException {
              sync.acquireSharedInterruptibly(1);
          }
      	//获取指定数目资源
       	public void acquire(int permits) throws InterruptedException {
              if (permits < 0) throw new IllegalArgumentException();
              sync.acquireSharedInterruptibly(permits);
          }
      
    • 释放:

         	//释放一个资源
      	public void release() {
              sync.releaseShared(1);
          }
      	//释放指定数目资源
      	public void release(int permits) {
              if (permits < 0) throw new IllegalArgumentException();
              sync.releaseShared(permits);
          }
      
  • 使用场景:

    • 有限资源的并发执行

      private static Semaphore semaphore=new Semaphore(5,true);
      
      
          public static void main(String[] args) {
              for(int i=0;i<20;i++){
                  final int j=i;
                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              semaphore.acquire();
                              System.out.println(j);
                              Thread.sleep(3000);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }finally {
                               semaphore.release();
                          }
                      }
                  }).start();
              }
          }
      
    • 多线程并发执行,执行顺序

      private static Semaphore semaphore=new Semaphore(0);
      
      
          public static void main(String[] args) {
      
              Thread thread1=new Thread(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("我是第一步执行的代码");
                      semaphore.release();
                  }
              });
              thread1.start();
      
              Thread thread2=new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          semaphore.acquire(1);
                          System.out.println("我是第二步执行的代码");
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }finally {
                          semaphore.release(2);
                      }
                  }
              });
              thread2.start();
      
      
              Thread thread3=new Thread(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          semaphore.acquire(2);
                          System.out.println("我是第三步执行的代码");
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }finally {
                          semaphore.release(2);
                      }
                  }
              });
              thread3.start();
          }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值