多线程-并发编程

目录

有关模式及应用:https://blog.csdn.net/QGhurt/article/details/107604105

线程基础知识

进程和线程的区别

上下文切换

并行与并发

同步与异步

 线程的创建和运行

start和run方法

 sleep和yield方法

join方法

interrupt方法

主线程和守护线程

线程状态

线程状态转换

共享模型

Synchronized

synchronzied用法: 

synchronized案例分析:

 变量的线程安全分析

synchronzied原理

对象头

Monitor 

synchronized字节码分析

轻量级锁

锁膨胀

自旋

偏向锁

偏向锁失效

偏向锁批量重偏向

偏向锁批量撤销

锁消除

wait和notify

死锁

ReentrantLock

volatile

volatile原理

应用:单例模式双端检锁机制

无锁-乐观锁(非阻塞)

CAS

原子类

​CAS的ABA问题​

不可变类的使用

线程池

常见四个线程池 

提交任务 

线程池的异常处理

fork/join线程池

AQS

ReentrantLock原理

非公平锁实现原理

加锁源码

解锁源码

可重入原理

可打断原理

不可打断模式

可打断模式

公平锁实现原理

条件变量实现原理

条件变量原理源码

ReentrantReadWriteLock(读写锁)

StampedLock

Semaphore

CountdownLatch 

​ CyclicBarrier

线程安全集合类

ConcurrentHashMap原理


线程基础知识

进程和线程的区别

进程是一个独立的运行环境,而线程是在进程中执行的一个任务。他们两个本质的区别是是否单独占有内存地址空间及其它系统资源(比如I/O)

  • 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。

  • 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。

  • 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。

另外一个重要区别是,进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位 。

进程就是一段程序的执行过程。进程是一个实体。每一个进程都有它自己的地址空间. 由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位

一个程序至少有一个进程(QQ音乐只能打开一个,浏览器可以打开多个)


进程

一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

线程

进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

进程与线程的区别

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

 

多线程-并发编程_第1张图片

多线程-并发编程_第2张图片

多线程-并发编程_第3张图片

多线程-并发编程_第4张图片

 

上下文切换

上下文切换(有时也称做进程切换或任务切换)是指 CPU 从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时间点 CPU 寄存器和程序计数器的内容。

 

CPU通过为每个线程分配CPU时间片来实现多线程机制。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。

但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

 

多线程-并发编程_第5张图片

并行与并发

 

同步与异步

多线程-并发编程_第6张图片

 线程的创建和运行

方法一:直接只用Thread

多线程-并发编程_第7张图片

多线程-并发编程_第8张图片

多线程-并发编程_第9张图片

多线程-并发编程_第10张图片

多线程-并发编程_第11张图片

多线程-并发编程_第12张图片

start和run方法

多线程-并发编程_第13张图片

 sleep和yield方法

多线程-并发编程_第14张图片

 注意:sleep方法只会释放CPU资源,不会释放当前对象的锁。多线程-并发编程_第15张图片

join方法

同步等待,还可以带时长,join(1000)

多线程-并发编程_第16张图片多线程-并发编程_第17张图片

join()原理

是调用者轮询检查线程 alive 状态
多线程-并发编程_第18张图片

1.sleep()方法
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。
sleep()使当前线程进入阻塞状态,在指定时间内不会执行。


2.wait()方法
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。


3.yield方法
暂停当前正在执行的线程对象。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。 
 

4.join方法

join()等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测

interrupt方法

多线程-并发编程_第19张图片

  • interrupt() // 通知目标线程中断,也就是设置中断标志位
  • isInterrupted() // 通过检查编中断标志位判断线程是否被中断
  • interrupted() // 静态方法,判断是否被中断并清楚当前中断状态

InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。

对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

public class InterruptExample {

    private static class MyThread1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("Thread run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new MyThread1();
    thread1.start();
    thread1.interrupt();
    System.out.println("Main run");
}

interrupted()

如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。

但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

public class InterruptExample {

