synchronized关键字和Lock的实现类都是悲观锁
悲观锁
: 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。 乐观锁
:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作 CAS算法
,Java原子类中的递增操作就通过CAS自旋实现的CAS(Compare-and-Swap,即比较并替换)
算法实现package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;class Phone //资源类
{public synchronized void sendEmail(){System.out.println("-------sendEmail");}public synchronized void sendSMS(){System.out.println("-------sendSMS");}}/*** 标准访问有ab两个线程,请问先打印邮件还是短信* 结果:* -------sendEmail* -------sendSMS*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{phone.sendSMS();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;class Phone //资源类
{public synchronized void sendEmail(){//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("-------sendEmail");}public synchronized void sendSMS(){System.out.println("-------sendSMS");}}/*** sendEmail方法暂停3秒钟,请问先打印邮件还是短信* 结果:* -------sendEmail* -------sendSMS*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{phone.sendSMS();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;class Phone //资源类
{public synchronized void sendEmail(){//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("-------sendEmail");}public synchronized void sendSMS(){System.out.println("-------sendSMS");}public void hello(){System.out.println("-------hello");}}/*** 新增一个普通的hello方法,请问先打印邮件还是hello* 结果:* -------hello* 三秒后* -------sendEmail*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{
// phone.sendSMS();phone.hello();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;class Phone //资源类
{public synchronized void sendEmail(){//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("-------sendEmail");}public synchronized void sendSMS(){System.out.println("-------sendSMS");}public void hello(){System.out.println("-------hello");}}/*** 有两部手机,请问先打印邮件还是短信* 结果:* -------sendSMS* 三秒后* -------sendEmail*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{
// phone.sendSMS();
// phone.hello();phone2.sendSMS();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;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 sendSMS(){System.out.println("-------sendSMS");}public void hello(){System.out.println("-------hello");}}/*** 两个静态同步方法,同1部手机,请问先打印邮件还是短信* 结果:* 三秒后* -------sendEmail* -------sendSMS*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();
// Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{phone.sendSMS();
// phone.hello();
// phone2.sendSMS();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;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 sendSMS(){System.out.println("-------sendSMS");}public void hello(){System.out.println("-------hello");}}/*** 两个静态同步方法, 2部手机,请问先打印邮件还是短信* 结果:* 三秒后* -------sendEmail* -------sendSMS*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{
// phone.sendSMS();
// phone.hello();phone2.sendSMS();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;class Phone //资源类
{public static synchronized void sendEmail(){//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("-------sendEmail");}public synchronized void sendSMS(){System.out.println("-------sendSMS");}public void hello(){System.out.println("-------hello");}}/*** 1个静态同步方法,1个普通同步方法,同1部手机,请问先打印邮件还是短信* 结果:* -------sendEmail* 2.7秒后* -------sendSMS*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{phone.sendSMS();
// phone.hello();
// phone2.sendSMS();},"b").start();}
}
执行结果:
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;class Phone //资源类
{public static synchronized void sendEmail(){//暂停几秒钟线程try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println("-------sendEmail");}public synchronized void sendSMS(){System.out.println("-------sendSMS");}public void hello(){System.out.println("-------hello");}}/*** 1个静态同步方法,1个普通同步方法,2部手机,请问先打印邮件还是短信* 结果:* -------sendEmail* 2.7秒后* -------sendSMS*/
public class Lock8Demo {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();new Thread(()->{phone.sendEmail();},"a").start();//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }new Thread(()->{
// phone.sendSMS();
// phone.hello();phone2.sendSMS();},"b").start();}
}
执行结果:
锁一锁二
锁三锁四
锁五锁六
三种 synchronized 锁的内容有一些差别:
锁七锁八
当一个线程试图访问同步代码时它首先必须得到锁,退出或抛出异常时必须释放锁。
主要的思路就是判断锁对象是否一致,判断我们的锁是
对象锁
还是类锁
,只要锁对象不相同就不会出现加锁阻塞的情况
8锁问题讲的是synchronized,而synchronized 有三种加锁方式
同步方法:锁的是当前实例对象,通常指this
静态同步方法:静态同步方法,锁的是当前类的Class对象
同步代码块:时我们手动指定加锁对象比较容易判断,即判断两个线程的锁对象是否一致
javap -c ***.class文件反编译
- -c 对代码进行反汇编
假如你需要更多信息
- javap -v ***.class文件反编译
- -v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
package site.zhourui.juc.locks;/*** synchronized同步代码块字节码实现*/
public class LockByteCodeDemo {Object object = new Object();public void m1(){synchronized (object){System.out.println("----------hello sync code");}}public static void main(String[] args) {}
}
执行一下后用在控制台找到字节码文件进行反编译
小总结:
synchronized同步代码块实现使用的是monitorenter和monitorexit指令
m1方法里面自己添加一个异常试试
package site.zhourui.juc.locks;/*** synchronized同步代码块字节码实现*/
public class LockByteCodeDemo {Object object = new Object();public void m1(){synchronized (object){System.out.println("----------hello sync code");throw new RuntimeException("------exp");//添加异常}}public static void main(String[] args) {}
}
执行一下后用在控制台找到字节码文件进行反编译
回答不是:在程序出异常的情况下只有一个一个enter一个exit
package site.zhourui.juc.locks;/**
* synchronized同步代码方法字节码实现
*/
public class LockByteCodeDemo {public synchronized void m2(){System.out.println("----------hello static synchronized m2");}public static void main(String[] args) {}
}
我们使用-v查看更多信息
javap -v .\LockByteCodeDemo.class
同步方法字节码的实现是通过加标志位
ACC_SYNCHRONIZED
实现的调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先持有monitor然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor
package site.zhourui.juc.locks;/*** synchronized静态同步代码方法字节码实现*/
public class LockByteCodeDemo {public static synchronized void m3(){System.out.println("----------hello static synchronized m3");}public static void main(String[] args) {}
}
我们使用-v查看更多信息
javap -v .\LockByteCodeDemo.class
静态同步方法字节码的实现是通过加标志位
ACC_STATIC
和ACC_SYNCHRONIZED
实现的
回答:因为在HotSpot虚拟机中,monitor采用ObjectMonitor实现,而我们每个java类的父类都是Object类,所以每个对象天生都带着一个对象监视器,所以为什么任何一个对象都可以成为一个锁
c++ 源码解读 : ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
objectMonitor.hpp中定义了几项重要的属性
是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的
ReentrantLock reentrantLock = new ReentrantLock(true);
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后中请的线程比先中请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某个线程一直得不到锁)
ReentrantLock reentrantLock = new ReentrantLock(false);//默认false
ReentrantLock reentrantLock = new ReentrantLock();//默认非公平锁
公平锁保证了排队的公平性,不会造成锁饥饿,但是会增加线程的开销
非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,这就是传说中的 “锁饥饿”
package site.zhourui.juc.locks;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class Ticket
{private int number = 50;private Lock lock = new ReentrantLock(); //默认用的是非公平锁,分配的平均一点,=--》公平一点public void sale(){lock.lock();try{if(number > 0){System.out.println(Thread.currentThread().getName()+"\t 卖出第: "+(number--)+"\t 还剩下: "+number);}}finally {lock.unlock();}}
}public class SaleTicketDemo {public static void main(String[] args){Ticket ticket = new Ticket();new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"a").start();new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"b").start();new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"c").start();new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"d").start();new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"e").start();}
}
默认非公平锁时执行结果:
线程a直接将票卖完了
公平锁执行结果:
所有线程都参与进来了,没有出现线程饥饿的问题
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
package site.zhourui.juc.locks;public class ReEntryLockDemo {public static void main(String[] args){final Object objectLockA = new Object();new Thread(() -> {synchronized (objectLockA){System.out.println("-----外层调用");synchronized (objectLockA){System.out.println("-----中层调用");synchronized (objectLockA){System.out.println("-----内层调用");}}}},"a").start();}
}
验证结果:该锁可以递归调用
package site.zhourui.juc.locks;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReEntryLockDemo {public static void main(String[] args){Lock 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("------22222");}finally {lock.unlock();}},"t2").start();}
}
执行结果: 该锁可以递归调用
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
_count
为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程_owner
设置为当前线程,并且将其计数器_count
加1。_count
加1并且锁的重入次数_recursions
加一,否则需要等待,直至持有线程释放该锁。_recursions
不为0_recursions
同时减一,计数器为零代表锁已被释放。死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
package site.zhourui.juc.locks;import java.util.concurrent.TimeUnit;public class DeadLockDemo {static Object lockA = new Object();static Object lockB = new Object();public static void main(String[] args){Thread a = new Thread(() -> {synchronized (lockA) {System.out.println(Thread.currentThread().getName() + "\t" + " 自己持有A锁,期待获得B锁");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB) {System.out.println(Thread.currentThread().getName() + "\t 获得B锁成功");}}}, "a");a.start();new Thread(() -> {synchronized (lockB){System.out.println(Thread.currentThread().getName()+"\t"+" 自己持有B锁,期待获得A锁");try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }synchronized (lockA){System.out.println(Thread.currentThread().getName()+"\t 获得A锁成功");}}},"b").start();}
}
执行结果:
jps -l
查询我们程序执行的进程id
我们推测DeadLockDemo出现了死锁,jstack 进程编号
,如果出现死效果如下图:
控制台输入jconsole,选中猜测死锁的线程点击连接
选择线程选项点击检测死锁
查看是否有死锁信息,有死锁的话会自动显示出来
指针指向monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)