多线程的概念
多线程指的是一个应用程序中有多条并发执行的线索,每一个线索都称之为一个线程,他们之间会交替的执行,彼此之间可以进行通讯。
进程
每一个独立执行的程序都可以称之为一个进程
线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
并发
一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
并行
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
线程的创建
JDK1.5 之前创建线程的两种方式;继承Thread类 , 实现Runnable接口
方式一 继承Thread类
- 步骤:
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
public class MyThread {
public static void main(String[] args) {
ThreadMe threadMe = new ThreadMe();
threadMe.start();
for (int i = 0; i < 100; i++) {
System.out.println("my Thread Main ===> " + i);
}
}
}
class ThreadMe extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("thread Me ===> " + i);
}
}
}
方式二 实现Runnable接口
-
定义子类,实现Runnable接口。
-
子类中重写Runnable接口中的run方法。
-
通过Thread类含参构造器创建线程对象。
-
将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
-
调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
public class MyThreadRunnable {
public static void main(String[] args) {
meThreadRunnable meThreadRunnable = new meThreadRunnable();
Thread thread = new Thread(meThreadRunnable);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("my Thread Main ===> " + i);
}
}
}
class meThreadRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("thread Me ===> " + i);
}
}
}
Threa类相关的API
| Thread类的相关API | |
|---|---|
| void start(): | 启动线程,并执行对象的run()方法 |
| run() | 线程在被调度时执行的操作 |
| String getName() | 返回线程的名称 |
| void setName(String name) | 设置该线程名称 |
| static Thread currentThread() | 返回当前线程。在Thread子类中就 是this,通常用于主线程和Runnable实现类 |
| static void yield() | 线程让步 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法 |
| join() | 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将 被阻塞,直到 join() 方法加入的 join 线程执行完为止 |
| static void sleep(long millis) | 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后 重排队。 |
| stop | 强制线程生命期结束,不推荐使用 |
| boolean isAlive() | 返回boolean,判断线程是否还活着 |
线程的调度
线程的优先级
线程的生命周期和状态
线程的几种状态,创建、就绪、运行、阻塞、死亡
-
新建 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
-
就绪处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
-
运行当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
-
阻塞在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中 止自己的执行,进入阻塞状态
-
死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
线程的同步
线程安全问题
public class TicketWindow {
public static void main(String[] args) {
Window1 window1 = new Window1();
new Thread(window1,"窗口1:").start();
new Thread(window1,"窗口2:").start();
new Thread(window1,"窗口3:").start();
}
}
class Window1 implements Runnable{
private int ticket = 10;
@Override
public void run() {
while(ticket > 0){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "------卖出的票号是:" + ticket--);
}
}
}
执行的结果
可以看得到不同的窗口买出了相同的车票,这是不能被接受的。
解决方法:
同步代码块
synchronized (对象){ // 需要被同步的代码; }
public class TicketWindow {
public static void main(String[] args) {
Window1 window1 = new Window1();
new Thread(window1,"窗口1:").start();
new Thread(window1,"窗口2:").start();
new Thread(window1,"窗口3:").start();
}
}
class Window1 implements Runnable{
private int ticket = 10;
Object lock = new Object();
@Override
public void run() {
while(true){
synchronized(lock){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + "------卖出的票号是:" + ticket--);
}else{
break;
}
}
}
}
}
synchronized的锁是什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
- 注意
- 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就 无法保证共享资源的安全
- 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎)
释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导 致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。 应尽量避免使用suspend()和resume()来控制线程
死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
Lock(锁)
从JDK5.0开始 Java提供了更强大的线程同步机制——通过显式定义同 步锁对象来实现同步。同步锁使用Lock对象充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。
synchronized 与 Lock 的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)
线程的通信
wait() 与 notify() 和 notifyAll()
wait() 令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
notify() 唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll () 唤醒正在排队等待资源的所有线程结束等待.
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。
新增创建线程的方式
新增方式一 实现Callable接口
public class CallableDemo {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
OptThread optThread = new OptThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask<>(optThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法
Thread thread = new Thread(futureTask);
thread.start();
//6.获取Callable中的call
try {
Object o = futureTask.get();
System.out.println("总的和是:" + o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.创建一个实现Callable的实现类
class OptThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1;i<=10;i++){
sum += i;
System.out.println(Thread.currentThread().getName() + " i = " + i);
}
return sum;
}
}
新增方式二 使用线程池
public class ThreadPool {
public static void main(String[] args) {
//1、创建线程池
ExecutorService service = Executors.newFixedThreadPool(10); //创建线程池
//2、执行指定的线程操作,需要提供事项Runnable接口或者Callable接口实现类的对象
service.execute(new OprThread()); //适合于Runnable接口
// service.submit(); //适合用于Callable接口
//3、关闭线程链接
service.shutdown();
}
}
class OprThread implements Runnable{
@Override
public void run() {
for (int i = 1;i <= 10;i++){
System.out.println(Thread.currentThread().getName() + " i:" + i);
}
}
}
多线程进阶
在学习完多线程的基础篇以后,最近又看了黑马的课,前来总结
复习进程和线程
进程
程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)
线程
一个进程之内可以分为一到多个线程。
一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作 为线程的容器
并发与并行
何为并发,通俗点来说就是在同一个时间点cup去做多件事情
何为并行,两个及以上的CPU在同时处理不同的事情
同步和异步
同步:什么是同步,就是需要等待结果返回,然后才能继续执行程序。
异步:什么是异步,不需要等待结果的返回,可以继续执行程序。
在多线程的模式下,尽量使用异步,比如说在读取文件磁盘的时候需要花费3秒钟,如果没有线程的调度,在这段时间内cup什么也做不了。白白浪费了这段时间。
多线程的创建方式
使用lambda表达式创建线程
方式一: 通过Thread 和 Runnbale的方式进行创建
public class ThreadCreate01 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("我是t1线程....");
});
t1.start();
System.out.println("我是main线程");
}
}
方式二:通过并发包的FutureTask 进行创建
/**
* FutureTask 接口
*/
public class ThreadCreate02 {
public static void main(String[] args) {
//创建任务对象
FutureTask futureTask = new FutureTask(() -> {
System.out.println("我是通过FutureTask创建的线程");
return 100;
});
Thread t1 = new Thread(futureTask,"FutureTaskThread");
t1.start();
System.out.println("我是Main线程");
}
}
可以看到他的构造方法,有一个Callable参数,这个接口也是一个函数式的接口,所以可以使用Lambda的形式使用。
多线程的运行原理
栈和栈帧
在Java虚拟机中有堆空间,方法区、栈空间。其中的栈空间就是给线程用的,每一次启动线程虚拟机就会分配一块栈内存空间。每一个栈是由栈帧组成,对应者每次调用时候所占用的内存
线程的上下文切换
当前的线程切换到另外一个线程,线程cpu时间片用完,垃圾回收,线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
线程中常用的方法
| 方法名 | 是否是静态的 | 功能 | 注意 |
|---|---|---|---|
| start() | NO | 启动线程 | start方法只是启动线程,启动以后并不会立马就运行 |
| run() | NO | 普通的方法 | |
| join() | NO | 等待线程的结束 | |
| join(long n) | NO | 等待线程的结束,单位毫秒 | |
| getId() | NO | 获取线程的Id | 线程的ID是唯一的 |
| getName() | NO | 获取线程的名称 | |
| setName(String) | NO | 设置线程的名称 | |
| getPriority() | NO | 获取线程的优先级 | |
| setPriority(int) | NO | 设置线程的优先级 | |
| getState() | NO | 获取线程的状态 | |
| isInterrupted() | NO | 判断线程是否被打断 | 不会清除掉打断标记 |
| isAlive()() | NO | 判断线程是否还存活 | |
| interrupt() | NO | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 打断标 记 ;如果打断的正在运行的线程,则会设置 打断标 记 ;park 的线程被打断,也会设置 打断标记 |
| interrupted() | YES | 打断线程 | 会清除掉打断标记 |
| currentThread() | YES | 获取当前正在执行的线程 | |
| sleep(long n)() | YES | 让当前线程休眠几秒,让出cup的时间片 | |
| yield() | YES | 提示线程调度器 让出当前线程对 CPU的使用 |
start方法和run方法
@Slf4j
public class StartAndRunTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.debug("我是Runnable里面的run方法....");
}
};
Thread t1 = new Thread(runnable);
//调用的是普通的方法
runnable.run();
//线程启动方法
t1.start();
}
}
调用完成以后,可以明显的看到他们启动的是不同的线程。
直接调用 run 是在主线程中执行了 run,没有启动新的线程 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
Sleep 和 yield
sleep sleep 方法会让一个线程从Running状态进入到timewaiting状态,会让线程休眠
其他的线程可以使用interrupt方法打断正在休眠的线程,时期抛出interruptException
在休眠结束以后,线程是不会立马执行,他需要重新的参与cpu的调度和竞争。
/**
* 线程打断案例
*/
@Slf4j
public class InterrupeTest01 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
log.debug("t1线程开始了");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("t1线程被打断了");
e.printStackTrace();
}
log.debug("t1线程结束了");
}, "t1");
log.debug("Main线程开始了");
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
yield
线程让步,会让一个线程从running状态进入到Runnable状态。
他依赖操作系统的任务调度器
/**
* 线程让步案例
*/
@Slf4j
public class YieleTest01 {
public static void main(String[] args) {
//准备两个线程
//t1线程
Thread t1 = new Thread(()->{
int t1count = 0;
while(true){
//让出当前cup的执行权
Thread.yield();
t1count ++;
log.debug("{}",t1count);
}
},"t1");
//t2线程
Thread t2 = new Thread(()->{
int t2count = 0;
while(true){
t2count ++;
log.debug("{}",t2count);
}
},"t2");
t1.start();
t2.start();
}
}
Join方法
/**
* Join方法复习
*/
@Slf4j
public class JoinTest01 {
static int r = 0;
public static void main(String[] args) throws InterruptedException {
log.debug("Main线程开始");
Thread t1 = new Thread(() -> {
log.debug("T1线程开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("T1线程结束");
r = 10;
});
t1.start();
//等待某个线程结束
t1.join(100);
log.debug("执行的结果为:{}", r);
log.debug("Main线程");
}
}
首先可以知道Main线程和t1线程是并行的执行的,当t1线程执行到sleep的时候,t1线程就去休息了进入到了TimeWating状态,但是main线程还是running状态啊,会继续向下执行,如果没有join方法,就会一直跑下去,打印r的结果 0。但是加了join方法就不一样了,main线程跑到join方法的时候会先判断这个线程是否执行结束了,如果结束了main线程才会继续向下执行,然后打印r的结果。
线程打断 interrupt 方法
守护线程
守护线程:守护线程就是当一个java程序执行完毕以后会自动销毁的一个线程
案例:设置一个守护线程
/**
* 守护线程
*/
@Slf4j
public class DeamonThreadTest01 {
public static void main(String[] args) {
Thread tDeamon = new Thread(() -> {
int countDeamon = 0;
while(countDeamon < 80){
log.debug("这是一个守护线程,{}",countDeamon++);
}
},"tDeamon");
Thread t1 = new Thread(() -> {
int countT1 = 0;
while(countT1 < 30){
log.debug("这是一个非守护线程T1,{}",countT1++);
}
},"t1");
Thread t2 = new Thread(() -> {
int countT2 = 0;
while(countT2 < 30){
log.debug("这是一个非守护线程T2,{}",countT2++);
}
},"t2");
tDeamon.setDaemon(true);
t1.start();
t2.start();
tDeamon.start();
}
}
线程的几种运行状态
创建:仅仅是创建的线程对象,此时还没有对线程做任何的操作
就绪:cpu可以执行调度
运行:cpu拿到了执行的权力,并运行这段代码
阻塞:整个线程进入到一个阻塞的状态,比如说执行了Join、sleep方法
死亡:当前的线程使用结束,这个线程也就销毁了
线程安全问题
为什么会产生线程安全问题,就是因为不同的线程使用了共享变量,导致线程安全问题的产生。
线程安全问题案例演示
产生线程安全问题的代码:
/**
* 线程安全问题
*/
@Slf4j
public class SalfQuestion01 {
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count--;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",count);
}
}
一个数加5000次然后减5000次,这个值应该是0才对,显然这个时候就出现了线程安全问题。
代码的一个执行流程
出现负数的情况:
出现正数的情况:
临界区和竞态条件
临界区Critical Section
一个程序运行多个线程本身是没有问题的。问题出现在多个线程访问共享资源,多个线程共享读资源其实也没有问题,在多个线程对共享资源读写操作时候发生指令交错,就会出现线程安全问题。
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码为临界区域。
竞态条件 Race Condition
多个线程在临界区内执行,由于不同的执行序列而导致结果无法进行预测,称之为发生了竞态条件
解决线程安全的方案
可以使用阻塞式的解决方案:synchronized关键字或者使用Lock锁
还可以使用非阻塞式的方案:原子变量(CAS)
使用Synchronized进行解决
/**
* 线程安全问题解决
*/
@Slf4j
public class SalfQuestion02 {
static int count = 0;
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (obj){
count++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (obj){
count--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",count);
}
}
加锁以后的执行流程:
使用面向对象的思想改进上面的代码
Room类
class Room{
private int counter = 0;
public void increment(){
synchronized (this){
counter++;
}
}
public void decrement(){
synchronized (this){
counter--;
}
}
public int getCounter(){
synchronized (this){
return counter;
}
}
}
主方法类
public class Code04_FaceObjSynchronized {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.increment();
}
},"t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
room.decrement();
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("counter = " + room.getCounter());
}
}
synchronized 加载方法上
线程8锁
情况1:
/**
* 执行结果:
* 情况1: 先打印1后打印2
* 情况2: 先打印2后打印1
*/
@Slf4j
public class ThreadEngiht {
public static void main(String[] args) {
NumberEngiht n1 = new NumberEngiht();
NumberEngiht n2 = new NumberEngiht();
new Thread(() -> {
n1.a();
}).start();
new Thread(() -> {
n2.b();
}).start();
}
}
@Slf4j
class NumberEngiht{
public static synchronized void a(){
log.debug("1");
}
public static synchronized void b(){
log.debug("2");
}
}
情况2:
/**
* 两种结果:
* 结果1:先打印2,过一秒钟打印 1
* 结果2:等一秒钟,打印 1 2
*
*/
@Slf4j
public class ThreadTwo {
public static void main(String[] args) {
NumberTwo n2 = new NumberTwo();
new Thread(() -> {
log.debug("t1 线程开始了...");
n2.a();
},"t1").start();
new Thread(() -> {
log.debug("t2 线程开始了...");
n2.b();
},"t2").start();
}
}
@Slf4j
class NumberTwo{
//相当于锁的是This
public synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
情况3:
/**
* 执行的结果情况:
* 情况1:3 、1秒后1 2
* 情况2:23、1秒后 1
* 情况3:32、1秒后 1
*/
@Slf4j
public class ThreadThree {
public static void main(String[] args) {
NumberThree n3 = new NumberThree();
new Thread(()->{ n3.a(); }).start();
new Thread(()->{ n3.b(); }).start();
new Thread(()->{ n3.c(); }).start();
}
}
@Slf4j
class NumberThree{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
情况4:
/**
* 执行结果:
* 情况1 : 2 1秒后打印 1
*/
public class ThreadFore {
public static void main(String[] args) {
NumberFore n1 = new NumberFore();
NumberFore n2 = new NumberFore();
new Thread(() -> {
n1.a();
}).start();
new Thread(() -> {
n2.b();
}).start();
}
}
@Slf4j
class NumberFore{
public synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
情况5:
/**
* 执行结果:
* 情况1: 2 1秒后 打印1
*/
@Slf4j
public class ThreadFive {
public static void main(String[] args) {
NumberFive n1 = new NumberFive();
new Thread(()->{
n1.a();
}).start();
new Thread(() -> {
n1.b();
}).start();
}
}
@Slf4j
class NumberFive{
public static synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
情况6:
/**
* 执行结果
* 情况1:1秒后 1,2
* 情况2:2,1秒后打印1
*/
@Slf4j
public class ThreadSix {
public static void main(String[] args) {
NumberSix n1 = new NumberSix();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
n1.a();
}).start();
new Thread(() -> {
n1.b();
}).start();
}
}
@Slf4j
class NumberSix{
public static synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public static synchronized void b(){
log.debug("2");
}
}
情况7:
/**
* 执行结果
* 情况1:打印2 1秒钟以后打印 1
*/
@Slf4j
public class ThreadSeven {
public static void main(String[] args) {
NumberSeven n1 = new NumberSeven();
NumberSeven n2 = new NumberSeven();
new Thread(() -> {
n1.a();
}).start();
new Thread(() -> {
n2.b();
}).start();
}
}
@Slf4j
class NumberSeven{
public static synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
情况8:
/**
* 执行结果:
* 情况1: 1秒后打印 1 2
* 情况2: 先打印2 1秒以后打印 1
*/
@Slf4j
public class ThreadEngiht {
public static void main(String[] args) {
NumberEngiht n1 = new NumberEngiht();
NumberEngiht n2 = new NumberEngiht();
new Thread(() -> {
n1.a();
}).start();
new Thread(() -> {
n2.b();
}).start();
}
}
@Slf4j
class NumberEngiht{
public static synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1");
}
public static synchronized void b(){
log.debug("2");
}
}
常见的线程安全类
String、Integer、StringBuffer、Vecotor、HashTable、java.lang.concurrent包下面的类
Java对象头
Monitor原理
Monitor被称之为,监视器或者管程。
每一个Java对象都可以关联一个Monitor对象头,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
Synchronized原理
轻量级锁:轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。
重量级锁::解锁的时候、锁的记录的值不是null,这时使用 cas 将 Mark Word 的值恢复给对象 头,在解锁失败的时候轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。
锁膨胀::两个线程在竞争的过程中,其中的一个线程已经获取该对象的轻量级锁,这个时候表明是有竞争的,会进入到锁的膨胀过程。
锁的自旋::在做重量级锁优化的时候会使用到锁的自旋经行优化。
偏向锁:TODO 没明白,后期再来学习??
wait / notify
根据自己的理解就是当一个线程抢到锁以后但是不能立马工作,需要等待。这个时候外面的线程进不来,里面的线程也不工作,就会导致阻塞,所以这个时候需要将获取锁的那个线程释放,让它进入到WaitSet中。如果它的执行条件满足了会重新到外边排队竞争
Wait方法
/**
* wait 方法
* wait 的调用必须是在获取锁以后,调用完这个方法以后会进入到 waitSet 队列中
*
* wait() 或者 wait(0) 会无休止的一直等待下去,一直到这个线程被唤醒
*/
@Slf4j
public class WaitTest {
public final static Object obj = new Object();
public static void main(String[] args) {
// 没有获取锁 , 会抛出来的异常 java.lang.IllegalMonitorStateException
/*new Thread(() -> {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();*/
// wait正确使用方法
new Thread(() -> {
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
}
}
notify notifyAll方法
/**
* wait notify
*/
@Slf4j
public class WaitNotifyTest {
final static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (obj) {
log.debug("t1线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1线程其它代码....");
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("t2线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t2线程其它代码....");
}
},"t2").start();
Thread.sleep(2000);
log.debug("唤醒其他的线程...");
synchronized (obj){
// 随机唤醒一个线程 ??
//obj.notify();
// 唤醒全部的线程
obj.notifyAll();
}
}
}
wait / sleep
这两个方法的不同之处:
1、wait 方法是 Object类里面的方法,Sleep方法是Thread类里面的
2、wait 方法执行完成以后释放锁,sleep方法则不会
3、wait 方法需要先获取锁才可以使用,而sleep方法则不用获取锁也能使用。
保护暂停模式
案例实现:
@Slf4j
public class GuardedObjectTest {
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(() -> {
try {
// 子线程执行下载
List<String> response = DownLoader.download();
log.debug("download complete...");
guardedObject.complete(response);
} catch (IOException e) {
e.printStackTrace();
}
},"t1").start();
log.debug("waiting...");
// 主线程阻塞等待
Object response = guardedObject.get();
log.debug("get response: [{}] lines", ((List<String>) response).size());
}
}
class GuardedObject{
private Object response;
private final Object lock = new Object();
public Object get() {
synchronized (lock) {
// 条件不满足则等待
while (response == null) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
public void complete(Object response) {
synchronized (lock) {
// 条件满足,通知等待线程
this.response = response;
lock.notifyAll();
}
}
}
超时模板
public Object get(long time){
synchronized (lock){
long begin = System.currentTimeMillis();
//经历过的时间
long passedTime = 0;
while(response == null){
long waitTime = time - passedTime;
if (passedTime >= time){
break;
}
try {
lock.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//经历过的时间
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}
保护型暂停模式解耦
用下面的例子来演示
@Slf4j
public class GuardedObjectTest {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new People().start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Integer id : Mailboxes.getIds()) {
new Postman(id, "内容" + id).start();
}
}
}
@Slf4j
class GuardedObject{
private int id;
private Object response;
public GuardedObject(int generateId) {
this.id = generateId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public void complate(Object response){
synchronized (this){
this.response =response;
this.notifyAll();
}
}
public Object get(long timeOut){
synchronized (this){
//1、beginTime当前时间 和 delay延时时间
long beginTime = System.currentTimeMillis();
long delay = 0;
while(response == null){
//2、计算当前的延时是否到达timeOut
long waitTime = timeOut - delay;
if (waitTime<=0){
break;
}
try {
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
delay = System.currentTimeMillis() - beginTime;
}
}
return response;
}
}
@Slf4j
class People extends Thread{
@Override
public void run() {
// 收信
GuardedObject guardedObject = Mailboxes.createGuardedObject();
log.debug("开始收信 id:{}", guardedObject.getId());
Object mail = guardedObject.get(5000);
log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
}
}
@Slf4j
class Postman extends Thread {
private int id;
private String mail;
public Postman(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
log.debug("送信 id:{}, 内容:{}", id, mail);
guardedObject.complate(mail);
}
}
@Slf4j
class Mailboxes {
private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
private static int id = 1;
// 产生唯一 id
private static synchronized int generateId() {
return id++;
}
public static GuardedObject getGuardedObject(int id) {
return boxes.remove(id);
}
public static GuardedObject createGuardedObject() {
GuardedObject go = new GuardedObject(generateId());
boxes.put(go.getId(), go);
return go;
}
public static Set<Integer> getIds() {
return boxes.keySet();
}
}
使用了Mailboxes类和GuardedObject来做解耦。
生产者消费者模式
@Slf4j
public class MessageQueueDemo {
public static void main(String[] args) {
MessageQueue messageQueue = new MessageQueue(2);
for (int i=0;i<3;i++){
int id = i;
new Thread(() -> {
messageQueue.put(new Message(id,"值" + id));
},"生产者" + i).start();
}
/* try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(() -> {
while(true){
try {
Thread.sleep(1000);
messageQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者").start();
}
}
//消息队列
@Slf4j
class MessageQueue{
private LinkedList<Message> list = new LinkedList();
private int capcity;
public MessageQueue(int capcity) {
this.capcity = capcity;
}
//获取消息
public Message take(){
//检查对象是否为空
synchronized (list){
while(list.isEmpty()){
try {
log.debug("队列容量为空,无法继续获取。。。");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Message message = list.removeFirst();
log.debug("已经消费消息{}",message);
list.notifyAll();
return message;
}
}
//存入消息
public void put(Message message){
//检查队列是否是满的
synchronized (list){
while(list.size() >= capcity){
try {
log.debug("队列容量已满,无法继续存入。。。");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(message);
log.debug("已经存入消息{}",message);
list.notifyAll();
}
}
}
@Slf4j
class Message{
private int id;
private Object value;
public Message(int id, Object value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value=" + value +
'}';
}
}
park and unpark
这两个方法分别代表着线程的暂停 和 恢复线程的运行。
@Slf4j
public class ParkAndUnPark {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.debug("start ....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("park ....");
LockSupport.park();
log.debug("resume");
},"t1");
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("unpark ....");
LockSupport.unpark(t1);
}
}
特点:
park 和 unpark 与 wait 和 notify相比较
wait 和 notify 必须和 Object 的 Monitor相配合使用 , 而park 和 unpark 不必
park 和 unpark 是以线程为单位来【阻塞】和【唤醒】线程,而notify是随机唤醒一个等待的线程 notifyAll是唤醒所有的线程。
park 和 unpark可以先unpark 而 wait 和 notify 不能先 notify。
park 和 unpark原理
多把锁
/**
* 多把锁子
* 比如说一个屋子有两个功能,学习和睡觉
* 现在一个人要学习,一个人要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
* 解决方法是准备多个房间(多个对象锁)
*
*/
public class MoreLockDemo {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
//这样做会有阻塞的问题,是串行执行的,执行完sleep以后才可以让另外一个人进行学习
new Thread(() -> {
bigRoom.sleep();
},"小女").start();
new Thread(() -> {
bigRoom.study();
},"小南").start();
}
}
@Slf4j
class BigRoom{
//解决方案,我可以使用多把锁,每一把锁之间不存在任何的关系,也就是说没有共享资源
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
//睡觉
public void sleep() {
synchronized (bedRoom) {
log.debug("sleeping 2 小时");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//学习
public void study() {
synchronized (studyRoom) {
log.debug("study 1 小时");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
死锁
死锁产生的现象:每一个线程各自持有一把锁,但是在获取对方锁的时候,会产生死锁
/**
* 死锁
*
*/
@Slf4j
public class DeathLock {
static Object A = new Object();
static Object B = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (A){
log.debug("t1 线程获取了 A锁");
synchronized (B){
log.debug("t1 线程获取了 B锁");
}
}
},"t1").start();
new Thread(() -> {
synchronized (B){
log.debug("t2 线程获取了 B锁");
synchronized (A){
log.debug("t2 线程获取了 A锁");
}
}
},"t2").start();
}
}
如何查看死锁
可以使用 jps 查看这个进程号, 然后使用jstack 进程号,查看死锁的信息。
死锁 - 哲学家就餐问题
筷子类
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
哲学家类
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
// 卡在这里, 不向下运行
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
// 获得左手筷子
synchronized (left) {
// 获得右手筷子
synchronized (right) {
// 吃饭
eat();
}
// 放下右手筷子
}
// 放下左手筷子
}
}
}
测试类:
public class JiuCanTset {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
活锁
活锁,两个线程互相改变对方的结束条件,导致最后谁也无法结束。
@Slf4j
public class ActiveLockTest {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}
饥饿
加锁的解决方案
ReentrantLock
ReentrantLock 相较于 synchronized 它有一下的几个特点
-
可以中断
-
可以设置超时时间
-
可以设置为公平锁
-
支持多个条件变量
可重入
ReentrantLock 支持可重入
/**
* 可重入
*/
@Slf4j
public class ReentrantLockDemo {
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1(){
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2(){
lock.lock();
try {
log.debug("execute method1");
method3();
} finally {
lock.unlock();
}
}
public static void method3(){
lock.lock();
try {
log.debug("execute method3");
}finally {
lock.unlock();
}
}
}
可打断
/**
* 可打断
*/
@Slf4j
public class ReentrantLockInterruptDemo {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
//lock.lockInterruptibly(); 证明我这个锁是可以被打断的
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("t1线程获得了锁");
} finally {
log.debug("t1线程释放了锁");
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("主线程获得了锁");
t1.start();
try {
Thread.sleep(1000);
t1.interrupt();
log.debug("执行打断");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
锁超时
/**
* 锁超时
*/
@Slf4j
public class ReentrantLockTimeOutDemo {
static ReentrantLock lock = new ReentrantLock();
//立刻失败
/* public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("t1线程启动......");
if (!lock.tryLock()){
log.debug("t1线程尝试获取锁失败.....");
return;
}
try {
log.debug("获得了锁");
}finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("主线程获得了锁");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}*/
//超时失败
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("t1启动...");
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("t1获取等待 1s 后失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("t1获得了锁");
} finally {
lock.unlock();
log.debug("t1释放了锁");
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
顺序加载
wait / notify 实现
/**
* 按照指定的顺序进行打印
*/
@Slf4j
public class QueueDaYiTest {
static Object lock = new Object();
static boolean t2runed = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock){
while(!t2runed){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("1");
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (lock){
log.debug("2");
t2runed = true;
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
park & unpark 方式实现
/**
* 按照指定的顺序进行打印
*/
@Slf4j
public class QueueDaYiTest02 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
LockSupport.park();
log.debug("1");
}, "t1");
Thread t2 = new Thread(() -> {
log.debug("2");
LockSupport.unpark(t1);
},"t2");
t2.start();
t1.start();
}
}
交替输出
方式1
/**
* 交替输出
*/
public class SwapPrintDemo {
public static void main(String[] args) {
WaitNotify wn = new WaitNotify(1,5);
new Thread(() -> {
wn.print("a",1,2);
},"t1").start();
new Thread(() -> {
wn.print("b",2,3);
},"t2").start();
new Thread(() -> {
wn.print("c",3,1);
},"t3").start();
}
}
class WaitNotify{
private int flag;
private int loopNumber;
public WaitNotify(int flag, int loopNumber) {
this.flag = flag;
this.loopNumber = loopNumber;
}
public WaitNotify() {
}
public void print(String str, int waitFlag, int nextFlag){
for (int i=0;i<loopNumber;i++){
synchronized (this){
while(flag != waitFlag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(str);
flag = nextFlag;
this.notifyAll();
}
}
}
}
方式2:
/**
* 交替输出 使用lock的方式
*/
public class SwapPrintDemo02 {
public static void main(String[] args) {
AwaitSingle awaitSingle = new AwaitSingle(5);
Condition a = awaitSingle.newCondition();
Condition b = awaitSingle.newCondition();
Condition c = awaitSingle.newCondition();
new Thread(() -> {
awaitSingle.print("a",a,b);
},"t1").start();
new Thread(() -> {
awaitSingle.print("b",b,c);
},"t2").start();
new Thread(() -> {
awaitSingle.print("c",c,a);
},"t3").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
awaitSingle.lock();
try {
a.signal();
}finally {
awaitSingle.unlock();
}
}
}
class AwaitSingle extends ReentrantLock {
private int loopNumber;
public AwaitSingle(){}
public AwaitSingle(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str, Condition current,Condition next){
for (int i=0;i<loopNumber;i++){
lock();
try {
try {
current.await();
System.out.println(str);
next.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
unlock();
}
}
}
}
方式3
/**
* 交替输出 使用lock的方式
*/
public class SwapPrintDemo03 {
static Thread t1;
static Thread t2;
static Thread t3;
public static void main(String[] args) {
ParkUnpark pu = new ParkUnpark(5);
t1 = new Thread(() -> {
pu.print("a",t2);
});
t2 = new Thread(() -> {
pu.print("b",t3);
});
t3 = new Thread(() -> {
pu.print("c",t1);
});
t1.start();
t2.start();
t3.start();
//要在主线程里面唤醒t1这个线程
LockSupport.unpark(t1);
}
}
class ParkUnpark{
private int loopNumber;
public ParkUnpark() {
}
public ParkUnpark(int loopNumber) {
this.loopNumber = loopNumber;
}
public void print(String str,Thread next){
for (int i = 0;i<loopNumber;i++){
LockSupport.park();
System.out.println(str);
LockSupport.unpark(next);
}
}
}
Java 内存模型
JMM:JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。
JMM体现的几个方面:
-
原子性 - 保证指令不会受到线程上下文切换的影响
-
可见性 - 保证指令不会受cpu缓存的影响
-
有序性 - 保证指令不会受 cpu 指令并行优化的影响
可见性
运行以下代码,会发现问题,这个程序停不下来,但是这个 run 已经被改成false了。
/**
* 可见性
*/
@Slf4j
public class CanFindTest {
static boolean run = true;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (run) {
// ....
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
run = false;
}
}
原因分析:
- 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
- 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中, 减少对主存中 run 的访问,提高效率
- 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量 的值,结果永远是旧值
解决办法,可以使用 volatile 这个关键字。
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取 它的值,线程操作 volatile 变量都是直接操作主存
有序性
保证字节码执行的顺序
可以使用 Volatile 禁止指令重排
CAS
使用CAS的方式结局资源共享问题。
public class CAS01Test {
public static void main(String[] args) {
Account account = new AccountCas(10000);
Account.demo(account);
Account account2 = new AccountCas(10000);
Account.demo(account2);
}
}
class AccountCas implements Account{
private AtomicInteger balance;
public AccountCas() {}
public AccountCas(int balance) {
this.balance = new AtomicInteger(balance);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while(true){
//获取余额的最新之
int prev = balance.get();
//要修改的余额
int next = prev - amount;
//真正修改
if (balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface Account{
public Integer getBalance();
public void withdraw(Integer amount);
static void demo(Account account) {
List<Thread> ts = new ArrayList<>();
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(10);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()
+ " cost: " + (end-start)/1000_000 + " ms");
}
}
CAS的工作方式
Cas的特点
原子整数
-
AtomicBoolean
-
AtomicInteger
-
AtomicLong
AtomicInteger
AtomicInteger i = new AtomicInteger(0);
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
原子引用
为什么需要原子引用类型?
-
AtomicReference
-
AtomicMarkableReference
-
AtomicStampedReference
public class YuanZiTest {
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));
DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));
}
}
interface DecimalAccount {
// 获取余额
BigDecimal getBalance();
// 取款
void withdraw(BigDecimal amount);
/**
* 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
* 如果初始余额为 10000 那么正确的结果应当是 0
*/
static void demo(DecimalAccount account) {
List<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(() -> {
account.withdraw(BigDecimal.TEN);
}));
}
ts.forEach(Thread::start);
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(account.getBalance());
}
}
//不安全实现
class DecimalAccountUnsafe implements DecimalAccount{
BigDecimal balance;
public DecimalAccountUnsafe(BigDecimal balance) {
this.balance = balance;
}
@Override
public BigDecimal getBalance() {
return balance;
}
@Override
public void withdraw(BigDecimal amount) {
BigDecimal balance = this.getBalance();
this.balance = balance.subtract(amount);
}
}
//使用锁进行安全实现
class DecimalAccountSafeLock implements DecimalAccount{
public static final Object lock = new Object();
BigDecimal balance;
public DecimalAccountSafeLock(BigDecimal balance) {
this.balance = balance;
}
@Override
public BigDecimal getBalance() {
return balance;
}
@Override
public void withdraw(BigDecimal amount) {
synchronized (lock){
BigDecimal balance = this.getBalance();
this.balance = balance.subtract(amount);
}
}
}
// 使用cas的方式进行安全实现
class DecimalAccountSafeCas implements DecimalAccount {
AtomicReference<BigDecimal> ref;
public DecimalAccountSafeCas(BigDecimal balance) {
ref = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return ref.get();
}
@Override
public void withdraw(BigDecimal amount) {
while(true){
BigDecimal prev = ref.get();
BigDecimal next = prev.subtract(amount);
if (ref.compareAndSet(prev,next)){
break;
}
}
}
}
ABA 问题
/**
* ABA
*/
@Slf4j
public class ABATest {
static AtomicReference<String> ref = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
// 获取值 A
// 这个共享变量被它线程修改过?
String prev = ref.get();
other();
Thread.sleep(1000);
// 尝试改为 C
log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
}
public static void other(){
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
}, "t1").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
}, "t2").start();
}
}
这个时候主线程只能够感知到共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况。如果想要主线程感知到,则需要添加一个版本号。代码修改如下:
/**
* ABA
*/
@Slf4j
public class ABATest02 {
static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
// 获取值 A
String prev = ref.getReference();
// 获取版本号
int stamp = ref.getStamp();
log.debug("版本 {}", stamp);
// 如果中间有其它线程干扰,发生了 ABA 现象
other();
Thread.sleep(1000);
// 尝试改为 C
log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() {
new Thread(() -> {
log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本为 {}", ref.getStamp());
}, "t1").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
ref.getStamp(), ref.getStamp() + 1));
log.debug("更新版本为 {}", ref.getStamp());
}, "t2").start();
}
}
字段更新器
-
AtomicReferenceFieldUpdater // 域 字段
-
AtomicIntegerFieldUpdater
-
AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 异常
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
public class UpdateDemo {
private volatile int field;
public static void main(String[] args) {
AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(UpdateDemo.class, "field");
UpdateDemo test5 = new UpdateDemo();
fieldUpdater.compareAndSet(test5, 0, 10);
// 修改成功 field = 10
System.out.println(test5.field);
// 修改成功 field = 20
fieldUpdater.compareAndSet(test5, 10, 20);
System.out.println(test5.field);
// 修改失败 field = 20
fieldUpdater.compareAndSet(test5, 10, 30);
System.out.println(test5.field);
}
}
使用unsafe为对象赋值
@Slf4j
public class UnsafeTest01 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//1、获取Unsafe的反射对象
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
System.out.println(unsafe);
//2、获取要赋值对象的偏移
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
Teacher t = new Teacher();
//3、为对象进行赋值操作。
unsafe.compareAndSwapInt(t, idOffset,0,1);
unsafe.compareAndSwapObject(t,nameOffset,null,"张三");
System.out.println(t);
}
}
@Data
class Teacher{
volatile int id;
volatile String name;
}
共享模型之不可变
使用不可变性解决线程安全问题
/**
* ctrl + alt + m 快速抽取方法
*/
@Slf4j
public class SimpleDateFormatTest {
public static void main(String[] args) {
DateTimeFormatter stf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TemporalAccessor parse = stf.parse("1951-04-21");
log.debug("{}",parse);
}).start();
}
}
private static void test() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (sdf){
try {
log.debug("{}", sdf.parse("1951-04-21"));
} catch (Exception e) {
log.error("{}", e);
}
}
}).start();
}
}
}
享元模式
线程池
自定义线程池
@Slf4j
public class MyThreadPoolDemo {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10,(queue,task)->{
queue.put(task);
});
for (int i=0;i<5;i++){
int j = i;
threadPool.execute(() -> {
log.debug("{}",j);
});
}
}
}
@Slf4j
class ThreadPool{
// 任务队列
private BlockingQueue<Runnable> taskQueue;
// 线程集合
private HashSet<Worker> workers = new HashSet();
// 核心线程数
private int coreSize;
// 获取任务的超时时间
private long timeout;
private TimeUnit timeUnit;
// 拒绝策略
private RejectPolicy rejectPolicy;
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit,int queueCapcity,RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapcity);
this.rejectPolicy = rejectPolicy;
}
public void execute(Runnable task){
//当前任务数量没有超过coreSize核心数量,直接交给worker执行
//如果任务数量超过coreSize时候,加入到阻塞队列
synchronized (workers){
if (workers.size() < coreSize){
Worker worker = new Worker(task);
log.debug("新增 worker{}, {}", worker, task);
workers.add(worker);
worker.start();
}else{
//taskQueue.put(task);
//1、死等
//2、带超时的等待
//3、让调用者放弃任务执行
//4、让调用者抛出异常
//5、让调用者自己执行任务
taskQueue.tryPut(rejectPolicy,task);
}
}
}
class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
//执行任务
//当task不为空,执行任务
//当task执行完毕,再接着从任务队列获取任务并执行
while(task!=null || (task = taskQueue.take()) == null){
try {
log.debug("正在执行...{}", task);
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
synchronized (workers){
log.debug("worker 被移除{}", this);
workers.remove(this);
}
}
}
}
@Slf4j
class BlockingQueue<T>{
//1、任务队列
private Deque<T> queue = new ArrayDeque<T>();
//2、定义锁
ReentrantLock lock = new ReentrantLock();
//3、生产者条件变量
private Condition fullWaitSet = lock.newCondition();
//4、消费者变量
private Condition emptyWaitSet = lock.newCondition();
//5、容量
private int capcity;
public BlockingQueue(int queueCapcity) {
this.capcity = queueCapcity;
}
//带超时的阻塞获取
public T poll(long timeout, TimeUnit unit){
lock.lock();
try {
//将timeout统一转换为纳秒
long nanos = unit.toNanos(timeout);
while(queue.isEmpty()){
try {
//返回等待的剩余时间
if (nanos<=0){
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
//获取队列中的元素
public T take(){
lock.lock();
try {
while(queue.isEmpty()){
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
// 阻塞添加
public void put(T element){
lock.lock();
try {
while(queue.size()>=this.capcity){
try {
log.debug("等待加入任务队列 {} ...", element);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入任务队列 {}", element);
queue.addLast(element);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
// 带超时的阻塞添加
public boolean offer(T task , long timeout , TimeUnit timeUnit){
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while(queue.size() == capcity){
try {
log.debug("等待加入到任务队列:{}",task);
if (nanos<=0){
return false;
}
nanos = fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("加入到任务队列{}",task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
}finally {
lock.unlock();
}
}
// 获取大小
public int size(){
lock.lock();
try {
return queue.size();
}finally {
lock.unlock();
}
}
// 策略
public void tryPut(RejectPolicy rejectPolicy, T task) {
lock.lock();
try {
if (queue.size() == capcity){
rejectPolicy.reject(this,task);
queue.addLast(task);
emptyWaitSet.signal();
}
}finally {
lock.unlock();
}
}
}
@FunctionalInterface
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue,T task);
}
ThreadPoolExecutor
// c 为旧值, ctlOf 返回结果为新值 ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们 private static int ctlOf(int rs, int wc) { return rs | wc; }
构造方法
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
-
corePoolSize 核心线程数目 (最多保留的线程数)
-
maximumPoolSize 最大线程数目
-
keepAliveTime 生存时间 - 针对救急线程
-
unit 时间单位 - 针对救急线程
-
workQueue 阻塞队列
-
threadFactory 线程工厂 - 可以为线程创建时起个好名字
-
handler 拒绝策略
线程的工作方式:
newFixedThreadPool
固定线程池
newCachedThreadPool
带缓存的线程池
newSingleThreadExecutor
单个线程池
任务提交
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
7) 关闭线程池
shutdown
shutdownNow
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
关闭线程池
fork/Join
Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型 运算 所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计 算,如归并排序、斐波那契数列、都可以用分治思想进行求解 Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运 算效率 Fork/Join 默认会创建与 cpu 核心数大小相同的线程池
提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下 面定义了一个对 1~n 之间的整数求和的任务
/**
* fork/join
*/
public class ForkJoinTest {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
Integer invoke = pool.invoke(new MyTask(5));
System.out.println(invoke);
}
}
class MyTask extends RecursiveTask<Integer>{
private Integer n=1;
public MyTask(Integer n) {
this.n = n;
}
@Override
protected Integer compute() {
if (n == 1){
return 1;
}
MyTask t1 = new MyTask(n - 1);
t1.fork();
int result = n+ t1.join();
return result;
}
}
AQS
全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
获取锁
// 如果获取锁失败
if (!tryAcquire(arg))
{
// 入队, 可以选择阻塞当前线程 park unpark
}
释放锁
// 如果释放锁成功
if (tryRelease(arg))
{
// 让阻塞线程恢复运行
}
自定义锁
/**
* Aqs
*/
@Slf4j
public class TestAqs {
public static void main(String[] args) {
MyLock lock = new MyLock();
// ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
log.debug("locking...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
log.debug("unlocking...");
lock.unlock();
}
},"t1").start();
}
}
//自定义锁
class MyLock implements Lock{
static MySync sync = new MySync();
//独占锁
static final class MySync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int acquires) {
if (acquires == 1){
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}
@Override
protected boolean tryRelease(int acquires) {
if(acquires == 1) {
if(getState() == 0) {
throw new IllegalMonitorStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
return false;
}
//持有独占锁
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
@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();
}
}
读写锁
/**
* 读写锁
* 读 - 读 不互斥
* 读 - 写 互斥
* 写 - 写 互斥
*
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
DataContainer dataContainer = new DataContainer();
new Thread(() -> {
dataContainer.read();
}, "t1").start();
/* new Thread(() -> {
dataContainer.read();
},"t2").start();*/
new Thread(() -> {
dataContainer.write();
},"t2").start();
}
}
@Slf4j
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("读取");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return data;
}finally {
log.debug("释放读锁....");
r.unlock();
}
}
public void write(){
log.debug("获取写锁....");
w.lock();
try {
log.debug("写入");
}finally {
log.debug("释放写锁....");
w.unlock();
}
}
}
StampedLock
该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
加读锁
long stamp = lock.readLock();
lock.unlockRead(stamp);
加写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
支持乐观读
StampedLock 支持 tryOptimisticRead() 方法(乐观读),读取完毕后需要做一次 戳校验 如果校验通 过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。
long stamp = lock.tryOptimisticRead();
// 验戳
if(!lock.validate(stamp)){ // 锁升级 }
演示案例
/**
* 该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用
*/
public class StampedLockDemo {
public static void main(String[] args) {
DataContainerStamped dataContainer = new DataContainerStamped(1);
new Thread(() -> {
dataContainer.read(1);
}, "t1").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
dataContainer.read(0);
}, "t2").start();
}
}
@Slf4j
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);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
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);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
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 {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = newData;
} finally {
log.debug("write unlock {}", stamp);
lock.unlockWrite(stamp);
}
}
}
-
StampedLock 不支持条件变量
-
StampedLock 不支持可重入
Semaphore
Semaphore ,用来限制能同时访问共享资源的线程上限。
演示案例
/**
* Semaphore ,用来限制能同时访问共享资源的线程上限。
*/
@Slf4j
public class SemaphoreDemo {
public static void main(String[] args) {
// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
int j = i;
new Thread(() -> {
// 3. 获取许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("running..." + j);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("end..." + j);
} finally {
// 4. 释放许可
semaphore.release();
}
}).start();
}
}
}
CountdownLatch
倒计时锁:
用来进行线程同步协作,等待所有线程完成倒计时。 其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一。
CyclicBarrier
循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执 行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。
/**
*循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执
*行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行
*/
@Slf4j
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行
new Thread(()->{
System.out.println("线程1开始.."+new Date());
try {
cb.await(); // 当个数不足时,等待
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程1继续向下运行..."+new Date());
}).start();
new Thread(()->{
System.out.println("线程2开始.."+new Date());
try { Thread.sleep(2000); } catch (InterruptedException e) { }
try {
cb.await(); // 2 秒后,线程个数够2,继续运行
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("线程2继续向下运行..."+new Date());
}).start();
}
}