Java的各种锁 公平锁 不公平锁 公平锁:非常公平,不能插队,必须先来后到。
不公平锁:非常不公平,可以插队。(默认都是非公平锁)
ReentrantLock下的锁
1 2 3 public ReentrantLock () { sync = new NonfairSync (); }
类ReentrantLock创建的锁默认是非公平锁。
1 2 3 public ReentrantLock (boolean fair) { sync = fair ? new FairSync () : new NonfairSync (); }
可以通过改变参数改为公平锁。
可重入锁 所有的锁都是可重入锁
可重入锁又称递归锁。是指同一个线程在外层方法获取锁的时候,再进入线程的内层方法会自动获取锁(前提:锁的对象是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞。
可重入锁指的是某个线程已经获得某个锁,可以再次获取而不会出现死锁。
synchronized的可重入锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Demo36_ReentrantLock { public static void main (String[] args) { Phone5 phone5 = new Phone5 (); new Thread (()->{ phone5.sendSms(); },"A" ).start(); new Thread (()->{ phone5.sendSms(); },"B" ).start(); } } class Phone5 { public synchronized void sendSms () { System.out.println(Thread.currentThread().getName()+" Sms" ); call(); } public synchronized void call () { System.out.println(Thread.currentThread().getName()+" Call" ); } }
在代码中,线程A使用了phone的同步方法sendSms()
,锁住了对象phone,然后sendSms()
方法调用了 同步方法call(),此时本应等待对象phone解锁后才能执行,因为 可重入锁 的原因,该操作继续执行。
ReentrantLock
可重入锁可以记录同一线程的锁的个数。
一个线程(A)可以多次使用lock()获得锁,ReentrantLock
对象会记录这个线程(A)锁的数量,在全部锁被解锁之前,另一个线程(B)进行lock()会被阻塞,直到这个线程(A)将锁全部解锁。
使用lock()方法为当前线程获得一把锁并锁住这把锁,用unlock()解锁。lock()可以重复使用,使用几次,则获得几把锁,解锁时也需要解锁相应的次数(也就是相同数量的unlock())。
业务代码结束后,一定要解锁,以防下个线程不能执行业务代码。
API
API
说明
lock()
为当前线程获得一把锁并锁住这把锁。若ReentrantLock
已被另一个线程锁定,则进入阻塞状态,直到锁被完全解锁。同一线程可以多次使用,多次使用会获得多个锁。必须在同一线程中有相同数量的unlock()被执行才能完全解锁。
unlock()
解锁一把锁。在同一线程中,与lock()配对使用,使用lock()后可以使用unlock()。若锁并没有进行lock(),使用unlock()会抛出异常:java.lang.IllegalMonitorStateException
。
…
…
使用
正常使用的ReentrantLock
可重入锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Demo37_ReentrantLock 可重入锁 { public static void main (String[] args) { Phone6 phone = new Phone6 (); new Thread (()->{ System.out.println("线程A开始-----------------" ); phone.sendSms(); System.out.println("线程A结束-----------------" ); },"A" ).start(); new Thread (()->{ System.out.println("线程B开始-----------------" ); phone.sendSms(); System.out.println("线程B结束-----------------" ); },"B" ).start(); } } class Phone6 { Lock lock = new ReentrantLock (); public void sendSms () { lock.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(); } } }
执行结果:
1 2 3 4 5 6 7 8 9 10 11 线程A开始----------------- A Sms A Call 线程A结束----------------- 线程B开始----------------- B Sms B Call 线程B结束----------------- Process finished with exit code 0 //“线程结束”的提示输出可能会错乱,因为多线程导致顺序不同,因仅测试学习提示用,所以不是重点。
分析一个线程,这里分析线程A:
sendSms
()方法使用了lock.lock
()获得并锁住了一把锁,
到call()方法里,方法lock.lock
()为线程A再次获得了一把锁,然后进行业务代码,
之后lock.unlock
()解锁,
call()方法结束回到sendSms
()方法,lock.unlock
()再次解锁,
lock中线程A的锁被完全解锁。
缺少一个unlock()的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Demo37_ReentrantLock 可重入锁 { public static void main (String[] args) { Phone6 phone = new Phone6 (); new Thread (()->{ System.out.println("线程A开始-----------------" ); phone.sendSms(); System.out.println("线程A结束-----------------" ); },"A" ).start(); new Thread (()->{ System.out.println("线程B开始-----------------" ); phone.sendSms(); System.out.println("线程B结束-----------------" ); },"B" ).start(); } } class Phone6 { Lock lock = new ReentrantLock (); public void sendSms () { lock.lock(); try { System.out.println(Thread.currentThread().getName()+" Sms" ); call(); } catch (Exception e) { e.printStackTrace(); } finally { } } public void call () { lock.lock(); try { System.out.println(Thread.currentThread().getName()+" Call" ); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
输出结果:
1 2 3 4 5 6 7 线程A开始----------------- A Sms A Call 线程A结束----------------- 线程B开始----------------- //程序没有结束
在这个代码中去掉了sendSms
()方法中的lock.unlock
(),在线程A执行结束后lock并没有被完全解锁,
到线程B时,由于lock锁被锁定,线程被阻塞,暂时不能正常执行。
lock()和unlock()必须配对使用。
自旋锁 SpinLock
自旋锁与其他锁类似,不同的是让线程暂停运行的实现方式不同。
其他锁让线程暂停运行的方式是挂起 ,挂起后线程的资源会被切换,线程不会消耗CPU资源。
自旋锁让线程暂停运行的方式是无限循环 ,线程会消耗CPU时间。
特点
线程没有挂起,一旦线程获得锁,并且获得CPU时间,会立即执行,效率相对其他锁高;
线程自旋锁期间会消耗CPU时间,影响性能。
使用场景
锁被占用的时间很短,其他线程能较早地获得锁。
要求
自旋等待的时间必须有一定的限度,如果自旋超过了限定次数没有成功获得锁,就应当挂起线程
实现原理
例1:getAndAddInt()是一个自旋锁
1 2 3 4 5 6 7 8 public final int getAndAddInt (Object var1, long var2, int var4) { int var5; do { var5 = this .getIntVolatile(var1, var2); } while (!this .compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
例2:手动实现自旋锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Demo38_SpinLock 自旋锁 { public static void main (String[] args) { MySpinLock mySpinLock = new MySpinLock (); new Thread (()->{ mySpinLock.lock(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } mySpinLock.unlock(); },"A" ).start(); new Thread (()->{ mySpinLock.lock(); try { TimeUnit.SECONDS.sleep(1 ); } catch (InterruptedException e) { e.printStackTrace(); } mySpinLock.unlock(); },"B" ).start(); } } class MySpinLock { AtomicReference<Thread> atomicReference = new AtomicReference <>(); public void lock () { Thread thread = Thread.currentThread(); while (!atomicReference.compareAndSet(null ,thread)){ } System.out.println(Thread.currentThread().getName()+" ==> lock" ); } public void unlock () { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread,null ); System.out.println(Thread.currentThread().getName()+" ==> unlock" ); }
1 2 3 4 5 6 A ==> lock A ==> unlock B ==> lock B ==> unlock Process finished with exit code 0
死锁(如何排查死锁) 两个线程之间互相拥有对方需要的资源,且在得到需要的资源你之前不会释放已拥有的资源。
简介
如何排查死锁
先放一个死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Demo39_ 死锁1 { public static void main (String[] args) { String lockA = "lockA" ; String lockB = "lockB" ; new Thread (new MyThread2 (lockA,lockB),"T1" ).start(); new Thread (new MyThread2 (lockB,lockA),"T2 " ).start(); } } class MyThread2 implements Runnable { private String lockA; private String lockB; public MyThread2 (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); } } } }
排查死锁1 查看日志
1 2 3 T2 locklockB = >get lockAT1 locklockA = >get lockB
排查死锁2 查看堆栈信息
使用jps -l定位进程号
使用jstack 进程号
查看进程信息 ( 这里是jstack 11452
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 略... Java stack information for the threads listed above: =================================================== "T2 " : at com.yn.MyThread2.run(Demo39_死锁1. java:35 ) - waiting to lock <0x000000076c0149d0 > (a java.lang.String) - locked <0x000000076c014a08 > (a java.lang.String) at java.lang.Thread.run(Thread.java:748 ) "T1" : at com.yn.MyThread2.run(Demo39_死锁1. java:35 ) - waiting to lock <0x000000076c014a08 > (a java.lang.String) - locked <0x000000076c0149d0 > (a java.lang.String) at java.lang.Thread.run(Thread.java:748 ) Found 1 deadlock.
找到一个死锁,
T2等待0x000000076c0149d0 ,锁定了0x000000076c014a08 ;
T1等待0x000000076c014a08 ,锁定了0x000000076c0149d0 。
其他 Java查看堆栈信息
jps -l 查看进程列表
jstack 进程号 查看进程堆栈信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 2022-09-04 10:35:00 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode): //略... Found one Java-level deadlock: ============================= "T2 ": waiting to lock monitor 0x000000001cac0c28 (object 0x000000076c0149d0, a java.lang.String), which is held by "T1" "T1": waiting to lock monitor 0x000000001cac3408 (object 0x000000076c014a08, a java.lang.String), which is held by "T2 " Java stack information for the threads listed above: =================================================== "T2 ": at com.yn.MyThread2.run(Demo39_死锁1.java:35) - waiting to lock <0x000000076c0149d0> (a java.lang.String) - locked <0x000000076c014a08> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) "T1": at com.yn.MyThread2.run(Demo39_死锁1.java:35) - waiting to lock <0x000000076c014a08> (a java.lang.String) - locked <0x000000076c0149d0> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.