多线程---详解各种锁和AQS原理

详解各种锁与锁的使用

  • 1. synchronized
    • 可重入
      • 同一个线程
      • 不同线程
  • 2. volatile
    • 保证线程可见性
    • 禁止指令重排序
      • 查看结果
  • 3. 锁优化
    • 锁细化
    • 锁粗化
    • 锁对象
  • 4. CAS(无锁优化, 自旋)
  • 5. JUC同步锁
    • 0. 前置知识(AQS)
    • 1. ReentrantLock(可重入锁, 排他锁)
      • 源码解析
      • 同一个线程
      • 不同线程
      • tryLock
      • lockInterruptibly()
      • 公平锁
    • 2. ReadWriteLock(读写锁)
    • 3. LangAdder(分段锁)
    • 4. CountDownLatch(倒数门栓)
    • 5. CyclicBarrier(循环栅栏)
    • 6. CountDownLatch和CyclicBarrier的区别
    • 7. Semaphore
    • 8. LockSupport
  • 6. 测试题
    • 线程同步
      • 方法一 普通判断
        • 线程不同步
        • 使用线程同步
      • 方法二 wait()和notify()
        • 反例
        • 优化方法
      • 方法三 CountDownLatch()
        • 一个countDownLatch
        • 优化CountDownLatch
      • 方法四 LockSupport
        • 普通方案
        • 优化方案 LockSupport
      • 方法五 Semaphore
    • 容器- 生产者消费者
      • 1. 使用wait和notify/notifyAll来实现
      • 2. 使用lock.newCondition()
  • 总结
    • AQS原理
    • AQS核心代码
      • 获取锁的代码
      • 释放锁的代码
    • ReentranLock原理
    • 如何自己来实现一把锁?
    • 自旋锁

1. synchronized

详细信息参考下方文章

https://blog.csdn.net/A_Java_Dog/article/details/118679431

  • synchronized(Object)

    Object不能是String , Integer, Long , 因为字符串最终都是基于一个字符串常量池中的数据。

  • 线程同步

    synchronized

    • 锁的是对象, 是不是代码

    • synchronized void methodName 锁定的是this(当前对象)
      synchronized static void m静态方法锁定的是XXX.class

      public class T {
      
          private int count = 10;
      
          public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
              count--;
              System.out.println(Thread.currentThread().getName() + " count = " + count);
          }
      }
      
      private static int count = 10;
      
      //因为这个m是静态方法, 所以这里等同于synchronized(T.class)
      public synchronized static void m() { 
          count--;
          System.out.println(Thread.currentThread().getName() + " count = " + count);
      }
      
    • 锁定方法和非锁定方法同步执行

        public static void main(String[] args) {
            T t = new T();
            //m1是同步方法, m2是非同步方法, 如果m1和m2不能同时调用, 则肯定会在m1执行完成后,
            //也就是输出m1 end后, 才会执行m2
            //结果显示: m1开始执行后, m2立刻开始执行, 也就是两者执行不受影响
            new Thread(t::m1, "t1").start();
            new Thread(t::m2, "t2").start();
        }
      
        public synchronized void m1() {
            System.out.println(Thread.currentThread().getName() + " m1 start...");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " m1 end");
        }
      
        public void m2() {
            System.out.println(Thread.currentThread().getName() + " m2 start...");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " m2 end");
        }
      
    • 锁升级- 偏向锁, 自旋锁, 重量级锁

    • 使用条件:1. 执行时间短(加锁代码),线程数少,用自旋

      ​ 2. 执行时间长,线程数多,用重量级锁(系统锁)(系统锁有等待队列, 不占用CPU资源)

      ​ 3. 操作消耗时间长->使用重量级锁

可重入锁, 非公平锁, 悲观锁

可重入

同一个线程

synchronized代码中 , 同一个线程可再次调用其他synchronized修饰的方法,如下例所示

public class T01_ReentrantLock1 {
   synchronized void m1() {
      for(int i=0; i<10; i++) {
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(i);
         if(i == 2) {
             //如果synchronized锁不可重入, 则这里将会阻塞住, 因为当前线程执行的m1方法持有该对象的锁,
            this.m2();
             //这里输出m2的结果, 证明同一个线程下synchronized锁可重入
         }
      }
      
   }
   
   synchronized void m2() {
      System.out.println("m2 ...");
   }
   
   public static void main(String[] args) {
      T01_ReentrantLock1 rl = new T01_ReentrantLock1();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
}

执行结果

多线程---详解各种锁和AQS原理_第1张图片

不同线程

/**
 * 本例中由于m1锁定this,只有m1执行完毕的时候,m2才能执行
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.TimeUnit;

public class T01_ReentrantLock1 {
   synchronized void m1() {
      for(int i=0; i<10; i++) {
         try {
            TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(i);
      }
      
   }
   
   synchronized void m2() {
      System.out.println("m2 ...");
   }
   
   public static void main(String[] args) {
      T01_ReentrantLock1 rl = new T01_ReentrantLock1();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
       //由于m1锁定this,只有m1执行完毕的时候,m2才能执行
      new Thread(rl::m2).start();
   }
}

执行结果

多线程---详解各种锁和AQS原理_第2张图片

2. volatile

保证线程可见性

  • MESI
  • 缓存一致性协议

见上方 线程三大特性-可见性解析

禁止指令重排序

使用以下单例模式, 懒汉式的双重检查为例

package com.cyc.design.singleton;

/**
 * @author chenyunchang
 * @version 1.0
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Singleton06 {

    //volatile关键字禁止指令重排
    private static volatile Singleton06 INSTANCE;


    public void c() {
        System.out.println("C");
    }

    /**
     * 构造方法为私有,只能在当前类中new,外部类无法new出来
     */
    private Singleton06() {
    }

