多线程:synchronized关键字解析
原理
synchronized是JVM层面的锁,是一种重量级的锁。synchronized可以同步方法和代码块。
public class Synchronized {public static void main(String[] args) {// 对Synchronized Class对象进行加锁synchronized (Synchronized.class) {}// 静态同步方法,对Synchronized Class对象进行加锁m();}public static synchronized void m() {}
}
执行javap - v Synchronized
public static void main(java.lang.String[]);
// 方法修饰符,表示:public staticflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: ldc #1 // class com/murdock/books/multithread/book/Synchronized2: dup3: monitorenter // monitorenter:监视器进入,获取锁4: monitorexit // monitorexit:监视器退出,释放锁5: invokestatic #16 // Method m:()V8: returnpublic static synchronized void m();// 方法修饰符,表示: public static synchronizedflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=0, locals=0, args_size=00: return
方法级别的同步是隐式的,无需通过字节码指令来控制,它依靠的是方法表里的ACC_SYNCHRONIZED标志(什么是方法表和标志?),当方法调用时,调用指令会检查方法的ACC_SYNCHRONIZED是否被设置了,如果被设置了执行线程首先需要持有管程才能执行方法,执行后或异常时释放管程。
而代码块级别的同步依靠的是monitorenter和monitorexit指令,这两个指令总是成对执行的,在程序异常时编译器会生成一个异常处理器来执行monitorexit指令。
无论采用哪种方式,都是对一个对象的监视器或叫做管程(Monitor)进行获取,这个过程是排他的,也就是同一时刻只可以有一个线程获取到有synchronized保护对象的监视器。获取不到的线程会阻塞在同步方法或同步块的入口处,进入BLOCKED阻塞状态。这里要区别一下阻塞状态和等待状态,使用Object的wait方法后会进入等待队列,notify后唤醒线程从等待队列移入到阻塞(同步)队列。线程正常结束或者异常释放monitor。
以下是对象,对象的监视器,同步队列以及执行线程的关系
另外,JVM对重量级锁进行了优化,在对象头里存放着锁的类型和偏向线程id。
偏向锁:某个线程用这个锁用的比较频繁,那就把这个线程id存起来,锁类型设为偏向锁。那么下次如果还是他来获取锁的话,不用CAS直接将锁给他。
轻量级锁:多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放。
锁的级别从低到高为:无状态锁,偏向锁,轻量级锁(自旋),重量级锁。锁只可以升级不可以降级。
使用
两个线程操作同一个对象里的实例变量,为什么是实例变量?因为局部变量是没有线程安全问题的。
不安全的代码如下:
public class HasSelfPrivateNum {private int num = 0;public void addi(String username){ (1)try{if (username.equals("a")){num = 100;System.out.println("a set over!");Thread.sleep(3000);}else {num = 200;System.out.println("b set over!");}System.out.println( username + " num = " + num);}catch (InterruptedException e){e.printStackTrace();}}
}
public class ThreadA extends Thread {private HasSelfPrivateNum num;public ThreadA(HasSelfPrivateNum num){this.num = num;}@Overridepublic void run() {super.run();num.addi("a");}
}
public class ThreadB extends Thread{private HasSelfPrivateNum num;public ThreadB(HasSelfPrivateNum num){this.num = num;}@Overridepublic void run() {super.run();num.addi("b");}
}
public class Run {public static void main(String[] args) {HasSelfPrivateNum num = new HasSelfPrivateNum();// HasSelfPrivateNum num1 = new HasSelfPrivateNum(); (2)ThreadA threadA = new ThreadA(num);threadA.start();ThreadB threadB = new ThreadB(num); (3)threadB.start();}
}
执行结果:
a set over!
b set over!
b num = 200
a num = 200
执行结果显然发生了线程安全的问题。
接下来:
使用synchronized同步方法,在HasSelfPrivateNum的方法(1)上添加sychronized,即
synchronized public void addi(String username){...}
此时的执行结果为:
a set over!
a num = 100
b set over!
b num = 200
接下来:
在之前添加synchronized的基础上,我们将之前两个线程访问同一个对象改为每个线程单独访问一个对象,将Run类中的(2)的注释打开,将(3)处传入的对象改为num1。
此时的执行结果为:
a set over!
b set over!
b num = 200
a num = 100
可以看到没有线程安全问题,但是执行结果的顺序是交叉的。
这是因为关键词synchronized取得的都是对象的锁,所以当两个线程访问同一个对象的时候,这个对象的锁没有释放另一个线程就无法访问,执行结果就会是按照顺序的。但是如果两个线程执行的是同一个类的两个对象,那么就会创建两个锁,两个线程分别执行互不影响。所以执行结果就会是交叉的。
以上代码证明了多个线程可以异步操作多个对象的同一个sychronized方法。
但是,多个线程却不可以操作同一个类的同一个sychronized类型的静态方法,因为同步方法因为可以有多个对象所以会对应多个monitor,而静态方法只会对应一个monitor。多个线程访问时只有一个可以获取monitor。
接下来讨论一下同步方法和同步代码块的区别,以实例的为例。
代码如下:
public class Var {synchronized public void methodA(){try {System.out.println(Thread.currentThread().getName() + " run method A " + System.currentTimeMillis());Thread.sleep(3000);}catch (InterruptedException e){e.printStackTrace();}}public void methodB(){synchronized (this){System.out.println(Thread.currentThread().getName() + " run method B " + System.currentTimeMillis());}}public void methodC(){String syn = "synchtronized";synchronized (syn){System.out.println(Thread.currentThread().getName() + " run method C " + System.currentTimeMillis());}}}
public class Test {public static void main(String[] args) {Var var = new Var();new Thread(new Runnable() {@Overridepublic void run() {var.methodA();}}).start();new Thread(new Runnable() {@Overridepublic void run() {var.methodB();}}).start();new Thread(new Runnable() {@Overridepublic void run() {var.methodC();}}).start();}
}
执行结果:
Thread-0 run method A 1520142974301
Thread-2 run method C 1520142974301
Thread-1 run method B 1520142977302
可以看到方法B比其两个方法打印慢3秒,线程Threa-0首先获得对象var的锁,接着线程Thread-0会休眠3秒,这时虽然线程Thread-1先进入线程规划器,但是因为方法methodB内部使用了sychronized代码块,而因为methodC同步的只是方法内部的一个变量所以可以执行。
脏读
发生脏读的代码如下:
public class PublicVar {public String username = "A";public String password = "AA";synchronized public void setValue(String username,String password){try {this.username = username;Thread.sleep(1000);this.password = password;System.out.println("current thread = " + Thread.currentThread().getName() + " username = " + username+ " password = " + password);}catch (InterruptedException e){e.printStackTrace();}}public void getValue(){System.out.println("username = " + username + " password = " + password);}}
public class ThreadA extends Thread {private PublicVar publicVar;public ThreadA(PublicVar publicVar){super();this.publicVar = publicVar;}@Overridepublic void run() {super.run();publicVar.setValue("B","BB");}
}
public class Test
{public static void main(String[] args) {try {PublicVar publicVar = new PublicVar();ThreadA threadA = new ThreadA(publicVar);threadA.start();Thread.sleep(500); // 打印结果受此值影响,大于线程threadA(即setValue方法)休眠的时间就不会出现脏读publicVar.getValue();}catch (InterruptedException e){e.printStackTrace();}}
}
执行结果:
username = B password = AA
current thread = Thread-0 username = B password = BB
如果也将getValue设为sychronized,那么执行结果:
current thread = Thread-0 username = B password = BB
username = B password = BB
此实验可以得到另个结论:
1,A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object的非sychronized;
2,A线程持持有object对象的Lock锁,B线程如果要调用object的sychronized类型方法则需等待,也就是同步。
第一次执行的时候,线程threadA先获得publicVar对象的锁,但是main线程依然可以调用publicVar对象的非sychronized方法getValue,此时username已被更改,password没被该。
第二次执行的时候,线程threadA先获得publicVar对象的锁,但是main线程在threadA没有执行完成setValue方法之前是不可以调用publicVar对象的sychronized方法getValue的,也就是只有threadA释放了锁,将username和password都赋值了,main线程才可以获取publicVar的锁进而调用getValue方法。
为什么会这样呢?之前提到过在调用方法前会检查方法的ACC_SYNCHRONIZED标志是否被标志了,标志的情况下才需要获取锁,如果没有标志即使这个对象的锁没有被当前对象持有依然可以执行。所以实例方法同步的是对象,静态方法同步的是类这个说法不是很全面。
sychronized锁重入
sychronized关键字拥有锁重入的功能,也就是在一个线程得到一个对象琐时,再次请求此对象锁时是可以得到对象锁的,广义的可重入锁也叫递归锁,是指同一线程外层函数获得锁之后,内层还可以再次获得此锁。这也证明了在sychronized方法内部调用本类的其他sychronized方法时,是可以永远得到锁的。
参考:《深入理解JVM虚拟机》《Java并发编程的艺术》
多线程:synchronized关键字解析相关推荐
- synchronized 关键字解析
synchronized 关键字解析 同步锁依赖于对象,每个对象都有一个同步锁. 现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A 获得 Test 的 ...
- java线程同步以及对象锁和类锁解析(多线程synchronized关键字)
一.关于线程安全 1.是什么决定的线程安全问题? 线程安全问题基本是由全局变量及静态变量引起的. 若每个线程中对全局变量.静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线 ...
- java同步锁synchronized_Java对象锁和类锁全面解析(多线程synchronized关键字)
本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁.特别的是希望能帮大家理清一些概念. 一.synchronized关键字 synchronized关键字有如下两种 ...
- java中存在对多个对象加锁的情况_Java对象锁和类锁全面解析(多线程synchronized关键字)...
最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不过是别人怎么用就跟着用,并没有搞清楚锁的概念.最近也是遇到一些问题,不搞清楚锁的概念,很容易碰壁,甚至有些时候自己连用没 ...
- Java对象锁和类锁全面解析(多线程synchronized关键字)
最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不过是别人怎么用就跟着用,并没有搞清楚锁的概念.最近也是遇到一些问题,不搞清楚锁的概念,很容易碰壁,甚至有些时候自己连用没 ...
- Java 对象锁和类锁 死锁(多线程synchronized关键字)
本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁 和 类锁. 对于同步,要时刻清醒在 哪个锁对象 上同步,这是关键. 对于同步代码块,要看清楚什么对象已经用于锁定(sy ...
- Java并发编程之synchronized关键字解析
前言 公司加班太狠了,都没啥时间充电,这周终于结束了.这次整理了Java并发编程里面的synchronized关键字,又称为隐式锁,与JUC包中的Lock显示锁相对应:这个关键字从Java诞生开始就有 ...
- Java多线程-synchronized关键字
进程:是一个正在执行中的程序.每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 线程:就是进程中的一个独立的控制单元.线程在控制着进程的执行. 一个进程中至少有一个线程 Ja ...
- Java 多线程(六) synchronized关键字详解
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题. 同步机制可以使用synchronized关键字实现. 当synchroniz ...
- Java 多线程:synchronized 关键字用法(修饰类,方法,静态方法,代码块)
前言 在 多线程生成的原因(Java内存模型与i++操作解析) 中,介绍了Java的内存模型,从而可能导致的多线程问题.synchronized就是避免这个问题的解决方法之一.除了 synchroni ...
最新文章
- Android使用Apache HttpClient发送GET、POST请求
- Ackerman 函数的解法
- 职业中专的计算机综合应用,职业中专计算机教学的思考
- 北斗导航 | Matlab实现电离层延迟计算:Klobuchar(源代码)
- 安卓逆向系列教程 4.4 逆向云播 VIP
- tsm2812通用定时器中断_通用定时器中断(TIM2)
- activemq后台管理 看topic消息_「Java」 - SpringBoot amp; ActiveMQ
- 车牌检测和识别(转)
- document.body.scrollTop滚动失效
- eclipse 代码问题总结
- 计算机在智能交通应用,计算机信息技术在智能交通系统中的应用
- 深入理解:面向服务与微服务架构
- 迅雷极速版服务器未响应,迅雷极速版频繁崩溃,求大神帮忙
- 日本华人IT派遣那点事儿(2)
- 读的, 且经过美化, 能在所有 JavaScript 环境中运行, 并且应该和对应手写的 JavaScript 一样快或者更快.
- 高考415分能上计算机网络的学校吗,2021年高考理科415分能上什么学校 理科415分左右的大学有哪些...
- 【常规人力面试50题】
- WEB开发36式打狗棒法
- 服装企业部署MES管理系统的五个原因
- 三种思维,让我脱胎换骨