文章目录
JUC并发编程—— 各种锁的理解
1、公平锁,非公平锁
公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权,必须先来后到。
//ReentrantLock(true)设置为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁则随机分配这种使用权,可以插队。有时候,一些线程的执行时间很长,如果其他线程需要锁的话就需要等待很长的时间,所以为了提供效率,我们一般使用非公平锁
//ReentrantLock()默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
2、可重入锁
可重入锁又叫递归锁,指同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。JDK 中基本都是可重入锁,避免死锁的发生。
synchronized 可重入锁
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"=> sms");
call();//内层方法
}
//内层方法的锁
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"=> call");
}
}
Lock 可重入锁
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
}
class Phone2{
Lock lock=new ReentrantLock();
public void sms(){
lock.lock(); //细节:这个是两把锁,分别是sms和call
//lock锁必须配对,加了几次就要释放几次,否则就会死锁在里面
try {
System.out.println(Thread.currentThread().getName()+"=> sms");
call();//这里也有一把锁
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "=> call");
}catch (Exception e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
3、自旋锁
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。尝试获取锁的线程不会立即阻塞,采用循环的方式尝试获取锁!减少上下文的切换!缺点会消耗CPU
自定义自旋锁
public class SpinlockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "-->myLock");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
//解锁
public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "-->myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
测试自旋锁
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
SpinLockDemo lockDemo = new SpinLockDemo();
new Thread(()->{
lockDemo.myLock();
try{
TimeUnit.SECONDS.sleep(5);
}catch (Exception e){
e.printStackTrace();
}finally {
lockDemo.myUnlock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lockDemo.myLock();
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}finally {
lockDemo.myUnlock();
}
},"T2").start();
}
}
线程T1先拿到锁,T2在自旋等待,直到T1释放了锁,T2才结束自旋,获取到锁。
4、死锁
死锁问题:多个线程互相拿着对方需要的资源,然后形成僵持。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能宝贝一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
死锁案例
import java.util.concurrent.TimeUnit;
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB){
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+ " lock:"+lockA + " => get" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+ " lock:"+lockB + " => get" + lockA);
}
}
}
}
运行结果:
线程T1已经占有了A锁,在等待B锁,线程T2已经占有了B锁,在等待A锁,两个线程互相僵持,导致程序运行阻塞。
如何排查死锁
使用 进行排查,
jps(Java Virtual Machine Process Status Tool)是 jdk 提供的一个查看当前java进程的小工具。
以上面死锁为例,进行排查:
1、使用 jps -l
定位进程号
2、使用jstack + 进程号
查看堆栈信息,用以分析线程情况