    private static class MyThread2 extends Thread {
        @Override
        public void run() {
            while (!interrupted()) {
                // ...并不会真的被打断,因为当前线程没有sleep()方法
            }
            System.out.println("Thread end");
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread2 = new MyThread2();
    thread2.start();
    thread2.interrupt();
}

 

wait()和notify()方法 

等待(wait)和通知(notify)

这两个方法是Object类中的,任何对象都可以调用这两个方法。

wait()方法只能在synchronized方法或synchronized块中使用(原因:wait方法会释放锁,只有在syn中才有锁)

notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会

notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。

锁池EntiyList:当一个线程需要调用调用此方法时必须获得该对象的锁,而该对象的锁被其他线程占用,该线程就需要在一个地方等待锁释放,这个地方就是锁池。(排队等待,准备抢锁的池子) 

等待池WaitSet:调用了wait方法的线程会释放锁并进入等待池,在等待池的线程不会竞争锁。(休息的池子

值得注意的是无论是wait方法还是notify方法都需要首先获得目标对象的一个监视锁(monitor lock),得到锁后才可以执行方法。

wait 和 sleep 的区别:

Thread.sleep只会让出CPU ,不会释放任何资源; Object.wait不仅让出CPU , 还会释放已经占有的同步资源锁。

 

线程阻塞工具类(LockSupport)

LockSupport 可以在线程内任意位置让线程阻塞,与 suspend 方法相比,弥补了由于 resume 方法发生导致线程无法继续执行的情况,和 wait 方法相比,他不需要获得锁也不会抛出中断异常。

LockSupport 包含静态方法 park 可以阻塞当前线程,unpark 方法可以解开。

LockSupport 使用类似信号量的机制,为每个线程准备了一个许可,如果这个许可可用,park 方法会返回,并把这个许可变为不可用,如果许可不可用,就阻塞线程。unpark 则相反。这个机制使得即使 unpark 方法操作发生在 park 之前,也可以使下一次的 park方法操作立即返回。

 

主线程和守护线程

多线程-并发编程_第20张图片 多线程-并发编程_第21张图片

线程状态

从操作系统层面分为五层:

多线程-并发编程_第22张图片 多线程-并发编程_第23张图片

2.从Java API 层面分为六层:

多线程-并发编程_第24张图片

多线程-并发编程_第25张图片

线程状态转换

多线程-并发编程_第26张图片

共享模型

管程-悲观锁(阻塞)

多线程-并发编程_第27张图片

 

多线程-并发编程_第28张图片

Synchronized

多线程-并发编程_第29张图片

多线程-并发编程_第30张图片

多线程-并发编程_第31张图片

多线程-并发编程_第32张图片

多线程-并发编程_第33张图片

多线程-并发编程_第34张图片

多线程-并发编程_第35张图片

多线程-并发编程_第36张图片

synchronzied用法: 

多线程-并发编程_第37张图片

多线程-并发编程_第38张图片

synchronized案例分析:(经典的线程八锁问题)

多线程-并发编程_第39张图片

多线程-并发编程_第40张图片

多线程-并发编程_第41张图片

多线程-并发编程_第42张图片

多线程-并发编程_第43张图片

多线程-并发编程_第44张图片

多线程-并发编程_第45张图片

多线程-并发编程_第46张图片

多线程-并发编程_第47张图片

 变量的线程安全分析

多线程-并发编程_第48张图片

多线程-并发编程_第49张图片

多线程-并发编程_第50张图片

 

多线程-并发编程_第51张图片

多线程-并发编程_第52张图片

多线程-并发编程_第53张图片

多线程-并发编程_第54张图片

多线程-并发编程_第55张图片

多线程-并发编程_第56张图片

多线程-并发编程_第57张图片

多线程-并发编程_第58张图片

 

 

我们知道HashTable是线程安全的,单个方法是线程安全的,但是多个方法结合在一起使用就未必是线程安全的了.

解决:需要在调用这两个方法的外层的方法加上Sychronized才能保证线程安全.

多线程-并发编程_第59张图片

多线程-并发编程_第60张图片

分析:线程1在执行玩第一句就被切换到线程2执行了,线程2执行完回来继续执行线程1剩下的语句,Thread1以为key还是==null,这个时候线程2 所做的修改就会被覆盖,导致修改丢失.

 

synchronzied原理

对象头

多线程-并发编程_第61张图片

Monitor 

多线程-并发编程_第62张图片

waitset(休息室):当前获得锁的线程(Owner)是屋子的主人,但是该线程可能因为条件不足了(可能等待I/O获取数据)无法继续往下运行了, 这个歌时候就会调用wait(),进入waitset等待条件好了才能继续运行;这个歌时候它就会让出锁,给entrylist排队等待的线程来竞争锁,谁抢到就是谁的;当条件满足了,当前获得锁的线程就会调用notify()唤醒waitset休息室的线程,它就会去重新跟别人一起排队,去竞争锁.

synchronized字节码分析

多线程-并发编程_第63张图片

多线程-并发编程_第64张图片

轻量级锁

多线程-并发编程_第65张图片

1.栈帧的锁记录使用CAS尝试替换对象头的MarkWord

2.当前线程内继续加锁,(锁重入)计数器+1(相应地,解锁-1)

3.别的线程来加锁,发现已经存在轻量级锁了,就会转成重量级锁(锁膨胀)

多线程-并发编程_第66张图片

锁膨胀

多线程-并发编程_第67张图片

多线程-并发编程_第68张图片

多线程-并发编程_第69张图片

自旋

 

偏向锁

多线程-并发编程_第70张图片

多线程-并发编程_第71张图片

多线程-并发编程_第72张图片

多线程-并发编程_第73张图片

多线程-并发编程_第74张图片

偏向锁失效

1.

多线程-并发编程_第75张图片

多线程-并发编程_第76张图片

2.

3.

偏向锁批量重偏向

多线程-并发编程_第77张图片

刚开始偏向锁是加在thread1上的,这个时候30个线程thread2来了,20次以后 ,偏向锁就重定向指向thread2了

偏向锁批量撤销

多线程-并发编程_第78张图片

锁消除

wait和notify

多线程-并发编程_第79张图片

多线程-并发编程_第80张图片

多线程-并发编程_第81张图片

多线程-并发编程_第82张图片

多线程-并发编程_第83张图片

多线程-并发编程_第84张图片

多线程-并发编程_第85张图片

多线程-并发编程_第86张图片

多线程-并发编程_第87张图片

 

多线程-并发编程_第88张图片

死锁

多线程-并发编程_第89张图片

多线程-并发编程_第90张图片

多线程-并发编程_第91张图片

ReentrantLock

 

synchronized 和 Lock 有什么区别?

  • 首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
  • synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
  • synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

synchronized 和 ReentrantLock 区别是什么?

synchronized 是和 if、else、for、while 一样的关键字ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

相同点:两者都是可重入锁

两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

主要区别如下:

  • ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
  • ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
  • ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、代码块等。
  • 二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  • 普通同步方法,锁是当前实例对象
  • 静态同步方法,锁是当前类的class对象
  • 同步方法块,锁是括号里面的对象

什么是可重入锁(ReentrantLock)?

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。

在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁。

重入性的实现原理

要想支持重入性,就要解决两个问题:

1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;

2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功

ReentrantLock支持两种锁:公平锁非公平锁何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO

多线程-并发编程_第92张图片

多线程-并发编程_第93张图片

多线程-并发编程_第94张图片

多线程-并发编程_第95张图片

多线程-并发编程_第96张图片

多线程-并发编程_第97张图片

多线程-并发编程_第98张图片

使用tryLock()解决哲学家就餐问题

多线程-并发编程_第99张图片

多线程-并发编程_第100张图片

多线程-并发编程_第101张图片

synchronized,ReentrantLock都是非公平锁,可以抢锁,进行竞争

多线程-并发编程_第102张图片

也可以设置为公平(true)的,但是这样会降低并发度。

多线程-并发编程_第103张图片

多线程-并发编程_第104张图片

多线程-并发编程_第105张图片

volatile

Java关键字-volatile

指令重排

多线程-并发编程_第106张图片

多线程-并发编程_第107张图片

volatile原理

多线程-并发编程_第108张图片

多线程-并发编程_第109张图片

多线程-并发编程_第110张图片

多线程-并发编程_第111张图片

多线程-并发编程_第112张图片

多线程-并发编程_第113张图片

多线程-并发编程_第114张图片

多线程-并发编程_第115张图片

应用:单例模式双端检锁机制

多线程-并发编程_第116张图片

多线程-并发编程_第117张图片

多线程-并发编程_第118张图片

多线程-并发编程_第119张图片

多线程-并发编程_第120张图片

happens-before

多线程-并发编程_第121张图片

多线程-并发编程_第122张图片

多线程-并发编程_第123张图片

多线程-并发编程_第124张图片

多线程-并发编程_第125张图片

多线程-并发编程_第126张图片

多线程-并发编程_第127张图片

无锁-乐观锁(非阻塞)

CAS

CAS(compare and swap)

看一个转账的例子

多线程-并发编程_第128张图片

多线程-并发编程_第129张图片

分析:

多线程-并发编程_第130张图片

多线程-并发编程_第131张图片

多线程-并发编程_第132张图片

多线程-并发编程_第133张图片

多线程-并发编程_第134张图片

多线程-并发编程_第135张图片

原子类

多线程-并发编程_第136张图片

 

多线程-并发编程_第137张图片

多线程-并发编程_第138张图片

多线程-并发编程_第139张图片

取款案例:

多线程-并发编程_第140张图片
CAS的ABA问题
多线程-并发编程_第141张图片

多线程-并发编程_第142张图片

解决:使用AtomicStampedReference

多线程-并发编程_第143张图片

多线程-并发编程_第144张图片

多线程-并发编程_第145张图片

另一种解决方法:使用AtomicMarkableRenference

多线程-并发编程_第146张图片

例子:

多线程-并发编程_第147张图片

多线程-并发编程_第148张图片

多线程-并发编程_第149张图片

多线程-并发编程_第150张图片

多线程-并发编程_第151张图片

多线程-并发编程_第152张图片

案例:

多线程-并发编程_第153张图片

多线程-并发编程_第154张图片

多线程-并发编程_第155张图片

多线程-并发编程_第156张图片

多线程-并发编程_第157张图片

多线程-并发编程_第158张图片

多线程-并发编程_第159张图片

多线程-并发编程_第160张图片

LongAdder>AtomicLong ,累加,性能比AtomicLong.getAndIncrement()好

多线程-并发编程_第161张图片

多线程-并发编程_第162张图片

多线程-并发编程_第163张图片

不可变类的使用

日期转换:SimpleDateFormat不是线程安全的,

多线程-并发编程_第164张图片

多线程-并发编程_第165张图片

String类的不可变

多线程-并发编程_第166张图片

多线程-并发编程_第167张图片

多线程-并发编程_第168张图片

多线程-并发编程_第169张图片

线程池

参考文章——线程池

多线程-并发编程_第170张图片

多线程-并发编程_第171张图片

常见四个线程池 

多线程-并发编程_第172张图片

 

多线程-并发编程_第173张图片

 

多线程-并发编程_第174张图片

 

newScheduledThreadPool (任务调度线程池)

多线程-并发编程_第175张图片

几个方法的使用 

多线程-并发编程_第176张图片

多线程-并发编程_第177张图片

 多线程-并发编程_第178张图片

 

提交任务 

多线程-并发编程_第179张图片

多线程-并发编程_第180张图片

invokeAll()运行演示:

多线程-并发编程_第181张图片

多线程-并发编程_第182张图片

 

invokeAny()演示:

多线程-并发编程_第183张图片

多线程-并发编程_第184张图片

多线程-并发编程_第185张图片

多线程-并发编程_第186张图片

多线程-并发编程_第187张图片

线程池的异常处理

1.自己手动try...catch...处理

多线程-并发编程_第188张图片

多线程-并发编程_第189张图片

fork/join线程池

多线程-并发编程_第190张图片

多线程-并发编程_第191张图片

多线程-并发编程_第192张图片

多线程-并发编程_第193张图片

改进:可以从中间开始拆分(类似于二分法) 

多线程-并发编程_第194张图片

多线程-并发编程_第195张图片

多线程-并发编程_第196张图片

AQS

AQS 原理
1. 概述
全称是 AbstractQueuedSynchronizer ,是阻塞式锁和相关的同步器工具的框架
 
特点:
state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取
锁和释放锁
getState - 获取 state 状态
setState - 设置 state 状态
compareAndSetState - cas 机制设置 state 状态
独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
提供了基于 FIFO 的等待队列,类似于 Monitor EntryList
条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor WaitSet
 
多线程-并发编程_第197张图片

 

自定义同步器,很容易复用 AQS ,实现一个功能完备的自定义锁
package cn.itcast.n8;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestAqs")
public class TestAqs {
    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
                sleep(1);
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t2").start();
    }
}

// 自定义锁(不可重入锁)
class MyLock implements Lock {

