Java‘锁’事
1、乐观锁和悲观锁
1.1、悲观锁
1.2、乐观锁
2、8种情况演示锁运行案例
2.1、1-2案例演示
class phone {
public synchronized void sendEmail ( ) {
try {
TimeUnit . SECONDS. sleep ( 3 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
System . out. println ( "------sendEmail" ) ;
}
public synchronized void sendMsg ( ) {
System . out. println ( "------sendMsg" ) ;
}
public void hello ( ) {
System . out. println ( "----------hello" ) ;
}
}
public class LockBDemo {
public static void main ( String [ ] args) {
phone phone = new phone ( ) ;
phone phone2 = new phone ( ) ;
new Thread ( ( ) -> {
phone. sendEmail ( ) ;
} , "A" ) . start ( ) ;
new Thread ( ( ) -> {
phone. sendMsg ( ) ;
} , "B" ) . start ( ) ;
}
}
2.2、3-4案例演示
class phone {
public synchronized void sendEmail ( ) {
try {
TimeUnit . SECONDS. sleep ( 3 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
System . out. println ( "------sendEmail" ) ;
}
public synchronized void sendMsg ( ) {
System . out. println ( "------sendMsg" ) ;
}
public void hello ( ) {
System . out. println ( "----------hello" ) ;
}
}
public class LockBDemo {
public static void main ( String [ ] args) {
phone phone = new phone ( ) ;
phone phone2 = new phone ( ) ;
new Thread ( ( ) -> {
phone. sendEmail ( ) ;
} , "A" ) . start ( ) ;
new Thread ( ( ) -> {
phone2. sendMsg ( ) ;
phone. hello ( ) ;
} , "B" ) . start ( ) ;
}
}
2.3、5-6案例演示
class phone {
public static synchronized void sendEmail ( ) {
try {
TimeUnit . SECONDS. sleep ( 3 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
System . out. println ( "------sendEmail" ) ;
}
public static synchronized void sendMsg ( ) {
System . out. println ( "------sendMsg" ) ;
}
public void hello ( ) {
System . out. println ( "----------hello" ) ;
}
}
public class LockBDemo {
public static void main ( String [ ] args) {
phone phone = new phone ( ) ;
phone phone2 = new phone ( ) ;
new Thread ( ( ) -> {
phone. sendEmail ( ) ;
} , "A" ) . start ( ) ;
new Thread ( ( ) -> {
phone2. sendMsg ( ) ;
phone. sendMsg ( ) ;
} , "B" ) . start ( ) ;
}
}
2.4、7-8案例演示
class phone {
public static synchronized void sendEmail ( ) {
try {
TimeUnit . SECONDS. sleep ( 3 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
System . out. println ( "------sendEmail" ) ;
}
public synchronized void sendMsg ( ) {
System . out. println ( "------sendMsg" ) ;
}
public void hello ( ) {
System . out. println ( "----------hello" ) ;
}
}
public class LockBDemo {
public static void main ( String [ ] args) {
phone phone = new phone ( ) ;
phone phone2 = new phone ( ) ;
new Thread ( ( ) -> {
phone. sendEmail ( ) ;
} , "A" ) . start ( ) ;
new Thread ( ( ) -> {
phone2. sendMsg ( ) ;
phone. sendMsg ( ) ;
} , "B" ) . start ( ) ;
}
}
3、公平锁和非公平锁
3.1、公平锁
3.1.1、概述
公平锁是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人排队。这就是公平锁ReentrantLock lock = new ReentrantLock(true)
3.2、非公平锁
3.2.1、概述
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿状态(某个线程一直得不到锁)。这就是非公平锁ReentrantLock lock = new ReentrantLock()
3.3、代码演示
class Ticket {
private int number = 50 ;
ReentrantLock lock = new ReentrantLock ( ) ;
public void sale ( ) {
lock. lock ( ) ;
try {
if ( number > 0 ) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "卖出第 : \t" + ( number -- ) ) ;
}
} finally {
lock. unlock ( ) ;
}
}
}
public class SaleTicketDome {
public static void main ( String [ ] args) {
Ticket ticket = new Ticket ( ) ;
new Thread ( ( ) -> {
for ( int i = 0 ; i < 55 ; i++ ) {
ticket. sale ( ) ;
}
} , "t1" ) . start ( ) ;
new Thread ( ( ) -> {
for ( int i = 0 ; i < 55 ; i++ ) {
ticket. sale ( ) ;
}
} , "t2" ) . start ( ) ;
new Thread ( ( ) -> {
for ( int i = 0 ; i < 55 ; i++ ) {
ticket. sale ( ) ;
}
} , "t3" ) . start ( ) ;
}
}
3.4 问题
为什么默认是非公平锁
恢复挂起的线程到真正获得锁是有时间差的,从开发人员看这时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显,所以非公平锁更充分的利用CPU的时间片,尽量减少CPU空闲状态时间 使用多线程很重要的考量点是线程切换的开销,当采用公平锁时,当一个线程请求锁获取同步状态,然后释放到同步状态,所以港释放锁的线程在此刻两次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
4、可重入锁(递归锁)
4.1、概述
可重入锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象是同一个对象)不会因为之前已经获取还没有释放而阻塞 ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是一定程度避免死锁。
4.2、可重入锁的种类
4.2.1、synchronized隐式可重入锁
4.2.1.1、synchronized同步代码块及同步方法演示
public class ReEntryLockDemo {
public static void main ( String [ ] args) {
final Object o = new Object ( ) ;
new Thread ( ( ) -> {
synchronized ( o) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----外层调用" ) ;
synchronized ( o) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----中层调用" ) ;
synchronized ( o) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----内层调用" ) ;
}
}
}
} , "t1" ) . start ( ) ;
}
}
public synchronized void m1 ( ) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----m1()" ) ;
m2 ( ) ;
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----经过m2" ) ;
}
private synchronized void m2 ( ) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----m2()" ) ;
m3 ( ) ;
}
private synchronized void m3 ( ) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----m3()" ) ;
}
public static void main ( String [ ] args) {
ReEntryLockDemo lockDemo = new ReEntryLockDemo ( ) ;
new Thread ( ( ) -> {
lockDemo. m1 ( ) ;
} , "t1" ) . start ( ) ;
}
4.2.2.2、synchronized可重入锁的实现机制
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1 在目标锁对象的暑假前不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放锁 当monitorexit时,Java虚拟机则需要将锁对象的计数器减1,计数器为零代表锁已被释放。
4.2.2、reentrantLock显式可重入锁
4.2.1.1、reentrantLock演示
public class ReEntryLock {
public static void main ( String [ ] args) {
final ReentrantLock lock = new ReentrantLock ( ) ;
new Thread ( ( ) -> {
lock. lock ( ) ;
try {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----外层调用" ) ;
lock. lock ( ) ;
try {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----中层调用" ) ;
} finally {
lock. unlock ( ) ;
}
} finally {
lock. unlock ( ) ;
}
} , "t1" ) . start ( ) ;
new Thread ( ( ) -> {
lock. lock ( ) ;
try {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t -----外层调用" ) ;
} finally {
lock. unlock ( ) ;
}
} , "t2" ) . start ( ) ;
}
}
5、死锁及排查
5.1、概述
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉它们都将无法推进下去。
5.2、死锁代码演示
public class deadLock {
public static void main ( String [ ] args) {
final Object o = new Object ( ) ;
final Object o1 = new Object ( ) ;
new Thread ( ( ) -> {
synchronized ( o) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t" + "o进来了" ) ;
try {
Thread . sleep ( 100 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
synchronized ( o1) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t" + "o1进来了" ) ;
}
}
} , "t1" ) . start ( ) ;
new Thread ( ( ) -> {
synchronized ( o1) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t" + "o1进来了" ) ;
try {
Thread . sleep ( 100 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
synchronized ( o) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + "\t" + "o进来了" ) ;
}
}
} , "t2" ) . start ( ) ;
}
}
5.2.1、死锁查看方式(命令方法)
jps -l jstack 进程编号
6、写锁(独占锁)/读锁(共享锁)
7、自旋锁SpinLock
8、无锁->独占锁->读写锁->邮戳锁
9、无锁->偏向锁->轻量锁->重量锁