1 锁的八个问题演示
- 标准访问,先打印短信还是邮件
class Phone{
public synchronized void sendSMS() throws InterruptedException {
System.out.println("----------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
----------sendSMS
-----------sendEmail
- 停4秒在短信方法内,先打印短信还是邮件
class Phone{
public synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
----------sendSMS
-----------sendEmail
- 新增普通的hello方法,是先打短信还是hello
class Phone{
public synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.getHello();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
-------------getHello
----------sendSMS
- 现在有两部手机,先打印短信还是邮件
class Phone{
public synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
-----------sendEmail
----------sendSMS
5.两个静态同步方法,1部手机,先打印短信还是邮件
class Phone{
public static synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public static synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
----------sendSMS
-----------sendEmail
- 两个静态同步方法,2部手机,先打印短信还是邮件
class Phone{
public static synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public static synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
----------sendSMS
-----------sendEmail
- 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
class Phone{
public static synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果
-----------sendEmail
----------sendSMS
-
- 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
class Phone{
public static synchronized void sendSMS() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("----------sendSMS");
}
public synchronized void sendEmail(){
System.out.println("-----------sendEmail");
}
public void getHello(){
System.out.println("-------------getHello");
}
}
public class Lock_8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone.sendSMS();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendEmail();
} catch (Exception e) {
throw new RuntimeException(e);
}
},"BB").start();
}
}
- 运行结果:
-----------sendEmail
----------sendSMS
- 结论
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式。- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是synchonized括号里配置的对象
2 公平锁与非公平锁
在多线程编程中,锁(Lock)是一种用于控制多个线程对共享资源访问的同步机制。锁可以分为公平锁(Fair Lock)和非公平锁(Non-Fair Lock)两种类型,它们在实现方式和行为上有显著的区别。
2.1 公平锁(Fair Lock)
2.1.1 概念
公平锁是指多个线程按照请求锁的顺序依次获得锁,即先请求的线程先获得锁。公平锁遵循FIFO(先进先出)原则,确保每个线程都有公平的机会获得锁。
2.1.2 特点
- 公平性:确保每个线程按照请求锁的顺序获得锁,避免线程饥饿(Starvation)现象。
- 性能较低:由于需要维护请求队列并按顺序分配锁,公平锁的性能通常比非公平锁低。
- 适用场景:适用于需要严格保证线程执行顺序的场景,如某些实时系统或对公平性要求较高的应用。
2.1.3 Java中的实现
在Java中,ReentrantLock
类可以通过构造函数参数指定是否使用公平锁。
ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
2.2 非公平锁(Non-Fair Lock)
2.2.1 概念
非公平锁是指多个线程请求锁时,不保证按照请求的顺序获得锁。非公平锁允许线程插队,即一个新请求的线程可以抢占正在等待的线程的锁。
2.2.2 特点
- 非公平性:不保证线程按照请求的顺序获得锁,允许线程插队。
- 性能较高:由于不需要维护请求队列和按顺序分配锁,非公平锁的性能通常比公平锁高。
- 适用场景:适用于对性能要求较高且对线程执行顺序要求不严格的场景,如大多数并发应用。
2.2.3 Java中的实现
在Java中,ReentrantLock
类默认使用非公平锁。
ReentrantLock nonFairLock = new ReentrantLock(); // 创建非公平锁
或
ReentrantLock nonFairLock = new ReentrantLock(false); // 创建非公平锁
2.3 对比与选择
2.3.1 公平锁 vs 非公平锁
- 公平性:公平锁保证线程按照请求顺序获得锁,非公平锁不保证。
- 性能:公平锁性能较低,非公平锁性能较高。
- 适用场景:公平锁适用于需要严格保证线程执行顺序的场景,非公平锁适用于对性能要求较高且对线程执行顺序要求不严格的场景。
2.3.2 选择原则
- 性能优先:如果对性能要求较高,且对线程执行顺序要求不严格,选择非公平锁。
- 公平性优先:如果需要严格保证线程执行顺序,避免线程饥饿现象,选择公平锁。
3 可重入锁
可重入锁是一种允许同一个线程多次获取同一个锁的同步机制。可重入锁的主要特点是,当一个线程已经持有某个锁时,它可以再次获取该锁而不会被阻塞。这种特性避免了死锁的发生,因为线程可以多次进入同一个锁保护的代码块
3.1 可重入锁的概念
可重入锁允许同一个线程多次获取同一个锁,而不会导致死锁。每次获取锁时,锁的计数器会增加,每次释放锁时,计数器会减少。只有当计数器归零时,锁才会被完全释放。
3.2 使用 synchronized 实现可重入锁
- synchronized实现同步代码块
public class SyncLockDemo {
public static void main(String[] args) {
// synchronized
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(Thread.currentThread().getName() + " 外层");
synchronized (o){
System.out.println(Thread.currentThread().getName() + " 中层");
synchronized (o){
System.out.println(Thread.currentThread().getName() + " 内层");
}
}
}
},"t1").start();
}
}
- 运行结果:
t1 外层
t1 中层
t1 内层
- synchronized实现同步方法
public class SyncLockDemo {
public synchronized void add(){
add();
}
public static void main(String[] args) {
new SyncLockDemo().add();
}
}
- 运行结果:
3.3 使用Lock实现可重入锁
- Lock实现可重入锁
public class SyncLockDemo {
public static void main(String[] args) {
//Lock演示可重入锁
Lock lock = new ReentrantLock();
//创建线程
new Thread(()->{
try {
// 上锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " 外层");
try {
// 上锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " 内层");
} finally {
// 解锁
lock.unlock();
}
} finally {
// 解锁
lock.unlock();
}
},"t1").start();
}
}
- 运行结果:
t1 外层
t1 内层
- 注意:上锁
lock.lock()
和解锁lock.unlock()
一定要成对出现,千万不要忘记解锁。
4 死锁
4.1 死锁的概念
两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去
4.2 产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
4.3 验证是否死锁
在多线程编程中,死锁是一种常见的问题,它发生在两个或多个线程相互等待对方释放资源的情况下。为了验证是否发生了死锁,可以使用Java提供的工具 jps
和 jstack
。
4.3.1 jps
工具
jps
是Java Virtual Machine Process Status Tool的缩写,类似于Linux中的 ps -ef
命令,用于列出当前正在运行的Java进程。
- 使用方法
在命令行中输入以下命令:
jps -l
- 输出示例
47872
29048 com.yunyang.javacoder.sync.DeadLock
47872
和29048
是Java进程的PID(进程ID)。DeadLock
是Java进程的名称。
4.3.2 jstack
工具
jstack
是JVM自带的堆栈跟踪工具,用于生成Java虚拟机当前时刻的线程快照(thread dump)。通过分析线程快照,可以检查是否存在死锁。
- 使用方法
在命令行中输入以下命令:
jstack <PID>
其中 <PID>
是Java进程的PID,可以通过 jps
命令获取。
- 输出示例
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.152-b16 mixed mode):
"DestroyJavaVM" #24 prio=5 os_prio=0 tid=0x0000000002ee3800 nid=0x71c0 waiting
on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"B" #23 prio=5 os_prio=0 tid=0x0000000021811800 nid=0x94f4 waiting for monitor
entry [0x00000000220df000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.yunyang.javacoder.sync.DeadLock.lambda$main$1(DeadLock.java:41)
- waiting to lock <0x000000076baa67e0> (a java.lang.Object)
- locked <0x000000076baa67f0> (a java.lang.Object)
at com.yunyang.javacoder.sync.DeadLock$$Lambda$2/1419810764.run(Unknow
n Source)
at java.lang.Thread.run(Thread.java:748)
"A" #22 prio=5 os_prio=0 tid=0x0000000021836000 nid=0xd3d4 waiting for monitor
entry [0x0000000021fdf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.yunyang.javacoder.sync.DeadLock.lambda$main$0(DeadLock.java:27)
- waiting to lock <0x000000076baa67f0> (a java.lang.Object)
- locked <0x000000076baa67e0> (a java.lang.Object)
at com.yunyang.javacoder.sync.DeadLock$$Lambda$1/913190639.run(Unknown
Source)
at java.lang.Thread.run(Thread.java:748)
...(此处省略若干行)
===================================================
"B":
at com.yunyang.javacoder.sync.DeadLock.lambda$main$1(DeadLock.java:41)
- waiting to lock <0x000000076baa67e0> (a java.lang.Object)
- locked <0x000000076baa67f0> (a java.lang.Object)
at com.yunyang.javacoder.sync.DeadLock$$Lambda$2/1419810764.run(Unknow
n Source)
at java.lang.Thread.run(Thread.java:748)
"A":
at com.yunyang.javacoder.sync.DeadLock.lambda$main$0(DeadLock.java:27)
- waiting to lock <0x000000076baa67f0> (a java.lang.Object)
- locked <0x000000076baa67e0> (a java.lang.Object)
at com.yunyang.javacoder.sync.DeadLock$$Lambda$1/913190639.run(Unknown
Source)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
4.3.3 分析线程快照
- BLOCKED 状态:表示线程正在等待获取某个对象的锁。
- waiting to lock:表示线程正在等待获取某个对象的锁。
- locked:表示线程已经持有某个对象的锁。
如果发现两个或多个线程相互等待对方持有的锁,则可能发生了死锁。例如,在上面的输出示例中,A
等待 B
持有的锁,而 B
等待 A
持有的锁,这表明可能发生了死锁。
4.3.4 示例:死锁代码
以下是一个简单的死锁示例代码:
public class DeadLock {
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a) {
System.out.println(Thread.currentThread().getName() + " 持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (b){
System.out.println(Thread.currentThread().getName() + " 获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b) {
System.out.println(Thread.currentThread().getName() + " 持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (a){
System.out.println(Thread.currentThread().getName() + " 获取锁a");
}
}
},"B").start();
}
}
- 运行结果:
A 持有锁a,试图获取锁b
B 持有锁b,试图获取锁a
4.3.5 验证死锁
- 运行程序:运行上述死锁示例代码。
- 获取PID:使用
jps
命令获取Java进程的PID。 - 生成线程快照:使用
jstack <PID>
命令生成线程快照。 - 分析线程快照:检查线程快照中的
BLOCKED
状态和waiting to lock
信息,确认是否存在死锁。
4.3.6 总结
通过使用 jps
和 jstack
工具,可以方便地验证Java程序中是否存在死锁。jps
用于列出Java进程,jstack
用于生成线程快照,通过分析线程快照中的 BLOCKED
状态和 waiting to lock
信息,可以判断是否发生了死锁。
5 附录 思维导图
6 参考链接
【【尚硅谷】大厂必备技术之JUC并发编程】