    // 独占锁  同步器类
    class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if(compareAndSetState(0, 1)) {
                // 加上了锁,并设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override // 是否持有独占锁
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private MySync sync = new MySync();

    @Override // 加锁(不成功会进入等待队列)
    public void lock() {
        sync.acquire(1);
    }

    @Override // 加锁,可打断
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override // 尝试加锁(一次)
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override // 尝试加锁,带超时
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override // 解锁
    public void unlock() {
        sync.release(1);
    }

    @Override // 创建条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}
多线程-并发编程_第198张图片

 

多线程-并发编程_第199张图片

多线程-并发编程_第200张图片

多线程-并发编程_第201张图片

多线程-并发编程_第202张图片

多线程-并发编程_第203张图片

ReentrantLock原理

非公平锁实现原理

多线程-并发编程_第204张图片

多线程-并发编程_第205张图片

多线程-并发编程_第206张图片

多线程-并发编程_第207张图片

多线程-并发编程_第208张图片

多线程-并发编程_第209张图片

多线程-并发编程_第210张图片

多线程-并发编程_第211张图片

多线程-并发编程_第212张图片

多线程-并发编程_第213张图片

加锁源码

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
 private static final long serialVersionUID = 7316153563782823691L;
 
 // 加锁实现
final void lock() {
 // 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁
 if (compareAndSetState(0, 1))
 setExclusiveOwnerThread(Thread.currentThread());
 else
 // 如果尝试失败,进入 ㈠
 acquire(1);
 }
 
 // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
 public final void acquire(int arg) {
 // ㈡ tryAcquire 
 if (
 !tryAcquire(arg) &&
 // 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤
 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
 ) {
 selfInterrupt();
 }
 }
 
 // ㈡ 进入 ㈢
 protected final boolean tryAcquire(int acquires) {
 return nonfairTryAcquire(acquires);
 }
 
 // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
 final boolean nonfairTryAcquire(int acquires) {
 final Thread current = Thread.currentThread();
 int c = getState();
 // 如果还没有获得锁
 if (c == 0) {
 // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
 if (compareAndSetState(0, acquires)) {
 setExclusiveOwnerThread(current);
 return true;
 }
 }
 // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
 else if (current == getExclusiveOwnerThread()) {
 // state++
 int nextc = c + acquires;
 if (nextc < 0) // overflow
 throw new Error("Maximum lock count exceeded");
 setState(nextc);
 return true;
 }
 // 获取失败, 回到调用处
 return false;
 }
 
 // ㈣ AQS 继承过来的方法, 方便阅读, 放在此处
 private Node addWaiter(Node mode) {
// 将当前线程关联到一个 Node 对象上, 模式为独占模式
 Node node = new Node(Thread.currentThread(), mode);
 // 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部
 Node pred = tail;
 if (pred != null) {
 node.prev = pred;
 if (compareAndSetTail(pred, node)) {
 // 双向链表
 pred.next = node;
 return node;
 }
 }
 // 尝试将 Node 加入 AQS, 进入 ㈥
 enq(node);
 return node;
 }
 
 // ㈥ AQS 继承过来的方法, 方便阅读, 放在此处
 private Node enq(final Node node) {
 for (;;) {
 Node t = tail;
 if (t == null) {
 // 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0)
 if (compareAndSetHead(new Node())) {
 tail = head;
 }
 } else {
 // cas 尝试将 Node 对象加入 AQS 队列尾部
 node.prev = t;
 if (compareAndSetTail(t, node)) {
 t.next = node;
 return t;
 }
 }
 }
 }
 
 // ㈤ AQS 继承过来的方法, 方便阅读, 放在此处
 final boolean acquireQueued(final Node node, int arg) {
 boolean failed = true;
 try {
 boolean interrupted = false;
 for (;;) {
 final Node p = node.predecessor();
 // 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取
 if (p == head && tryAcquire(arg)) {
 // 获取成功, 设置自己(当前线程对应的 node)为 head
 setHead(node);
 // 上一个节点 help GC
 p.next = null;
 failed = false;
 // 返回中断标记 false
 return interrupted;
}
 if (
 // 判断是否应当 park, 进入 ㈦
 shouldParkAfterFailedAcquire(p, node) &&
 // park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧
 parkAndCheckInterrupt()
 ) {
 interrupted = true;
 }
 }
 } finally {
 if (failed)
 cancelAcquire(node);
 }
 }
 
 // ㈦ AQS 继承过来的方法, 方便阅读, 放在此处
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 // 获取上一个节点的状态
 int ws = pred.waitStatus;
 if (ws == Node.SIGNAL) {
 // 上一个节点都在阻塞, 那么自己也阻塞好了
 return true;
 }
 // > 0 表示取消状态
 if (ws > 0) {
 // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
 do {
 node.prev = pred = pred.prev;
 } while (pred.waitStatus > 0);
 pred.next = node;
 } else {
 // 这次还没有阻塞
 // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
 }
 return false;
 }
 
 // ㈧ 阻塞当前线程
 private final boolean parkAndCheckInterrupt() {
 LockSupport.park(this);
 return Thread.interrupted();
 }
}

解锁源码

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
 // 解锁实现
 public void unlock() {
 sync.release(1);
 }
 