    public static Singleton06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Singleton06.class) {
                if (INSTANCE == null) {
                    try {
                        //这里让进入此代码块的线程睡一毫秒
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton06();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() ->
                    System.out.println(Singleton06.getInstance().hashCode())
            ).start();
        }
    }

}

查看结果

多线程---详解各种锁和AQS原理_第3张图片

为什么要加volatile?

为了防止指令重排。这里涉及到类的加载过程。

首先,第一个线程进来了, 加上锁之后,进入到INSTANCE = new Singleton06();代码,在初始化进行到一半的时候,也就是在preparation阶段,已经给Singleton06申请完内存,里面的成员变量已经赋过默认值,比如0,此时INSTANCE 已经指向这个分配的内存, 已经不再是null,此时另外一个线程进来了,由于此时INSTANCE 已经进行了半初始化状态,所以在if (INSTANCE == null)为false,此时另一个线程会拿到这个INSTANCE中的成员变量进行操作, 这样显然是不满足要求的。

想要解析这个问题, 需要查看其字节码文件

例如下面这个测试类T, 使用idea插件查看其字节码文件

多线程---详解各种锁和AQS原理_第4张图片

在0 new #2 之后,已经申请过内存。

4 invokespecial #3 这个给类中的静态变量赋初始值

在调用完4之后,才会把这块内存赋值给t,但是由于指令可能会重排的原因, 如果先执行的是7 astore_1, 相当于先把这个地址扔到内存中, 然后在进行的T初始化, 这种情况下,在双重检查懒汉式单例中,就会出现有别的线程读取到半初始化的单例。

  • DCL单例
  • Double Check Lock

3. 锁优化

锁细化

 /**
 * synchronized优化
 * 同步代码块中的语句越少越好
 * 比较m1和m2
 * @author cyc
 */
package com.cyc.juc.c_016_LockOptimization;
import java.util.concurrent.TimeUnit;

public class FineCoarseLock {
   
   int count = 0;

   synchronized void m1() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
      count ++;
      
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   
   void m2() {
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
      //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
      synchronized(this) {
         count ++;
      }
      //do sth need not sync
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   

}

锁粗化

当一个方法中有很多代码块都加锁时 , 不如直接将锁加在方法上

锁对象

/**
 * 锁定某对象o,如果o的属性发生改变,不影响锁的使用
 * 但是如果o变成另外一个对象,则锁定的对象发生改变
 * 应该避免将锁定对象的引用变成另外的对象或者将对象设为final类型
 * @author cyc
 */
package com.cyc.juc.c_017_MoreAboutSync;

import java.util.concurrent.TimeUnit;


public class SyncSameObject {

   /*final*/ Object o = new Object();

   void m() {
      synchronized(o) {
         while(true) {
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
            
            
         }
      }
   }
   
   public static void main(String[] args) {
      SyncSameObject t = new SyncSameObject();
      //启动第一个线程
      new Thread(t::m, "t1").start();
      
      try {
         TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      //创建第二个线程
      Thread t2 = new Thread(t::m, "t2");
      
      t.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会
      
      t2.start();
      
   }

   

}

4. CAS(无锁优化, 自旋)

在java.util.concurrent包下, Atomic开头的都是通过CAS来保证线程安全的类

https://blog.csdn.net/A_Java_Dog/article/details/118679431

cas(期望值,更新值)

m=0

m++

expected = read m;

cas(0,1){
for(;//如果当前m值==0 m=1

}

5. JUC同步锁

0. 前置知识(AQS)

AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,保证线程之间的可见性和有序性。state的访问方式有三种:

  • getState()
  • setState()
  • compareAndSetState()

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

1. ReentrantLock(可重入锁, 排他锁)

源码解析

多线程---详解各种锁和AQS原理_第5张图片
多线程---详解各种锁和AQS原理_第6张图片

多线程---详解各种锁和AQS原理_第7张图片
多线程---详解各种锁和AQS原理_第8张图片
多线程---详解各种锁和AQS原理_第9张图片

acquire(int)

此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。

  1. tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
  2. addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  3. acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

ReentrantLock属于JUC自带的锁, CAS类型,需要自己手动解锁,加锁一定要写在try finally中,保证在最终一定能解锁, 而synchronized属于自动上锁,自动释放锁。

synchronized代码块字节码展示:

多线程---详解各种锁和AQS原理_第10张图片

ReentrantLock代码块字节码展示:

多线程---详解各种锁和AQS原理_第11张图片

同一个线程

/**
 * reentrantlock用于替代synchronized
 * 由于m1锁定this,只有m1执行完毕的时候,m2才能执行
 * 使用reentrantlock可以完成同样的功能
 * 需要注意的是,必须要必须要必须要手动释放锁(重要的事情说三遍)
 * 使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放
 * @author cyc
 */
public class T02_ReentrantLock2 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock(); //synchronized(this)
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);

