synchronized关键字
Lock的实现类都是悲观锁
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源。
public class OptimismLock {
// 保证多个线程使用的是同一个lock对象的前提下
ReentrantLock lock = new ReentrantLock();
//悲观锁加锁方式 synchronized
public synchronized void test(){
}
//Lock的实现类都是悲观锁
public void test2(){
lock.lock();
try {
// 操作同步资源
}finally {
lock.unlock();
}
}
}
版本号机制Version。(只要有人提交了就会修改版本号,可以解决ABA问题)
ABA问题:再CAS中想读取一个值A,想把值A变为C,不能保证读取时的A就是赋值时的A,中间可能有个线程将A变为B再变为A。
最常采用的是CAS算法
数据库提供的类似于 write_condition 机制
适合读操作多的场景,不加锁的性能特点能够使其操作的性能大幅提升。
//=============乐观锁的调用方式
// 保证多个线程使用的是同一个AtomicInteger
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();
如果写入失败的处理方法
synchronized定义:就是监视器锁,monitor lock(对象锁),锁的是资源
1 标准访问有ab两个线程,请问先打印邮件还是短信?
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");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone.sendSMS();
},"b").start();
}
}
----------sendEmail
-------------sendSMS
2 a里面故意停3秒?先输出什么
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 class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone.sendSMS();
},"b").start();
}
}
//过了三秒输出
----------sendEmail
-------------sendSMS
3 添加一个普通的hello方法,请问先打印邮件还是hello?
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");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone.hello();
},"b").start();
}
}
-------------hello
----------sendEmail
synchronized
修饰的方法产生争抢4 有两部手机,请问先打印邮件(这里有个3秒延迟)还是短信?
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");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone1.sendSMS();
},"b").start();
}
}
-------------sendSMS
----------sendEmail
5.有两个静态同步方法(synchroized前加static,3秒延迟也在),有1部手机,先打印邮件还是短信?
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");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone.sendSMS();
},"b").start();
}
}
----------sendEmail
-------------sendSMS
6.两个手机,有两个静态同步方法(synchroized前加static,3秒延迟也在),先打印邮件还是短信?
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");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone1.sendSMS();
},"b").start();
}
}
----------sendEmail
-------------sendSMS
5.6中 static+synchronized 对应的锁加到了类对象中
对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁→实例对象本身。
对于静态同步方法,锁的是当前类的Class对象,如Phone,class唯一的一个模板。
对于同步方法块,锁的是synchronized括号内的对象。synchronized(o)
7 一个静态同步方法,一个普通同步方法,请问先打印邮件还是手机?
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");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone.sendSMS();
},"b").start();
}
}
-------------sendSMS
----------sendEmail
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 sendSMS(){
System.out.println("-------------sendSMS");
}
public void hello(){
System.out.println("-------------hello");
}
}
public class LockSynchronized {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(()->{
phone.sendEmail();
},"a").start();
//暂停毫秒,保证a线程先启动
try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}
new Thread(()->{
phone1.sendSMS();
},"b").start();
}
}
-------------sendSMS
----------sendEmail
javap -c ***.class
文件反编译,-c表示对代码进行反汇编javap -v ***.class
,-v即-verbose输出附加信息(包括行号、本地变量表、反汇编等详细信息)public class SynchronizedTest {
Object object = new Object();
public void m1(){
synchronized (object){
System.out.println("-----hello synchronized code block");
}
}
public static void main(String[] args) {
}
}
Compiled from "SynchronizedTest.java"
public class com.lsc.day03.SynchronizedTest {
java.lang.Object object;
public com.lsc.day03.SynchronizedTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."":()V
12: putfield #3 // Field object:Ljava/lang/Object;
15: return
public void m1();
Code:
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String -----hello synchronized code block
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
public static void main(java.lang.String[]);
Code:
0: return
}
synchronized同步代码块,实现使用的是moniterenter和moniterexit指令(moniterexit可能有两个)
那一定是一个enter两个exit吗?(不一样,如果主动throw一个RuntimeException,发现一个enter,一个exit,还有两个athrow)
/**
* 锁普通的同步方法
*/
public class LockSyncDemo {
public synchronized void m2(){
System.out.println("------hello synchronized m2");
}
public static void main(String[] args) {
}
}
.....
public synchronized void m2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED //请注意该标志
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String ------hello synchronized m2
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 11: 0
line 12: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/zhang/admin/controller/LockSyncDemo;
......
/**
* 锁静态同步方法
*/
public class LockSyncDemo {
public synchronized void m2(){
System.out.println("------hello synchronized m2");
}
public static synchronized void m3(){
System.out.println("------hello synchronized m3---static");
}
public static void main(String[] args) {
}
}
......
public static synchronized void m3();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED //访问标志 区分该方法是否是静态同步方法
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String ------hello synchronized m3---static
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 15: 0
line 16: 8
......
管程:Monitor(监视器),也就是我们平时说的锁。监视器锁
信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。 管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管理。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。
Java虚拟机给每个对象和class字节码都设置了一个监听器Monitor,用于检测并发代码的重入,同时在Object类中还提供了notify和wait方法来对线程进行控制。
为什么任何一个对象都可以成为一个锁?
Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法。
ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
ObjectMonitor.cpp中引入了头文件(include)objectMonitor.hpp
//140行
ObjectMonitor() {
_header = NULL;
_count = 0; //用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0;//锁的重入次数
_object = NULL;
_owner = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
_WaitSet = NULL; //存放处于wait状态的线程队列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//存放处于等待锁block状态的线程队列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
synchronized必须作用于某个对象中,所以Java在对象的头文件存储了锁的相关信息。锁升级功能主要依赖于 MarkWord 中的锁标志位和释放偏向锁标志位
Mark Word
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。
Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit,因为对象头中要存储的数据已经超过了64bit的限制,考虑到了了虚拟机的空间效率,所以Mark Word被设计成动态定义的数据结构,以便在极小的内存空间存储尽量多的数据,根据对象的状态来复用我们的存储空间
在32位JVM中是这么存储的
在64位JVM中是这么存的
ReentrantLock抢票案例
class Ticket{
private int num=30;
ReentrantLock lock =new ReentrantLock();
//ReentrantLock lock = new ReentrantLock(true);
public void sale(){
lock.lock();
try {
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出第:\t"+(num--)+"\t 还剩下:"+num);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class FairnessLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{ for (int i = 0; i < 35; i++) { ticket.sale(); } },"t1").start();
new Thread(()->{ for (int i = 0; i < 35; i++) { ticket.sale(); } },"t2").start();
new Thread(()->{ for (int i = 0; i < 35; i++) { ticket.sale(); } },"t3").start();
}
}
非公平锁:获取失败的线程进入阻塞队列,当锁被释放,阻塞队列的所有线程都有可能获得到锁(不一定是等待时间最长的线程获得)
synchronized,ReentrantLock默认是非公平锁
对应到买票案例——非公平锁可以插队,买卖票不均匀。
公平锁:获取失败的线程进入阻塞队列,当锁被释放,第一个进入阻塞队列的线程首先获得到锁(等待时间最长的线程获得锁)
ReentrantLock lock = new ReentrantLock(true);对应公平锁
对应到买票案例——买卖票一开始t1占优,后面a b c a b c a b c均匀分布
为什么会有公平锁/非公平锁的设计?为什么默认是非公平?
什么时候用公平?什么时候用非公平?
操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重入的。
可重入锁的一个优点是可一定程度避免死锁。
可重入锁又称为递归锁
可重入锁 详细解释
可:可以
重:再次
入:进入
锁:同步锁
进入什么:进入同步域(即同步代码块/方法或显示锁锁定的代码)
一句话:一个线程中的多个流程可以获取同一把锁,持有这把锁可以再次进入。自己可以获取自己的内部锁。
synchronized
是Java中的关键字,默认是可重入锁,即隐式锁在同步块中
public class ReentrantLockTest {
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();
}
}
//-----外层调用
//-----中层调用
//-----内层调用
在同步方法中
public class ReentrantLockTest {
public static void main(String[] args) {
ReentrantLockTest lockTest=new ReentrantLockTest();
lockTest.m1();
}
public synchronized void m1() {
//指的是可重复可递归调用的锁,在外层使用之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁
System.out.println(Thread.currentThread().getName()+"\t"+"-----come in m1");
m2();
System.out.println(Thread.currentThread().getName()+"\t-----end m1");
}
public synchronized void m2(){
System.out.println("-----m2");
m3();
}
public synchronized void m3() {
System.out.println("-----m3");
}
}
ObjectMoitor.hpp
ObjectMonitor() {
_header = NULL;
_count = 0; //用来记录该线程获取锁的次数
_waiters = 0,
_recursions = 0;//锁的重入次数
_object = NULL;
_owner = NULL; //------最重要的----指向持有ObjectMonitor对象的线程,记录哪个线程持有了我
_WaitSet = NULL; //存放处于wait状态的线程队列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//存放处于等待锁block状态的线程队列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
ObjectMoitor.hpp底层:每个锁对象(Monitor)拥有一个锁计数器和一个指向持有该锁的线程的指针。分别是_count 和 _owner
首次加锁:当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
重入:在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
释放锁:当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
感觉所谓的显式隐式即是指显示/隐式的调用锁
我们synchronized就不需要我们自己来加锁和解锁,进入对应方法体就加锁,执行完就解锁了
public class ReentrantLockTest {
static Lock lock=new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}, "t1").start();
}
}
t1 ----come in 外层调用
t1 ------come in 内层调用
lock
unlock
要成对
lock
unlock
不成对,单线程情况下问题不大,但多线程下出问题public class ReentrantLockTest {
static Lock lock=new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t----come in 外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t------come in 内层调用");
} finally {
lock.unlock();
}
} finally {
// lock.unlock();
//不成对
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try
{
System.out.println("t2 ----外层调用lock");
}finally {
lock.unlock();
}
},"t2").start();
}
}
t1 ----come in 外层调用
t1 ------come in 内层调用
系统资源不足
进程运行推进的顺序不合适
资源分配不当
public class DeadLock {
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
new Thread(()->{
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"我现在占用了object1资源,我还想要object2资源");
try { TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//使得线程b也启动
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"我现在占用了object1和object2资源");
}
}
},"t1").start();
new Thread(()->{
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"我现在占用了object2资源,我还想要object1资源");
try { TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//使得线程b也启动
synchronized (object1 ){
System.out.println(Thread.currentThread().getName()+"我现在占用了object1和object2资源");
}
}
},"t2").start();
}
}
t1我现在占用了object1资源,我还想要object2资源
t2我现在占用了object2资源,我还想要object1资源
只要破坏掉其中一个条件就可以解决死锁,最容易破坏的条件就是循环等待
破坏循环等待
最常用的一种死锁阻止技术就是锁排序. 假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号(1, 2, 3…M).N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁…它必须按照确定的顺序获取它们。它不能获取序列后面的锁,除非它获得了前面的锁。这样就可以避免循环等待
jps -l
查看当前进程运行状况jstack 进程编号
查看该进程信息win
+ r
输入jconsole
,打开图形化工具,打开线程
,点击 检测死锁
。以下相当于一些前置知识,为后面的章节做铺垫
写锁(独占锁)/读锁(共享锁)
自旋锁SpinLock
无锁-独占锁-读写锁-邮戳锁
无锁-偏向锁-轻量锁-重量锁