 // AQS 继承过来的方法, 方便阅读, 放在此处
 public final boolean release(int arg) {
 // 尝试释放锁, 进入 ㈠
 if (tryRelease(arg)) {
 // 队列头节点 unpark
 Node h = head; 
 if (
 // 队列不为 null
 h != null &&
 // waitStatus == Node.SIGNAL 才需要 unpark
 h.waitStatus != 0
 ) {
 // unpark AQS 中等待的线程, 进入 ㈡
 unparkSuccessor(h);
 }
 return true;
 }
 return false;
 }
 
 // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处
 protected final boolean tryRelease(int releases) {
 // state--
 int c = getState() - releases;
 if (Thread.currentThread() != getExclusiveOwnerThread())
 throw new IllegalMonitorStateException();
 boolean free = false;
 // 支持锁重入, 只有 state 减为 0, 才释放成功
 if (c == 0) {
 free = true;
 setExclusiveOwnerThread(null);
 }
 setState(c);
 return free;
 }
 
 // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
 private void unparkSuccessor(Node node) {
 // 如果状态为 Node.SIGNAL 尝试重置状态为 0
 // 不成功也可以
 int ws = node.waitStatus;
 if (ws < 0) {
 compareAndSetWaitStatus(node, ws, 0);
 }
 // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
 Node s = node.next;
 // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
 if (s == null || s.waitStatus > 0) {
 s = null;
 for (Node t = tail; t != null && t != node; t = t.prev)
 if (t.waitStatus <= 0)
 s = t;
 }
 if (s != null)
 LockSupport.unpark(s.thread);
 }
}

可重入原理

static final class NonfairSync extends Sync {
 // ...
 
 // Sync 继承过来的方法, 方便阅读, 放在此处
 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;
 if (nextc < 0) // overflow
 throw new Error("Maximum lock count exceeded");
 setState(nextc);
 return true;
 }
 return false;
 }
 
 // Sync 继承过来的方法, 方便阅读, 放在此处
 protected final boolean tryRelease(int releases) {
 // state-- 
 int c = getState() - releases;
 if (Thread.currentThread() != getExclusiveOwnerThread())
 throw new IllegalMonitorStateException();
 boolean free = false;
 // 支持锁重入, 只有 state 减为 0, 才释放成功
 if (c == 0) {
 free = true;
 setExclusiveOwnerThread(null);
 }
setState(c);
 return free;
 }
}

可打断原理

不可打断模式

在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
 // Sync 继承自 AQS
static final class NonfairSync extends Sync {
 // ...
 
 private final boolean parkAndCheckInterrupt() {
 // 如果打断标记已经是 true, 则 park 会失效
 LockSupport.park(this);
 // interrupted 会清除打断标记
 return Thread.interrupted();
 }
 
 final boolean acquireQueued(final Node node, int arg) {
 boolean failed = true;
 try {
 boolean interrupted = false;
 for (;;) {
 final Node p = node.predecessor();
 if (p == head && tryAcquire(arg)) {
 setHead(node);
 p.next = null;
 failed = false;
 // 还是需要获得锁后, 才能返回打断状态
 return interrupted;
 }
 if (
 shouldParkAfterFailedAcquire(p, node) &&
 parkAndCheckInterrupt()
 ) {
 // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
 interrupted = true;
 }
 }
 } finally {
 if (failed)
 cancelAcquire(node);
 }
 }
 
 public final void acquire(int arg) {
 if (
 !tryAcquire(arg) &&
 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
 ) {
 // 如果打断状态为 true
 selfInterrupt();
 }
 }
 
 static void selfInterrupt() {
 // 重新产生一次中断
 Thread.currentThread().interrupt();
 }
}

可打断模式

static final class NonfairSync extends Sync {
 public final void acquireInterruptibly(int arg) throws InterruptedException {
 if (Thread.interrupted())
 throw new InterruptedException();
 // 如果没有获得到锁, 进入 ㈠
 if (!tryAcquire(arg))
 doAcquireInterruptibly(arg);
 }
 
 // ㈠ 可打断的获取锁流程
 private void doAcquireInterruptibly(int arg) throws InterruptedException {
 final Node node = addWaiter(Node.EXCLUSIVE);
 boolean failed = true;
 try {
 for (;;) {
 final Node p = node.predecessor();
 if (p == head && tryAcquire(arg)) {
 setHead(node);
 p.next = null; // help GC
 failed = false;
 return;
 }
 if (shouldParkAfterFailedAcquire(p, node) &&
 parkAndCheckInterrupt()) {
 // 在 park 过程中如果被 interrupt 会进入此
 // 这时候抛出异常, 而不会再次进入 for (;;)
 throw new InterruptedException();
 }
 }
 } finally {
 if (failed)
 cancelAcquire(node);
 }
 }
}

公平锁实现原理

static final class FairSync extends Sync {
 private static final long serialVersionUID = -3000897897090466540L;
 final void lock() {
 acquire(1);
 }
 
