1>.
可重入锁(递归锁)
①. 指的是同一线程外层函数获得锁后,再进入该线程的内层方法会自动获取锁 (前提,锁对象是同一个对象
)
类似于家里面的大门,进入之后可以进入厕所、厨房等
②. Java中ReentranLock(显示锁)和synchronized(隐式锁)都是可重入锁,可重入锁的一个优点是可在一定程度避免死锁
③. 隐式锁:
(即synchronized关键字使用的锁)默认是可重入锁(同步块、同步方法)
原理如下:掌握
//1.同步块
public class SychronizedDemo {
Object object=new Object();
public void sychronizedMethod(){
new Thread(()->{
synchronized (object){
System.out.println(Thread.currentThread().getName()+"\t"+"外层....");
synchronized (object){
System.out.println(Thread.currentThread().getName()+"\t"+"中层....");
synchronized (object){
System.out.println(Thread.currentThread().getName()+"\t"+"内层....");
}
}
}
},"A").start();
}
public static void main(String[] args) {
new SychronizedDemo().sychronizedMethod();
/*
输出结果:
A 外层....
A 中层....
A 内层....
* */
}
}
//2.同步代码块
class Phone{
public synchronized void sendSms() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendSms");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendEmail");
}
}
/**
* Description:
* 可重入锁(也叫做递归锁)
* 指的是同一线程外层函数获得锁后,内层递归函数任然能获取该锁的代码
* 在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
* 也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
* **/
public class ReenterLockDemo {
/**
* t1 sendSms
* t1 sendEmail
* t2 sendSms
* t2 sendEmail
* @param args
*/
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
显示锁:
(即lock)也有ReentrantLock这样的可重入锁class Phone2{
static ReentrantLock reentrantLock=new ReentrantLock();
public static void sendSms(){
reentrantLock.lock();
/*
//reentrantLock.lock();
注意有多少个lock,就有多少个unlock,他们是配对使用的
如果多了一个lock(),那么会出现线程B一直处于等待状态
* */
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"sendSms");
sendEmails();
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
private static void sendEmails() {
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"sendEmails...");
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone2 phone2=new Phone2();
new Thread(()->{
phone2.sendSms();},"A").start();
new Thread(()->{
phone2.sendSms();},"B").start();
}
}
2>.为什么要使用LockSupport(先来了解下传统的等待唤醒机制)
public class SynchronizedDemo {
//等待线程
public void waitThread(){
// 1.如果将synchronized (this){}注释,会抛出异常,因为wait和notify一定要在同步块或同步方法中
synchronized (this){
try {
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t"+"end....");
}
}
//唤醒线程
public void notifyThread(){
synchronized (this){
System.out.println("唤醒A线程....");
notify();
}
}
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
new Thread(()->{
// 2.如果把下行这句代码打开,先notify后wait,会出现A线程一直处于等待状态
// try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
synchronizedDemo.waitThread();
},"A").start();
new Thread(()->{
synchronizedDemo.notifyThread();
},"B").start();
}
}
public class LockDemo {
static Object object=new Object();
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
//如果把下行这句代码打开,先signal后await,会出现A线程一直处于等待状态
//try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
condition.await();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName()+"\t"+"END....");
},"A").start();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"唤醒A线程****");
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
},"B").start();
}
}
3>.
JUC强大的三个工具类 掌握
为什么这里要介绍下JUC强大的工具类?
CountDownLatch | CyclicBarrier | Semaphore 底层都是AQS来实现的
①. CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
②. 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
③. 计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
//需求:要求6个线程都执行完了,mian线程最后执行
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t");
countDownLatch.countDown();
},i+"").start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t班长关门走人,main线程是班长");
}
}
public enum CountryEnum {
one(1,"齐"),two(2,"楚"),three(3,"燕"),
four(4,"赵"),five(5,"魏"),six(6,"韩");
private Integer retCode;
private String retMessage;
private CountryEnum(Integer retCode,String retMessage){
this.retCode=retCode;
this.retMessage=retMessage;
}
public static CountryEnum getCountryEnum(Integer index){
CountryEnum[] countryEnums = CountryEnum.values();
for (CountryEnum countryEnum : countryEnums) {
if(countryEnum.getRetCode()==index){
return countryEnum;
}
}
return null;
}
public Integer getRetCode() {
return retCode;
}
public String getRetMessage() {
return retMessage;
}
}
/*
楚 **国,被灭
魏 **国,被灭
赵 **国,被灭
燕 **国,被灭
齐 **国,被灭
韩 **国,被灭
main **秦国一统江湖
* */
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception{
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"**国,被灭");
countDownLatch.countDown();
},CountryEnum.getCountryEnum(i).getRetMessage()).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t"+"**秦国一统江湖");
}
}
2>.
CyclicBarrier
①. CyclicBarrier的字面意思是可循环(Cyclic) 使用的屏障(barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法
②. 代码验证:
//集齐7颗龙珠就能召唤神龙
public class CyclicBarrierDemo {
public static void main(String[] args) {
// public CyclicBarrier(int parties, Runnable barrierAction) {}
CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("召唤龙珠");
});
for (int i = 1; i <=7; i++) {
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t收集到了第"+temp+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
3>.
Semaphore(信号量)
①. acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
②. release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
③. 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
④. 代码验证
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"\t抢占了车位");
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
4>.
LockSupport详解
/*
(1).阻塞
(permit默认是O,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,
park方法会被唤醒,然后会将permit再次设置为O并返回)
static void park()
static void park(Object blocker)
(2).唤醒
static void unpark(Thread thread)
(调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,
permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回)
static void unpark(Thread thread)
* */
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"coming....");
LockSupport.park();
/*
如果这里有两个LockSupport.park(),因为permit的值为1,上一行已经使用了permit
所以下一行被注释的打开会导致程序处于一直等待的状态
* */
//LockSupport.park();
System.out.println(Thread.currentThread().getName()+"\t"+"被B唤醒了");
},"A");
t1.start();
//下面代码注释是为了A线程先执行
//try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
Thread t2=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t"+"唤醒A线程");
//有两个LockSupport.unpark(t1),由于permit的值最大为1,所以只能给park一个通行证
LockSupport.unpark(t1);
//LockSupport.unpark(t1);
},"B");
t2.start();
}
}
③. 锁,面向锁的使用者(定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可)
同步器,面向锁的实现者(比如Java并发大神Douglee,提出统一规 范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。)
④. 如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果
写在最前面:
(1). 本次讲解我们走最常用的,lock/unlock作为案例突破口
(2). 我相信你应该看过源码了,那么AQS里面有个变量叫State,它的值有几种?3个状态:没占用是0,占用了是1,大于1是可重入锁
(3). 如果AB两个线程进来了以后,请问这个总共有多少个Node节点?答案是3个,其中队列的第一个是傀儡节点(哨兵节点)
业务图:
public class AQSDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
//带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制
//3个线程模拟3个来银行网点,受理窗口办理业务的顾客
//A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
new Thread(() -> {
lock.lock();
try{
System.out.println("-----A thread come in");
try {
TimeUnit.MINUTES.sleep(20); }catch (Exception e) {
e.printStackTrace();}
}finally {
lock.unlock();
}
},"A").start();
//第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待,
//进入候客区
new Thread(() -> {
lock.lock();
try{
System.out.println("-----B thread come in");
}finally {
lock.unlock();
}
},"B").start();
//第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待,
//进入候客区
new Thread(() -> {
lock.lock();
try{
System.out.println("-----C thread come in");
}finally {
lock.unlock();
}
},"C").start();
}
}
假如3号ThreadC线程进来
(1). prev
(2).compareAndSetTail
(3).next
①. acquireQueued
(会调用如下方法:shouldParkAterFailedAcquire和parkAndCheckInterrupt | setHead(node) )
②. shouldParkAfterFailedAcquire
③. parkAndCheckInterrupt
④. 当我们执行下图中的③表示线程B或者C已经获取了permit了
①. release | tryRelease | unparkSuccessor(h);
②. tryRelease()