1、两种常见锁
synchronized和ReentrantLock的区别
- synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。ReentrantLock是Java并发包中提供的一个可重入的互斥锁。
- 可重入性:所谓的可重入性,就是可以支持一个线程对锁的重复获取,原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
- 公平锁/非公平锁:所谓公平锁,顾名思义,意指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次获得锁,排排队,不能插队;非公平锁则不同,当锁被释放时,等待中的线程均有机会获得锁。synchronized是非公平锁,ReentrantLock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。
sleep与wait区别
- 两者最主要的区别在于:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁 。两者都可以暂停线程的执⾏。
- Wait 通常被⽤于线程间交互/通信,sleep通常被⽤于暂停执⾏。
- wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(longtimeout)超时后线程会⾃动苏醒。
2、公平锁和非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
- 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
- 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
3、悲观锁和乐观锁
悲观锁:对线程安全问题持有悲观态度,认为竞争时刻会发生,因此在执行操作之前,会直接持有这个锁,然后进行操作,操作完释放锁。适用于写比较多的场景。
乐观锁:对线程安全问题持有乐观态度,认为操作期间,不会有其他线程操作此数据,不上锁,效率较高,适用于读比较多的场景。
4、死锁
多个线程同时被阻塞,它们中的⼀个或者全部都在等待某个资源被释放。由于线程被⽆限期地阻塞,因此程序不可能正常终⽌。
产⽣死锁必须具备以下四个条件
- 互斥条件:该资源任意⼀个时刻只由⼀个线程占⽤。
- 请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完毕后才释放资源。
- 循环等待条件:若⼲进程之间形成⼀种头尾相接的循环等待资源关系。
如何避免线程死锁
- 破坏互斥条件 :这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的(临界资源需要互斥访问)。
- 破坏请求与保持条件 :⼀次性申请所有的资源。
- 破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。破坏循环等待条件。
package com.mine.juc.deadlock;
public class DeadLock {
public static void main(String[] args) {
final Object obj1 = new Object();
final Object obj2 = new Object();
new Thread(new Runnable() {
public void run() {
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + ":获取锁1成功");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + ":获取锁2成功");
}
}
}
}, "线程1").start();
new Thread(new Runnable() {
public void run() {
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + ":获取锁2成功");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + ":获取锁1成功");
}
}
}
}, "线程2").start();
}
}
通过jdk自带工具查询是否有死锁(jps + jstack)
C:\Users\guofeng>jps -l
19604 jdk.jcmd/sun.tools.jps.Jps
28276 com.mine.juc.deadlock.DeadLock
14648
8764
C:\Users\guofeng>jstack 28276
2022-05-08 22:57:07
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b15 mixed mode):
"DestroyJavaVM" #12 prio=5 os_prio=0 tid=0x000000000335e000 nid=0x612c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"线程2" #11 prio=5 os_prio=0 tid=0x000000001b92b800 nid=0xd40 waiting for monitor entry [0x000000001c56f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.mine.juc.deadlock.DeadLock$2.run(DeadLock.java:42)
- waiting to lock <0x0000000780bb4c88> (a java.lang.Object)
- locked <0x0000000780bb4c98> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"线程1" #10 prio=5 os_prio=0 tid=0x000000001b92a800 nid=0x6204 waiting for monitor entry [0x000000001c46e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.mine.juc.deadlock.DeadLock$1.run(DeadLock.java:22)
- waiting to lock <0x0000000780bb4c98> (a java.lang.Object)
- locked <0x0000000780bb4c88> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001b914800 nid=0x5dc8 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001b894800 nid=0x668c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001a50e000 nid=0x5184 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001a50a800 nid=0x508 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001a4be000 nid=0x52a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001b878800 nid=0x5720 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001a499000 nid=0x4d0c in Object.wait() [0x000000001b80f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000780b08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x0000000780b08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000003451000 nid=0x552c in Object.wait() [0x000000001b70f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000780b06b50> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000780b06b50> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000000001a477800 nid=0x510 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000003377000 nid=0x133c runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000003378800 nid=0x4554 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000337a000 nid=0x2168 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000337b800 nid=0x314c runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001b917000 nid=0x4c2c waiting on condition
JNI global references: 17
Found one Java-level deadlock:
=============================
"线程2":
waiting to lock monitor 0x00000000034587f8 (object 0x0000000780bb4c88, a java.lang.Object),
which is held by "线程1"
"线程1":
waiting to lock monitor 0x0000000003459c98 (object 0x0000000780bb4c98, a java.lang.Object),
which is held by "线程2"
Java stack information for the threads listed above:
===================================================
"线程2":
at com.mine.juc.deadlock.DeadLock$2.run(DeadLock.java:42)
- waiting to lock <0x0000000780bb4c88> (a java.lang.Object)
- locked <0x0000000780bb4c98> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"线程1":
at com.mine.juc.deadlock.DeadLock$1.run(DeadLock.java:22)
- waiting to lock <0x0000000780bb4c98> (a java.lang.Object)
- locked <0x0000000780bb4c88> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
5、读写锁
独占锁和共享锁
- 独占锁:也叫排它锁,该锁只能由一个线程所持有,如果一个线程对它加上独占锁后,其他线程不能再对它加锁。
- 共享锁:可以被多个线程所持有,如果一个线程对它加上共享所以后,其他线程只能对它加共享锁,不能再加独占锁。
- 锁降级:独占锁可以降级为共享锁,获取独占锁的线程,可以在内部加共享锁,然后释放独占锁,把锁降级为共享锁。
- 锁升级:共享锁不能升级为独占锁,因为获取共享锁以后,不能再加独占锁。
读锁是独占锁,写锁是共享锁。
测试读锁是共享锁
结果:虽然主线程获取了读锁,但是其他线程的获取读锁还是正常获取,并执行。
package com.mine.juc.readwritelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadLockDemo {
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
final Lock readLock = readWriteLock.readLock();
// 读锁是共享锁
System.out.println("=====测试读锁是共享锁=====");
readLock.lock();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "执行");
readLock.unlock();
}
}, "读线程" + (i+1)).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("外层读锁释放");
readLock.unlock();
}
}
测试写锁是独占锁
结果:主线程获取了写锁,但是其他线程的获取读锁被阻塞等待,直到主线程释放了写锁,其他线程才能正常执行。
package com.mine.juc.readwritelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class WriteLockDemo {
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
final Lock writeLock = readWriteLock.writeLock();
// 写锁是独占锁
System.out.println("=====测试写锁是独占锁=====");
writeLock.lock();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "执行");
writeLock.unlock();
}
}, "写线程" + (i+1)).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("外层写锁释放");
writeLock.unlock();
}
}
测试写锁可以降级为读锁
结果:主线程首先获取写锁,然后获取读锁,获取成功。说明在一个线程中获取了写锁,还是可以获取读锁。
然后释放写锁,其他线程获取读锁的操作,都可以正常执行了,说明写锁释放后,主线程也变成读锁了,写锁降级读锁成功了。
package com.mine.juc.readwritelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class WriteToReadLock {
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
final Lock writeLock = readWriteLock.writeLock();
final Lock readLock = readWriteLock.readLock();
System.out.println("=====测试写锁可以降级为读锁=====");
System.out.println("获取写锁");
writeLock.lock();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
readLock.lock();
System.out.println(Thread.currentThread().getName() + "执行");
readLock.unlock();
}
}, "读线程" + (i+1)).start();
}
System.out.println("获取读锁");
readLock.lock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("释放写锁");
writeLock.unlock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("释放读锁");
readLock.unlock();
}
}
测试读锁可以不能升级为写锁
结果:不能升级写锁,获取读锁以后,主线程无法获取写锁,线程会阻塞。
package com.mine.juc.readwritelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadToWriteLock {
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
final Lock writeLock = readWriteLock.writeLock();
final Lock readLock = readWriteLock.readLock();
System.out.println("=====测试读锁是否可以升级为写锁=====");
System.out.println("获取读锁");
readLock.lock();
System.out.println("获取写锁");
writeLock.lock();
System.out.println("释放读锁");
readLock.unlock();
System.out.println("释放写锁");
writeLock.unlock();
}
}
测试如果一个线程获取了读锁,其他线程是否可以获取写锁
结果:主线程获取了读锁,在主线程释放读锁之前,子线程一直在等待状态,不能获取写锁。
package com.mine.juc.readwritelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadAndWriteLock {
public static void main(String[] args) {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
final Lock writeLock = readWriteLock.writeLock();
final Lock readLock = readWriteLock.readLock();
System.out.println("=====测试一个线程获取了读锁,其他线程是否可以获取写锁=====");
readLock.lock();
System.out.println("主线程获取读锁成功");
new Thread(new Runnable() {
public void run() {
System.out.println("子线程执行,尝试获取写锁");
writeLock.lock();
System.out.println("子线程获取写锁成功");
writeLock.unlock();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程释放读锁");
readLock.unlock();
}
}