 // AQS 继承过来的方法, 方便阅读, 放在此处
 public final void acquire(int arg) {
 if (
 !tryAcquire(arg) &&
 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
 ) {
 selfInterrupt();
 }
 }
 // 与非公平锁主要区别在于 tryAcquire 方法的实现
 protected final boolean tryAcquire(int acquires) {
 final Thread current = Thread.currentThread();
 int c = getState();
 if (c == 0) {
 // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
 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;
 }
 
 // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
 public final boolean hasQueuedPredecessors() {
 Node t = tail;
 Node h = head;
 Node s;
 // h != t 时表示队列中有 Node
 return h != t &&
 (
 // (s = h.next) == null 表示队列中还有没有老二
 (s = h.next) == null ||
 // 或者队列中老二线程不是此线程
 s.thread != Thread.currentThread()
 );
 }
}

条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

多线程-并发编程_第214张图片

多线程-并发编程_第215张图片

多线程-并发编程_第216张图片

多线程-并发编程_第217张图片

多线程-并发编程_第218张图片

多线程-并发编程_第219张图片

多线程-并发编程_第220张图片

条件变量原理源码

public class ConditionObject implements Condition, java.io.Serializable {
 private static final long serialVersionUID = 1173984872572414699L;
 
 // 第一个等待节点
 private transient Node firstWaiter;
 
 // 最后一个等待节点
 private transient Node lastWaiter;
 public ConditionObject() { }
 // ㈠ 添加一个 Node 至等待队列
 private Node addConditionWaiter() {
 Node t = lastWaiter;
 // 所有已取消的 Node 从队列链表删除, 见 ㈡
 if (t != null && t.waitStatus != Node.CONDITION) {
 unlinkCancelledWaiters();
 t = lastWaiter;
 }
 // 创建一个关联当前线程的新 Node, 添加至队列尾部
 Node node = new Node(Thread.currentThread(), Node.CONDITION);
 if (t == null)
 firstWaiter = node;
 else
 t.nextWaiter = node;
 lastWaiter = node;
 return node;
 }
 // 唤醒 - 将没取消的第一个节点转移至 AQS 队列
 private void doSignal(Node first) {
 do {
 // 已经是尾节点了
 if ( (firstWaiter = first.nextWaiter) == null) {
 lastWaiter = null;
 }
 first.nextWaiter = null;
 } while (
 // 将等待队列中的 Node 转移至 AQS 队列, 不成功且还有节点则继续循环 ㈢
 !transferForSignal(first) &&
 // 队列还有节点
 (first = firstWaiter) != null
 );
 }
 
 // 外部类方法, 方便阅读, 放在此处
 // ㈢ 如果节点状态是取消, 返回 false 表示转移失败, 否则转移成功
 final boolean transferForSignal(Node node) {
 // 如果状态已经不是 Node.CONDITION, 说明被取消了
 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
 return false;
 // 加入 AQS 队列尾部
 Node p = enq(node);
 int ws = p.waitStatus;
 if (
 // 上一个节点被取消
 ws > 0 ||
 // 上一个节点不能设置状态为 Node.SIGNAL
 !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 
 ) {
 // unpark 取消阻塞, 让线程重新同步状态
 LockSupport.unpark(node.thread);
 }
 return true;
 }
 // 全部唤醒 - 等待队列的所有节点转移至 AQS 队列
 private void doSignalAll(Node first) {
 lastWaiter = firstWaiter = null;
 do {
 Node next = first.nextWaiter;
 first.nextWaiter = null;
 transferForSignal(first);
 first = next;
 } while (first != null);
 }
 
