文章目录
1 可重入特性
1.1 可重入的含义及synchronized可重入特性演示
可重入的含义:
指的是同一个线程获得锁之后,再不释放锁的情况下,可以直接再次获取到该锁。
- synchronized为可重入锁验证demo:
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
/***
* 可重入特性: 指的是同一个线程获得锁之后,可以直接再次获取该锁
* 最常出现的场景: 递归
* synchronized为可重入锁验证demo
*/
@Slf4j
public class SynReentrantDemo {
public static void main(String[] args) {
Runnable sellTicket = new Runnable() {
@Override
public void run() {
synchronized (this) {
String name = Thread.currentThread().getName();
log.info("我是run,抢到锁的是{}", name);
test01();
} //正常来说走出临界区(这个括号)才会释放锁,但是再没走出之前,又进入test01,
//而test01需要和本方法一样的锁
//如果不可重入的话,就将出现死锁了-->即test01方法等着释放锁,而run方法又不会释放锁
//因此synchronized只有可以在不释放run方法的锁的情况下,又再次获得该锁才不会有问题
}
public void test01() {
synchronized (this) {
String name = Thread.currentThread().getName();
log.info("我是test01,抢到锁的是{}", name);
}
}
};
new Thread(sellTicket).start();
new Thread(sellTicket).start();
}
}
- 运行结果:
1.2 简单说一下synchronized可重入原理
首先应该知道synchronized锁的并不是同步代码块,而是锁对象关联的一个monitor对象(在java中每一个对象都会关联一个monitor对象),而这个对象里有一个变量叫recursions
— 中文是递归的意思(我想大概是因为递归的时候发生可重入的几率应该是最大的,所以才用这个当变量名的吧),其实可以将它简单理解为一个计数器。
以上面的栗子为例:
- (1)当线程1抢到run方法的执行权即抢到锁时,这个recursions的值就变为了1;
- (2)线程1接着运行并进入test01方法后,发现还是线程1且还是要this这把锁,就将recursions的值再+1;
- (3)当线程1,执行完test01方法时,recursions的值又-1
- (4)执行完run方法时recursions的值又-1,就变为了0,也就是表示线程1已经释放了this锁。
- (5)之后其他线程就可以继续抢this锁了。
当然ReentrantLock肯定也是可重入的,人家的名字翻译过来就是可重入锁☺☺☺ ,这里就不贴代码了。
2 synchronized不可中断特性 — interrupt和stop都不可中断
2.1 不可中断的含义及synchronized不可中断特性演示
不可中断的含义
: 第一个线程获得某把锁后,第二个线程也想要获得该锁,则它必须处于阻塞或等待状态。如果第一个线程不释放锁,那第二个线程就会一直阻塞或等待,不可被中断。
- synchronized不可中断性验证
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
/***
* 演示synchronized不可中断的思路
* 1.定义一个Runnable
* 2.在Runnable定义同步代码块
* 3.先开启一个线程来执行同步代码块,保证不退出同步代码块
* 4.后开启一个线程来执行同步代码块(阻塞状态)
* 5.停止第二个线程 --- 可以发现无法停止
*/
@Slf4j
public class UninterruptibleDemo {
private static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
// 1. 定义一个Runnable
Runnable run = () -> {
// 2.在Runnable定义同步代码块
synchronized (obj) {
try {
String name = Thread.currentThread().getName();
log.info("线程{}进入同步代码块", name);
Thread.sleep(888888); //保证不退出同步代码块
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 3. 先开启一个线程来执行同步代码块
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4. 后开启一个线程来执行同步代码块(阻塞状态)
Thread t2 = new Thread(run);
t2.start();
// 5.停止第二个线程
log.info("线程t2尝试停止线程前");
t2.interrupt();
//t2.stop(); //无论使用interrupt还是stop都不能把t2给中断
log.info("线程t2尝试停止线程后");
log.info("线程t1的状态:{}", t1.getState());
log.info("线程t2的状态:{}", t2.getState());
}
}
- 测试结果
2.2 ReentrantLock的可中断与不可中断
有没有感觉synchronized的不可中断机制其实是有一定问题的。比如说有一个线程抢了很久很久发现一直就是抢不到执行权,竟然还不能让它知难而退了 —> 这感觉像极了追女朋友,我追了很久很久很久,就是追不到,但是还不准许我不追了。。
—》 我想也许Doug Lea正是发现了这一点,所以在设计ReentrantLock时搞了个tryLock方法。
2.2.1 ReentrantLock使用lock()加锁 — interrupt不可中断,stop可中断
- 将2.1中的代码修改成使用ReentrantLock加锁:
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReentrantLockUninterruptibleDemo {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 1. 定义一个Runnable
Runnable run = () -> {
// 2.对方法进行加锁
lock.lock();
try {
String name = Thread.currentThread().getName();
log.info("线程{}进入同步代码块", name);
Thread.sleep(888888); //保证不退出同步代码块
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
};
// 3. 先开启一个线程来执行同步代码块
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4. 后开启一个线程来执行同步代码块(阻塞状态)
Thread t2 = new Thread(run);
t2.start();
// 5.停止第二个线程
log.info("线程t2尝试停止线程前");
t2.interrupt(); //使用interrupt不可中断
//t2.stop(); //使用stop可中断
log.info("线程t2尝试停止线程后");
log.info("线程t1的状态:{}", t1.getState());
log.info("线程t2的状态:{}", t2.getState());
}
}
- 使用Interrupt()方法尝试中断线程时的结果如下:
- 使用stop方法尝试中断线程时的结果如下:
这里需要再提醒一下,在并发编程时,是不推荐使用stop进行中断线程的,因为stop会释放掉线程所有的资源。 —》 更推荐的是使用interrupt方法 — 》 可以看一下我的另一篇文章《【并发编程】— interrupt、interrupted和isInterrupted使用详解》。
2.2.2 ReentrantLock使用tryLock()加锁 — interrupt和stop都可中断
- code
package com.nrsc.ch1.base.jmm.syn_study.texing;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReentrantLockCanInterruptibleDemo {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 1. 定义一个Runnable
Runnable run = () -> {
boolean flag = false;
try {
// 2.对方法进行加锁
flag = lock.tryLock();
String name = Thread.currentThread().getName();
if (flag) {
log.info("线程{}进入同步代码块", name);
Thread.sleep(888888); //保证不退出同步代码块
} else {
log.info("线程{}未抢到锁", name);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (flag) {
lock.unlock();
}
}
};
// 3. 先开启一个线程来执行同步代码块
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4. 后开启一个线程来执行同步代码块(阻塞状态)
Thread t2 = new Thread(run);
t2.start();
//等待5秒,确保已经抢了
Thread.sleep(5000);
log.info("线程t2尝试停止线程前");
t2.interrupt(); //使用interrupt不可中断
//t2.stop(); //使用stop可中断
log.info("线程t2尝试停止线程后");
log.info("线程t1的状态:{}", t1.getState());
log.info("线程t2的状态:{}", t2.getState());
}
}
- 测试结果
简单提一句
tryLock还可以指定在一定时间内尝试获取锁,如果获取不到,就不再获取了,这其实也可以避免追女朋友,追了很久很久很久,就是追不到,但是还不准许我不追的情况。
end