                System.out.println(i);
                if (i == 2) {
                   this.m2();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock();
            System.out.println("m2 ...");
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        T02_ReentrantLock2 rl = new T02_ReentrantLock2();
        new Thread(rl::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

不同线程

public class T02_ReentrantLock2 {
    Lock lock = new ReentrantLock();

    void m1() {
        try {
            lock.lock(); //synchronized(this)
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    void m2() {
        try {
            lock.lock();
            System.out.println("m2 ...");
        } finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {
        T02_ReentrantLock2 rl = new T02_ReentrantLock2();
        new Thread(rl::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(rl::m2).start();
    }
}

tryLock

m1执行10秒钟, 在m1执行期间, m2去尝试在5秒钟获取锁,如果获取不到则继续执行下面的方法,输出m2 …false。

/**
 * 使用reentrantlock可以进行“尝试锁定”tryLock,这样无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T03_ReentrantLock3 {
   Lock lock = new ReentrantLock();

   void m1() {
      try {
         lock.lock();
         for (int i = 0; i < 10; i++) {
            TimeUnit.SECONDS.sleep(1);

            System.out.println(i);
         }
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         lock.unlock();
      }
   }

   /**
    * 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行
    * 可以根据tryLock的返回值来判定是否锁定
    * 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中
    */
   void m2() {
      /*
      boolean locked = lock.tryLock();
      System.out.println("m2 ..." + locked);
      if(locked) lock.unlock();
      */
      
      boolean locked = false;
      
      try {
         // 在5秒内尝试获取锁, 如果获取不到, 则放弃去获取
         locked = lock.tryLock(5, TimeUnit.SECONDS);
         System.out.println("m2 ..." + locked);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         if(locked) lock.unlock();
      }
      
   }

   public static void main(String[] args) {
      T03_ReentrantLock3 rl = new T03_ReentrantLock3();
      new Thread(rl::m1).start();
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      new Thread(rl::m2).start();
   }
}

执行结果

多线程---详解各种锁和AQS原理_第12张图片

将m1方法改为休眠3秒钟,

   for (int i = 0; i < 3; i++) {
            TimeUnit.SECONDS.sleep(1);

            System.out.println(i);
         }

输出结果m2 …true

lockInterruptibly()

/**
 * 使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,
 * 在一个线程等待锁的过程中,可以被打断
 * @author cyc
 */
public class T04_ReentrantLock4 {
		
	public static void main(String[] args) {
		Lock lock = new ReentrantLock();
		
		
		Thread t1 = new Thread(()->{
			try {
				lock.lock();
				System.out.println("t1 start");
				TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
				System.out.println("t1 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t1.start();
		
		Thread t2 = new Thread(()->{
			try {
				//lock.lock();
				lock.lockInterruptibly(); //可以对interrupt()方法做出响应
				System.out.println("t2 start");
				TimeUnit.SECONDS.sleep(5);
				System.out.println("t2 end");
			} catch (InterruptedException e) {
				System.out.println("t2 interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t2.start();
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.interrupt(); //打断线程2的等待
		
	}
}

  • 输出结果

多线程---详解各种锁和AQS原理_第13张图片

公平锁

new ReentrantLock(true)为新建一个公平锁,所谓公平锁, 即每个线程获取锁之前, 都要去锁的等待队列里去查看有无正在等待锁的线程, 如果有,则加入等待队列,按照先后顺序去获取锁。如果没有,则直接去拿锁。

new ReentrantLock()默认为非公平锁,忽视等待队列, 直接去争抢锁。

注: synchronized属于非公平锁

/**
 *
 * ReentrantLock还可以指定为公平锁
 *
 * @author cyc
 */
package com.cyc.juc.c_020_lock;

import java.util.concurrent.locks.ReentrantLock;

public class T05_ReentrantLock5 extends Thread {

    private static ReentrantLock lock = new ReentrantLock(true); //参数为true表示为公平锁(默认为false),请对比输出结果

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得锁");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        T05_ReentrantLock5 rl = new T05_ReentrantLock5();
        Thread th1 = new Thread(rl);
        Thread th2 = new Thread(rl);
        th1.start();
        th2.start();
    }
}
  • 输出结果

多线程---详解各种锁和AQS原理_第14张图片

2. ReadWriteLock(读写锁)

/**
 * 读写锁(共享锁+排他锁), 读的时候是共享, 写的时候是排他
 * @author fei
 */
public class T10_TestReadWriteLock {
    static Lock lock = new ReentrantLock();
    private static int value;

    static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static Lock readLock = readWriteLock.readLock();
    static Lock writeLock = readWriteLock.writeLock();

    public static void read(Lock lock) {
        try {
            lock.lock();
            Thread.sleep(1000);
            System.out.println("read over!");
            //模拟读取操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void write(Lock lock, int v) {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = v;
            System.out.println("write over!");
            //模拟写操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }



    public static void main(String[] args) {
        //排它锁
//        Runnable readR = ()-> read(lock);
        //读写锁-读, 使用读写锁, 多个线程可同时读取同一个数据, 无需等待
        Runnable readR = ()-> read(readLock);

//        Runnable writeR = ()->write(lock, new Random().nextInt());
        //读写锁-写, 使用读写锁, 写的时候, 其他线程需要等待该线程之后完毕之后, 才可以进行读写
        Runnable writeR = ()->write(writeLock, new Random().nextInt());

        for(int i=0; i<18; i++) new Thread(readR).start();
        for(int i=0; i<2; i++) new Thread(writeR).start();


    }
}

3. LangAdder(分段锁)

/**
 * 几种锁的执行效率测试
 * count1,count2, count3分别使用不同的方式来实现递增
 */
public class T02_AtomicVsSyncVsLongAdder {
    static long count2 = 0L;
    static AtomicLong count1 = new AtomicLong(0L);
    //LongAdder内部做了一种类似分段锁(分段锁也是CAS操作)的操作, 例如本例中, 1000个线程可能会分为多个组,
    // 每组单独计算, 计算出的值最后累加一下, 得出总计值, LongAdder在线程特别多的时候, 优势比较明显
    static LongAdder count3 = new LongAdder();

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[1000];

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) {
                            count1.incrementAndGet();
                        }
                    });
        }

        long start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        long end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("Atomic: " + count1.get() + " time " + (end-start));
        //-----------------------------------------------------------
        Object lock = new Object();

        for(int i=0; i<threads.length; i++) {
            threads[i] =
                new Thread(() -> {
                    for (int k = 0; k < 100000; k++) {
                        synchronized (lock) {
                            count2++;
                        }
                    }
                });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        end = System.currentTimeMillis();


        System.out.println("Sync: " + count2 + " time " + (end-start));


        //----------------------------------
        for(int i=0; i<threads.length; i++) {
            threads[i] =
                    new Thread(()-> {
                        for(int k=0; k<100000; k++) {
                            count3.increment();
                        }
                    });
        }

        start = System.currentTimeMillis();

        for(Thread t : threads ) {
            t.start();
        }

        for (Thread t : threads) {
            t.join();
        }

        end = System.currentTimeMillis();

        //TimeUnit.SECONDS.sleep(10);

        System.out.println("LongAdder: " + count1.longValue() + " time " + (end-start));
    }
}

4. CountDownLatch(倒数门栓)

倒数门栓

/**
 * CountDown 倒数, Latch门栓, CountDownLatch倒数几个数打开门栓
 * 常用在控制线程上, 可以控制线程执行结束后, 才开始执行下面的代码
 *
 * @author cyc
 * @date 2021-08-07 16:03:42
 */
public class T06_TestCountDownLatch {
    public static void main(String[] args) {
        usingJoin();
        //使用CountDownLatch比join更加灵活
        usingCountDownLatch();
    }

    private static void usingCountDownLatch() {
        Thread[] threads = new Thread[100];
        CountDownLatch latch = new CountDownLatch(threads.length);

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                //下面的threads[i].start()执行过后才开始执行以下代码,
                //每执行一次, latch.countDown()会相应减1, 当减为0时,
                // latch.await(),才会执行
                int result = 0;
                for(int j=0; j<10000; j++) {
                    result += j;
                }
                latch.countDown();
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        try {
            System.out.println("latch await 等待执行");
            //这里会阻塞住主线程, 只有当latch减为0的时候, 才会继续执行下面的代码
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("end latch");
    }

    private static void usingJoin() {
        Thread[] threads = new Thread[100];

        for(int i=0; i<threads.length; i++) {
            threads[i] = new Thread(()->{
                int result = 0;
                for(int j=0; j<10000; j++) {
                    result += j;
                }
            });
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++) {
            try {
                //每一个线程都合并到主线程上, 主线程要等所有线程执行结束之后,
                // 才开始继续执行下面的代码(准备发车, 坐满就走)
                threads[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("end join");
    }
}

  • 执行结果

多线程---详解各种锁和AQS原理_第15张图片

5. CyclicBarrier(循环栅栏)

循环栅栏

package com.cyc.juc.c_020_lock;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Cyclic循环的, Barrier栅栏, CyclicBarrier循环的栅栏
 * @author cyc
 * @date 2021-08-07 16:35:30
 */
public class T07_TestCyclicBarrier {
    public static void main(String[] args) {
        //CyclicBarrier barrier = new CyclicBarrier(20);	

        CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));

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

                new Thread(()->{
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            
        }
    }
}
  • 执行结果

多线程---详解各种锁和AQS原理_第16张图片

  • 观察源码

多线程---详解各种锁和AQS原理_第17张图片
多线程---详解各种锁和AQS原理_第18张图片

点击barrier.await()方法进入发现,当执行的线程数量达到new CyclicBarrier(n)中的n时, 重新生成一次新的循环栅栏, 通过观察结果可知, n值是20, 执行的线程数量为100, 所以输出了5个CyclicBarrier中run方法的执行结果

应用场景

  • 复杂操作和并发执行

    ​ 例如有三个线程A,B,C , 分别去执行访问数据库, 访问网络和读取文件, 此时 , 就可以使用CyclicBarrier, 他可以在所有线程都执行完毕之后, 才去执行主流程.

6. CountDownLatch和CyclicBarrier的区别

CountDownLatch是设定一个值, 每次countDown以下, 直到等于0, 但是并不一定要在每一个线程都countDown一下, 在一个线程里CountDown也可以

就比如这样写, 依然可以

        Thread t = new Thread(() -> {
            for (int i = 0; i < threads.length; i++) {
                latch.countDown();
            }
        });
        t.start();

而CyclicBarrier就不一样了, 他必须在每个线程里调用await()方法, 才可以

测试一下

for (int i = 0; i < 100; i++) {
    try {
        System.out.println("等待中");
        barrier.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
}

只会输出一次 等待中, 然后就堵塞在那里了。

7. Semaphore

常用在限流上,

例如车道和收费站 , 收费站只有两个, 车道可能有多个

package com.cyc.juc.c_020_lock;

import java.util.concurrent.Semaphore;

/**
 * Semaphore 信号灯, Semaphore(n)最多允许n个线程同时运行
 * 常用在限流上
 * @author fei
 */
public class T11_TestSemaphore {
    public static void main(String[] args) {
        //允许两个线程同时执行, 没有先后顺序
        Semaphore s = new Semaphore(2);
        //允许两个线程同时执行, 公平锁,内部有等待队列,并按照先后顺序执行
//        Semaphore s = new Semaphore(2, true);
        //允许一个线程同时执行
        //Semaphore s = new Semaphore(1);

        new Thread(()->{
            try {
                //阻塞方法, 如果取不到 , 则阻塞在这里
                //这里取一下, new Semaphore(1)中的1就会变成0
                s.acquire();

                System.out.println("T1 running...");
                Thread.sleep(200);
                System.out.println("T1 running...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //将1加回去,即将线程加回去
                s.release();
            }
        }).start();

        new Thread(()->{
            try {
                s.acquire();

                System.out.println("T2 running...");
                Thread.sleep(200);
                System.out.println("T2 running...");

                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

8. LockSupport

/**
 * LockSupport 可以叫醒指定线程, unPark可以先与park之前调用
 */
public class T13_TestLockSupport {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if(i == 5) {
                    //阻塞当前线程, 需要手动唤醒
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t.start();

        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after 8 senconds!");

        LockSupport.unpark(t);
    }
}

  • 执行结果

多线程---详解各种锁和AQS原理_第19张图片

6. 测试题

线程同步

实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束

方法一 普通判断

线程不同步

public class T01_WithoutVolatile {

	List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}
	
	public static void main(String[] args) {
		T01_WithoutVolatile c = new T01_WithoutVolatile();

		new Thread(() -> {
			for(int i=0; i<10; i++) {
				c.add(new Object());
				System.out.println("add " + i);
				
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "t1").start();
		
		new Thread(() -> {
			while(true) {
				if(c.size() == 5) {
					break;
				}
			}
			System.out.println("t2 结束");
		}, "t2").start();
	}
}

  • 输出结果
add 0
add 1
add 2
add 3
add 4
add 5
add 6
add 7
add 8
add 9

由于线程1给list加的值,并不同立即同步给线程2 , 所以线程2会无法结束

使用线程同步

/**
 * 给lists添加volatile之后,t2能够接到通知,但是,t2线程的死循环很浪费cpu,如果不用死循环,
 * 而且,如果在if 和 break之间被别的线程打断,得到的结果也不精确,
 * @author cyc
 */
public class T02_WithVolatile {

   //添加volatile,使t2能够得到通知
   //volatile List lists = new LinkedList();
   //同步容器
   volatile List lists = Collections.synchronizedList(new LinkedList<>());

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }

   public static void main(String[] args) {

      T02_WithVolatile c = new T02_WithVolatile();
      new Thread(() -> {
         for(int i=0; i<10; i++) {
            c.add(new Object());
            System.out.println("add " + i);
  
            try {
               TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }, "t1").start();
      
      new Thread(() -> {
         while(true) {
            if(c.size() == 5) {
               break;
            }
         }
         System.out.println("t2 结束");
      }, "t2").start();
   }
}
  • 输出结果

add 0
add 1
add 2
add 3
add 4
t2 结束
add 5
add 6
add 7
add 8
add 9

方法二 wait()和notify()

反例

/**
 * 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁
 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class T03_NotifyHoldingLock { //wait notify

   //添加volatile,使t2能够得到通知
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }
   
   public static void main(String[] args) {
      T03_NotifyHoldingLock c = new T03_NotifyHoldingLock();
      
      final Object lock = new Object();
      
      new Thread(() -> {
         synchronized(lock) {
            System.out.println("t2启动");
            if(c.size() != 5) {
               try {
                  lock.wait();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
            System.out.println("t2 结束");
         }
         
      }, "t2").start();
      
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      new Thread(() -> {
         System.out.println("t1启动");
         synchronized(lock) {
            for(int i=0; i<10; i++) {
               c.add(new Object());
               System.out.println("add " + i);
               
               if(c.size() == 5) {
                  //notify()并不释放锁, 所以t1会等t2执行完之后才会执行
                  lock.notify();
               }
               
               try {
                  TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }, "t1").start();
       
   }
}

优化方法

/**
 * notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行
 * 整个通信过程比较繁琐
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


public class T04_NotifyFreeLock {

   //添加volatile,使t2能够得到通知
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }
   
   public static void main(String[] args) {
      T04_NotifyFreeLock c = new T04_NotifyFreeLock();
      
      final Object lock = new Object();
      
      new Thread(() -> {
         synchronized(lock) {
            System.out.println("t2启动");
            if(c.size() != 5) {
               try {
                  lock.wait();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
            System.out.println("t2 结束");
            //通知t1继续执行
            lock.notify();
         }
         
      }, "t2").start();
      
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      Thread t = new Thread(() -> {
         System.out.println("测试");
      });

      new Thread(() -> {
         System.out.println("t1启动");
         synchronized(lock) {
            for(int i=0; i<10; i++) {
               c.add(new Object());
               System.out.println("add " + i);
               
               if(c.size() == 5) {
                  lock.notify();
                  //释放锁,让t2得以执行
                  try {
                     lock.wait();
                  } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
               }
               
               try {
                  TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         }
      }, "t1").start();
      
      
   }
}

方法三 CountDownLatch()

一个countDownLatch

/**
 * @author cyc
 */
public class T05_CountDownLatch {

   // 添加volatile,使t2能够得到通知
   volatile List lists = new ArrayList();

   public void add(Object o) {
      lists.add(o);
   }

   public int size() {
      return lists.size();
   }

   public static void main(String[] args) {
      T05_CountDownLatch c = new T05_CountDownLatch();

      CountDownLatch latch = new CountDownLatch(1);

      new Thread(() -> {
         System.out.println("t2启动");
         if (c.size() != 5) {
            try {
               latch.await();
               
               //也可以指定等待时间
               //latch.await(5000, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
         System.out.println("t2 结束");

      }, "t2").start();

      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e1) {
         e1.printStackTrace();
      }

      new Thread(() -> {
         System.out.println("t1启动");
         for (int i = 0; i < 10; i++) {
            c.add(new Object());
            System.out.println("add " + i);

            if (c.size() == 5) {
               // 打开门闩,让t2得以执行
               latch.countDown();
            }
         }

      }, "t1").start();

   }
}

执行结果

t2启动
t1启动
add 0
add 1
add 2
add 3
add 4
add 5
add 6
t2 结束
add 7
add 8
add 9

可以看到, 正常来说,应该在打印完add 4之后打印 t2结束,但是由于t1的latch.await();执行之后,打印t2 结束执行结束之前, t1的线程又执行了两遍,导致“t2技术”在i=6时才输出。

优化CountDownLatch

使用两个CountDownLatch

/**
 * countDownLatch优化
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class T05_CountDownLatch_optimize {

	// 添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	public static void main(String[] args) {
		T05_CountDownLatch_optimize c = new T05_CountDownLatch_optimize();

		CountDownLatch latch1 = new CountDownLatch(1);
		CountDownLatch latch2 = new CountDownLatch(1);

		new Thread(() -> {
			System.out.println("t2启动");
			if (c.size() != 5) {
				try {
					latch1.await();
					//也可以指定等待时间
					//latch1.await(5000, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("t2 结束");
            //在t2执行结束后, 再放开门栓
			latch2.countDown();

		}, "t2").start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1启动");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					// 打开门闩,让t2得以执行
					latch1.countDown();
					try {
                        //latch1.countDown();执行后, 继续执行latch2.await();等待latch2归零
						latch2.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

				/*try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
			}

		}, "t1").start();

	}
}

方法四 LockSupport

使用lockSupport和上面的CountDownLatch会有同样的问题, 下面开始演示

普通方案

/**
 * @author cyc
 */
public class T06_LockSupport {

	// 添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	public static void main(String[] args) {
		T06_LockSupport c = new T06_LockSupport();

		CountDownLatch latch = new CountDownLatch(1);

		Thread t2 = new Thread(() -> {
			System.out.println("t2启动");
			if (c.size() != 5) {

				LockSupport.park();

			}
			System.out.println("t2 结束");


		}, "t2");

		t2.start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}

		new Thread(() -> {
			System.out.println("t1启动");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					LockSupport.unpark(t2);
				}
			}

		}, "t1").start();

	}
}

同样会出现, t2结束的输出语句不一定在t1输出 add 4之后, 可能是在 add5, add6,…之后

优化方案 LockSupport

/**
 * @author cyc
 */
package com.cyc.juc.c_020_01_Interview;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

public class T07_LockSupport_WithoutSleep {

	// 添加volatile,使t2能够得到通知
	volatile List lists = new ArrayList();

	public void add(Object o) {
		lists.add(o);
	}

	public int size() {
		return lists.size();
	}

	static Thread t1 = null, t2 = null;

	public static void main(String[] args) {
		T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();

		t1 = new Thread(() -> {
			System.out.println("t1启动");
			for (int i = 0; i < 10; i++) {
				c.add(new Object());
				System.out.println("add " + i);

				if (c.size() == 5) {
					LockSupport.unpark(t2);
					LockSupport.park();
				}
			}
		}, "t1");

		t2 = new Thread(() -> {
			System.out.println("t2启动");
			LockSupport.park();
			System.out.println("t2 结束");
			LockSupport.unpark(t1);


		}, "t2");

		t2.start();
		t1.start();

	}
}

方法五 Semaphore

public class T08_Semaphore {
    // 添加volatile,使t2能够得到通知
    volatile List lists = new ArrayList();

    public void add(Object o) {
        lists.add(o);
    }

    public int size() {
        return lists.size();
    }

    static Thread t1 = null, t2 = null;

    public static void main(String[] args) {
        T08_Semaphore c = new T08_Semaphore();
        Semaphore s = new Semaphore(1);

        t1 = new Thread(() -> {
            try {
                s.acquire();
                for (int i = 0; i < 5; i++) {
                    c.add(new Object());
                    System.out.println("add " + i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                t2.start();
                //等t2执行完了之后, t1再继续执行
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                s.acquire();
                for (int i = 5; i < 10; i++) {
                    System.out.println("add " + i);
                }
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t1");

        t2 = new Thread(() -> {
            try {
                s.acquire();
                System.out.println("t2 结束");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        t1.start();
    }
}

容器- 生产者消费者

* 面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
* 能够支持2个生产者线程以及10个消费者线程的阻塞调用

1. 使用wait和notify/notifyAll来实现

/**
 * 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 * 
 * 使用wait和notify/notifyAll来实现
 * 
 * @author cyc
 */
public class MyContainer1<T> {
   final private LinkedList<T> lists = new LinkedList<>();
   final private int MAX = 10; //最多10个元素
   private int count = 0;


   /**
    * 生产
    * @param t
    */
   public synchronized void put(T t) {
      while(lists.size() == MAX) { //想想为什么用while而不是用if?,如果这里使用if,
         // 执行完this.wait()后, 就不会继续判断lists.size() == MAX,而是往下执行了。
         try {
            this.wait(); //effective java
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }

      lists.add(t);
      ++count;
      this.notifyAll(); //通知消费者线程进行消费
   }

   /**
    * 消费
    * @return
    */
   public synchronized T get() {
      T t = null;
      while(lists.size() == 0) {
         try {
            this.wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      t = lists.removeFirst();
      count --;
      this.notifyAll(); //通知生产者进行生产
      return t;
   }

   public static void main(String[] args) {
      MyContainer1<String> c = new MyContainer1<>();
      //启动消费者线程
      for(int i=0; i<10; i++) {
         new Thread(()->{
            for(int j=0; j<5; j++) {
               System.out.println(c.get()+", count: "+ c.count);
            }
         }, "c" + i).start();
      }

      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }

      //启动生产者线程
      for(int i=0; i<2; i++) {
         new Thread(()->{
            for(int j=0; j<25; j++) {
               c.put(Thread.currentThread().getName() + " " + j);
            }
         }, "p" + i).start();
      }
   }
}

输出结果

p1 0, count: 9
p1 5, count: 4
p1 6, count: 3
p1 7, count: 2
p1 8, count: 1
p1 4, count: 10
p1 9, count: 9
p0 0, count: 8
p0 1, count: 7

2. 使用lock.newCondition()

上面的代码有个问题, 那就是使用notifyAll时, 会通知所有线程, 而不是仅仅通知消费者线程, 接下来使用lock.newCondition()来进行优化,newCondition()即新建一个条件, 这个条件便是等待队列, 每次的通知也只是通知对应等待队列的线程

/**
 * 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
 * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
 * 使用Lock和Condition来实现
 * 对比两种方式,Condition的方式可以更加精确的指定哪些线程被唤醒
 * 
 * @author cyc
 */
package com.cyc.juc.c_021_01_interview;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyContainer2<T> {
	final private LinkedList<T> lists = new LinkedList<>();
	final private int MAX = 10; //最多10个元素
	private int count = 0;
	
	private Lock lock = new ReentrantLock();
	//Condition本质上属于等待队列, new Condition()相当于新建了一个等待队列
	private Condition producer = lock.newCondition();
	private Condition consumer = lock.newCondition();
	
	public void put(T t) {
		try {
			lock.lock();
			while(lists.size() == MAX) { //想想为什么用while而不是用if?
				producer.await();
			}
			
			lists.add(t);
			++count;
			consumer.signalAll(); //通知消费者线程进行消费
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public T get() {
		T t = null;
		try {
			lock.lock();
			while(lists.size() == 0) {
				consumer.await();
			}
			t = lists.removeFirst();
			count --;
			producer.signalAll(); //通知生产者进行生产
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return t;
	}
	
	public static void main(String[] args) {
		MyContainer2<String> c = new MyContainer2<>();
		//启动消费者线程
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) System.out.println(c.get());
			}, "c" + i).start();
		}
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//启动生产者线程
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}

总结

AQS原理

Abstract : 因为它并不知道到怎么上锁。参照模板方法设计模式即可,暴露出上锁逻辑

Queue:线程阻塞队列

Synchronizer: 同步

AQS便是使用CAS+state完成多线程抢锁逻辑,以及使用Queue完成抢不到锁的线程排队

AQS核心代码

获取锁的代码

	public final void acquire(int arg) {
    if (!tryAcquire(arg) && //子类判定获取锁失败返回false, 那么同理,取反,则表示为true
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //获取失败后添加到阻塞队列中
        selfInterrupt();
	}

	//模板方法, 需要子类去实现,子类去实现获取锁的逻辑, AQS并不知道你怎么使用这个statue来上锁
	//因为是子类需要去实现的, 所以这里使用protected修饰
	protected boolean tryAcquire(int arg) {
  	 throw new UnsupportedOperationException();
	}

释放锁的代码

    public final boolean release(int arg) {
        //子类判断释放锁成功后
        if (tryRelease(arg)) {
            //检查阻塞队列唤醒即可
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

	//模板方法, 需要子类去实现,子类去实现获取锁的逻辑, AQS并不知道你怎么使用这个statue来释放锁	
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

**总结: **

子类只需要实现自己的获取锁逻辑和释放锁逻辑即可, 至于排队阻塞队列等待、唤醒机制均由AQS来完成

ReentranLock原理

概念

基于AQS实现的可重入锁实现类

核心变量和构造器

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;

    
    abstract static class Sync extends AbstractQueuedSynchronizer {

        abstract void lock();

        //非公平锁获取锁方法
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //当执行到这里, 正好持有锁的线程释放了锁,那么可以尝试抢锁
            if (c == 0) {
                //继续抢锁, 不看有没有线程排队
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 当前线程就是持有锁的线程, 表明锁重入
            else if (current == getExclusiveOwnerThread()) {
                // 利用state整型变量进行次数记录
                int nextc = c + acquires;
                //如果超过了int表示范围, 表明符号溢出, 所以抛出异常(int范围 正:2的32次方-1,负:2的32次方)
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                  (nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        final boolean isLocked() {
            return getState() != 0;
        }
    }
    
    //公平锁获取锁的方法
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
		// 由ReentrantLock调用
        final void lock() {
            // 没有尝试抢锁, 直接进入AQS标准获取锁流程
            acquire(1);
        }
		// AQS调用, 自己自己实现获取锁的流程
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //注意: 这里和非公平锁的区别在于: hasQueuedPredecessors看看队列中是否有线程正在排队, 没有的话再通过CAS抢锁。
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 当前线程就是获取锁的线程, 那么这里是锁重入
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    

    public ReentrantLock() {
        sync = new NonfairSync();
    }


    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    
    static final class NonfairSync extends Sync {
       //由ReentrantLock调用获取锁
        final void lock() {
            //非公平锁, 直接抢锁, 不管有没有线程排队
            if (compareAndSetState(0, 1))
                //上锁成功, 那么标识当前线程为获取锁的线程 
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //抢锁失败, 进入AQS的标准获取锁流程
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
}

这里首先理解一下公平锁和非公平锁

公平锁与非公平锁

多线程---详解各种锁和AQS原理_第20张图片

为什么非公平锁的性能高于公平锁

因为公平锁需要排队, 排队需要时间, 唤醒阻塞的线程也需要时间, 就如上图公平锁图中的B加入到运行队列,期间进行线程切换需要时间。

非公平锁性能高于公平锁的原因:线程上下文切换+调度延迟时间

核心方法

获取锁操作

public void lock() {
    //直接通过sync同步器进行上锁
    sync.lock();
}

而在ReentrantLock中sync有公平锁和非公平锁, 如下所示

多线程---详解各种锁和AQS原理_第21张图片

如何自己来实现一把锁?

  1. 如何表示锁状态:无锁?有锁?

    boolean status;//true 有锁 false 无锁 只能表示两种状态

    为了实现锁重入, 那么我需要记录锁重入次数: int times;

    两个变量有点冗余了, 所以我们直接用int state; 来表示锁状态和重入次数: 0无锁, 大于0重入次数 特别地: 1为重入一次, 也即只加锁一次

  2. 如何保证多线程抢锁线程安全

    CAS

  3. 如何处理获取不到锁的线程?

    自旋 阻塞 自旋+阻塞

  4. 如何释放锁?

    自旋: 自己抢锁

    阻塞: 唤醒

自旋锁

自旋锁缺点:CPU占用不干事,导致性能障碍,简称占着茅坑不拉屎。

自旋锁优点:适用于执行步骤较少且快的操作,自旋一会马上就能获取锁,这样不会消耗太多CPU资源。

注意:当CPU个数增加的情况下,优点会退化成自旋锁的缺点

使用场景:争用较少且代码量小的 临界区

你可能感兴趣的:(多线程与高并发,多线程,并发编程)