 // ㈡
 private void unlinkCancelledWaiters() {
 // ...
 }
 // 唤醒 - 必须持有锁才能唤醒, 因此 doSignal 内无需考虑加锁
 public final void signal() {
 if (!isHeldExclusively())
 throw new IllegalMonitorStateException();
 Node first = firstWaiter;
 if (first != null)
 doSignal(first);
 }
 // 全部唤醒 - 必须持有锁才能唤醒, 因此 doSignalAll 内无需考虑加锁
 public final void signalAll() {
 if (!isHeldExclusively())
 throw new IllegalMonitorStateException();
 Node first = firstWaiter;
 if (first != null)
 doSignalAll(first);
 }
 // 不可打断等待 - 直到被唤醒
 public final void awaitUninterruptibly() {
 // 添加一个 Node 至等待队列, 见 ㈠
 Node node = addConditionWaiter();
 // 释放节点持有的锁, 见 ㈣
 int savedState = fullyRelease(node);
 boolean interrupted = false;
 // 如果该节点还没有转移至 AQS 队列, 阻塞
 while (!isOnSyncQueue(node)) {
 // park 阻塞
 LockSupport.park(this);
 // 如果被打断, 仅设置打断状态
 if (Thread.interrupted())
 interrupted = true;
 }
 // 唤醒后, 尝试竞争锁, 如果失败进入 AQS 队列
 if (acquireQueued(node, savedState) || interrupted)
 selfInterrupt();
 }
 // 外部类方法, 方便阅读, 放在此处
 // ㈣ 因为某线程可能重入,需要将 state 全部释放
 final int fullyRelease(Node node) {
 boolean failed = true;
 try {
 int savedState = getState();
 if (release(savedState)) {
 failed = false;
 return savedState;
 } else {
 throw new IllegalMonitorStateException();
 }
 } finally {
 if (failed)
 node.waitStatus = Node.CANCELLED;
 }
 }
 // 打断模式 - 在退出等待时重新设置打断状态
 private static final int REINTERRUPT = 1;
 // 打断模式 - 在退出等待时抛出异常
 private static final int THROW_IE = -1;
 // 判断打断模式
 private int checkInterruptWhileWaiting(Node node) {
 return Thread.interrupted() ?
 (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
 0;
 }
 // ㈤ 应用打断模式
 private void reportInterruptAfterWait(int interruptMode)
 throws InterruptedException {
 if (interruptMode == THROW_IE)
 throw new InterruptedException();
 else if (interruptMode == REINTERRUPT)
 selfInterrupt();
 }
 // 等待 - 直到被唤醒或打断
 public final void await() throws InterruptedException {
 if (Thread.interrupted()) {
 throw new InterruptedException();
 }
 // 添加一个 Node 至等待队列, 见 ㈠
 Node node = addConditionWaiter();
 // 释放节点持有的锁
 int savedState = fullyRelease(node);
 int interruptMode = 0;
 // 如果该节点还没有转移至 AQS 队列, 阻塞
 while (!isOnSyncQueue(node)) {
 // park 阻塞
 LockSupport.park(this)
 // 如果被打断, 退出等待队列
 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
 break;
 }
 // 退出等待队列后, 还需要获得 AQS 队列的锁
 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
 interruptMode = REINTERRUPT;
 // 所有已取消的 Node 从队列链表删除, 见 ㈡
 if (node.nextWaiter != null) 
 unlinkCancelledWaiters();
 // 应用打断模式, 见 ㈤
 if (interruptMode != 0)
 reportInterruptAfterWait(interruptMode);
 }
 // 等待 - 直到被唤醒或打断或超时
 public final long awaitNanos(long nanosTimeout) throws InterruptedException {
 if (Thread.interrupted()) {
 throw new InterruptedException();
 }
 // 添加一个 Node 至等待队列, 见 ㈠
 Node node = addConditionWaiter();
 // 释放节点持有的锁
 int savedState = fullyRelease(node);
 // 获得最后期限
 final long deadline = System.nanoTime() + nanosTimeout;
 int interruptMode = 0;
 // 如果该节点还没有转移至 AQS 队列, 阻塞
 while (!isOnSyncQueue(node)) {
 // 已超时, 退出等待队列
 if (nanosTimeout <= 0L) {
 transferAfterCancelledWait(node);
 break;
 }
 // park 阻塞一定时间, spinForTimeoutThreshold 为 1000 ns
 if (nanosTimeout >= spinForTimeoutThreshold)
 LockSupport.parkNanos(this, nanosTimeout);
 // 如果被打断, 退出等待队列
 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
 break;
 nanosTimeout = deadline - System.nanoTime();
 }
 // 退出等待队列后, 还需要获得 AQS 队列的锁
 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
 interruptMode = REINTERRUPT;
 // 所有已取消的 Node 从队列链表删除, 见 ㈡
 if (node.nextWaiter != null)
 unlinkCancelledWaiters();
 // 应用打断模式, 见 ㈤
 if (interruptMode != 0)
 reportInterruptAfterWait(interruptMode);
 return deadline - System.nanoTime();
 }
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
读写锁原理
1. 图解流程
读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个
t1 w.lock,t2 r.lock
1) t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁
使用的是 state 的高 16 位 2)t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写
锁占据,那么 tryAcquireShared 返回 -1 表示失败
tryAcquireShared 返回值表示
-1 表示失败
 // 等待 - 直到被唤醒或打断或超时, 逻辑类似于 awaitNanos
 public final boolean awaitUntil(Date deadline) throws InterruptedException {
 // ...
 }
 // 等待 - 直到被唤醒或打断或超时, 逻辑类似于 awaitNanos
 public final boolean await(long time, TimeUnit unit) throws InterruptedException {
 // ...
 }
 // 工具方法 省略 ...
}

ReentrantReadWriteLock(读写锁)

当读操作远远高于写操作时,这时候使用 读写锁 读-读 可以并发 ,提高性能。 类似于数据库中的 select ... from ... lock in share mode
提供一个 数据容器类 内部分别使用读锁保护数据的 read() 方法,写锁保护数据的 write() 方法
 
package cn.itcast.n8;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantReadWriteLock;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestReadWriteLock")
public class TestReadWriteLock {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
            dataContainer.read();
        }, "t1").start();

        new Thread(() -> {
            dataContainer.read();
        }, "t2").start();
    }
}

@Slf4j(topic = "c.DataContainer")
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    public Object read() {
        log.debug("获取读锁...");
        r.lock();
        try {
            log.debug("读取");
            sleep(1);
            return data;
        } finally {
            log.debug("释放读锁...");
            r.unlock();
        }
    }

    public void write() {
        log.debug("获取写锁...");
        w.lock();
        try {
            log.debug("写入");
            sleep(1);
        } finally {
            log.debug("释放写锁...");
            w.unlock();
        }
    }
}

多线程-并发编程_第221张图片

多线程-并发编程_第222张图片

多线程-并发编程_第223张图片

多线程-并发编程_第224张图片

多线程-并发编程_第225张图片

多线程-并发编程_第226张图片

多线程-并发编程_第227张图片

StampedLock

多线程-并发编程_第228张图片

package cn.itcast.n8;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.StampedLock;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestStampedLock")
public class TestStampedLock {
    public static void main(String[] args) {
        DataContainerStamped dataContainer = new DataContainerStamped(1);
        new Thread(() -> {
            dataContainer.read(1);
        }, "t1").start();
        sleep(0.5);
        new Thread(() -> {
            dataContainer.read(0);
        }, "t2").start();
    }
}

@Slf4j(topic = "c.DataContainerStamped")
class DataContainerStamped {
    private int data;
    private final StampedLock lock = new StampedLock();

    public DataContainerStamped(int data) {
        this.data = data;
    }

    public int read(int readTime) {
        long stamp = lock.tryOptimisticRead();
        log.debug("optimistic read locking...{}", stamp);
        sleep(readTime);
        if (lock.validate(stamp)) {
            log.debug("read finish...{}, data:{}", stamp, data);
            return data;
        }
        // 锁升级 - 读锁
        log.debug("updating to read lock... {}", stamp);
        try {
            stamp = lock.readLock();
            log.debug("read lock {}", stamp);
            sleep(readTime);
            log.debug("read finish...{}, data:{}", stamp, data);
            return data;
        } finally {
            log.debug("read unlock {}", stamp);
            lock.unlockRead(stamp);
        }
    }

    public void write(int newData) {
        long stamp = lock.writeLock();
        log.debug("write lock {}", stamp);
        try {
            sleep(2);
            this.data = newData;
        } finally {
            log.debug("write unlock {}", stamp);
            lock.unlockWrite(stamp);
        }
    }
}

多线程-并发编程_第229张图片

多线程-并发编程_第230张图片

Semaphore [ˈseməfɔː(r)]信号量

信号量,用来限制能同时访问共享资源的线程上限。

多线程-并发编程_第231张图片

多线程-并发编程_第232张图片

CountdownLatch 

多线程-并发编程_第233张图片

这个工具常用来控制线程等待,可以让一个线程等待到倒计数结束再执行。 

public class CountDownLatchDemo implements Runnable {
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo=new CountDownLatchDemo();
    @Override
    public void run() {
        try {
            //模拟检查任务
            Thread.sleep(new Random().nextInt(10)*1000);
            System.out.println("check complete");
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for(int i=0;i<10;i++){
            exec.submit(demo);
        }
        //等待检查
        end.await();
        //发射火箭
        System.out.println("Fire!");
        exec.shutdown();
    }
}

 countDown 方法通知计数器一个线程已经完成,倒计数减一。主线程在CountDownLatch 上等待,所有检查任务完成后继续执行。

多线程-并发编程_第234张图片

多线程-并发编程_第235张图片

多线程-并发编程_第236张图片

多线程-并发编程_第237张图片

多线程-并发编程_第238张图片

多线程-并发编程_第239张图片

多线程-并发编程_第240张图片 CyclicBarrier

多组循环可以恢复到原来的状态(原来的倒计时),不用每次都重新创建一个倒计时锁对象(countdownLatch不能重用)

循环栅栏(CyclicBarrier)

用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。

和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。

CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环栅栏。

CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。

多线程-并发编程_第241张图片

举个栗子 

CountDownLatch

  • 让一些线程阻塞直到另外一些线程完成后才别唤醒
  • CountDownLatch主要有两个方法,当一个或多个线程调用await 方法时,调用线程会被阻塞,其他线程调用countDown 方法计数器减1(调用countDown 方法时线程不会阻塞),当计数器的值变为0,因调用await 方法被阻塞的线程会被唤醒,进而继续执行。
  • 关键点:
    1)await() 方法
    2) countDown() 方法
  • 例子:一个教室有1个班长和若干个学生,班长要等所有学生都走了才能关门,那么要如何实现。
//没有使用CountDownLatch
public class CountDownLanchDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 离开了教室...");
            }, i+"号学生").start();
        }
        System.out.println("========班长锁门========");
    }

}
//打印结果
/**
	0号学生 离开了教室...
	4号学生 离开了教室...
	3号学生 离开了教室...
	2号学生 离开了教室...
	========班长锁门========
	1号学生 离开了教室...
	5号学生 离开了教室...
*/
//可以看出班长走之后还有两个学生被锁在了教室

//=====解决方法=====
public static void main(String[] args) {
	   try {
	        CountDownLatch countDownLatch = new CountDownLatch(6);
	        for (int i = 0; i < 6; i++) {
	            new Thread(() -> {
	                countDownLatch.countDown();
	                System.out.println(Thread.currentThread().getName() + " 离开了教室...");
	            }, i + "号学生").start();
	        }
	        countDownLatch.await(); //这里相当于挡住,在countDownLatch还没有变0之前不能执行以下方法
	        System.out.println("========班长锁门========");
	    } catch (InterruptedException e) {
	        e.printStackTrace();
	    }
}
//打印结果
/**
	0号学生 离开了教室...
	3号学生 离开了教室...
	2号学生 离开了教室...
	1号学生 离开了教室...
	4号学生 离开了教室...
	5号学生 离开了教室...
	========班长锁门========
*/
//可以看出班长等学生都走了才锁门

6)CyclicBarrier

  • CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)时被阻塞,知道最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。
  • 例子:跟上面一样,一个班级有六个学生,要等学生都离开后班长才能关门。
public static void main(String[] args) {
        CyclicBarrier cyclicBarrie = new CyclicBarrier(6, () -> {
            System.out.println("班长锁门离开教室...");
        });

        for (int i = 0; i < 6; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println("离开教室...");
                try {
                    cyclicBarrie.await();     //调用一次内部就会加1,与上面6呼应,等到6的时候就可以执行上面班长离开的方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, temp + "号学生").start();
        }
    }
 //打印结果
/**
	0号学生离开教室...
	4号学生离开教室...
	3号学生离开教室...
	2号学生离开教室...
	1号学生离开教室...
	5号学生离开教室...
	班长锁门离开教室...
*/
//可以看出班长等学生都走了才锁门

CountDownLatch 和 CyclicBarrier 其实是相反的操作,一个是相减到0开始执行,一个是相加到指定值开始执行

 

线程安全集合类

多线程-并发编程_第242张图片

多线程-并发编程_第243张图片

ConcurrentHashMap的一个实用案例:统计字母出现次数

多线程-并发编程_第244张图片

改进:使用ConcurrentHashMap的computeIfAbsent方法+原子累加器 

多线程-并发编程_第245张图片

ConcurrentHashMap原理

多线程-并发编程_第246张图片

多线程-并发编程_第247张图片

 

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