一、前言

  在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的。

二、引入Synchronized锁机制

本篇将通过以下实例代码来讲述synchronized锁机制

2.1 多线程安全问题实例

  举例:两个线程分别获取不同的userName对应的num值。

  ThreadSynch类,不同的userName对应不同的num值,“zs”对应的num值是100,“ls”对应的num值是200

public class ThreadSynch {

    private int num;

    public void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

  定义两个线程去获取不同的userName对应的num值,线程1获取“zs”对应的num值

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("zs");
}
}

  线程2获取“ls”对应的num值

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("ls");
}
}

  测试,通过同一个对象构建两个线程,获取不同的userName对应的num值

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch = new ThreadSynch();
//根据相同的对象构建两个线程
Thread thread01 = new Thread01(threadSynch);
Thread thread02 = new Thread02(threadSynch);
thread01.start();
thread02.start();
}
}

  按说,结果应该是:“zs”对应的num值是100,“ls”对应的num值是200,看一下控制台的打印,“zs”和“ls”的num值都是200

zs setNum 100
ls setNum 200
ls num ======200
zs num ======200

  分析一下上述的线程安全问题是怎么产生的:

  1、thread01先运行,去获取“zs”对应的num值,此时num值已经被赋值为100了,然后开始睡眠2秒,控制台显示“zs setNum 100”

  2、在thread01睡眠的时候,thread02开始运行或是运行结束了,此时num值已经由100改成了200,控制台显示“ls setNum 200        ls num ======200”

  3、thread01睡眠结束,但由于thread01和thread02操作的是同一个对象的num属性,所以此时num值已经由100变成了200,控制台显示“zs num ======200”

  产生问题的原因找到了,解决就很简单了。thread01操作threadSynch对象的getNumByUserName()方法的时候,在其结束之前不要让thread02进入该方法执行,即给getNumByUserName()方法加一个同步锁。

public class ThreadSynch {

    private int num;

    public synchronized void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

  结果:

zs setNum 100
zs num ======100
ls setNum 200
ls num ======200

三、Synchronized原理

根据上面的实例来了解Synchronized的原理。反编译一下加了synchronized的代码是如何实现对代码块进行同步的

3.1 synchronized修饰代码块:

public class ThreadSynch {
private int num; public void getNumByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}
}

反编译的结果:

关于图中标红的两条指令,monitorenter和monitorexit,我们直接参考JVM规范中的描述:

monitorenter:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

这段话的大概意思为:

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

  2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

  3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

 monitorexit:

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

这段话的大概意思为:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

具体的解释:

  1、当thread01执行getNumByUserName方法碰到synchronized关键字时,执行monitorenter指令,thread01去获取与threadSynch对象关联的monitor(监视器)的entry count,此时与threadSynch对象关联的monitor(监视器)的entry count是0,所以thread01进入该监视器并将其entry count设置为1,thread01此时是与threadSynch对象关联的monitor(监视器)的所有者。

  2、thread01执行getNumByUserName方法sleep的时候,thread02进来执行该方法,但如上所述,thread01此时是与threadSynch对象关联的monitor(监视器)的所有者,thread02进入阻塞状态。

  3、thread01执行getNumByUserName方法结束,执行monitorexit指令,thread01将与threadSynch对象关联的monitor(监视器)的entry count减1,此时entry count为0,thread01退出monitor,不在是其所有者。

  4、处于阻塞状态的thread02重新进入与threadSynch对象关联的monitor(监视器),获取所有权,此时与threadSynch对象关联的monitor(监视器)的entry count是0,接下来的步骤与1中相同。

3.2 synchronized修饰普通方法

public class ThreadSynch {
private int num; public synchronized void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

反编译结果:

  从反编译结果来看,方法的同步并没有通过monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

3.3 synchronized修饰静态方法

public class ThreadSynch {
private static int num; public static synchronized void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

反编译结果:

  可以看到,静态方法的同步和普通方法的同步其实现方式都是一样的。

通过实例验证:

① 多线程访问同一对象-----同一方法-----不加锁

  上述的多线程安全问题实例已经演示过了。

② 多线程访问同一对象-----同一方法-----加锁

  上述讲解synchronized原理已经演示过了。

③ 多线程访问同一对象-----不同方法-----两个方法都不加锁

  线程:thread01、thread02

  同一对象:ThreadSynch

  两个方法:getNumByUserName、getIDByUserName

  线程访问的对象:

public class ThreadSynch {

    private int num;
private int id; public void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
} public void getIDByUserName(String userName){
if("zs".equals(userName)){
try {
id = 1;
System.out.println("zs setID 1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
id = 2;
System.out.println("ls setID 2");
}
System.out.println(" " + userName + " id ======" + id);
}
}

  thread01:

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("zs");
}
}

  thread02:

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getIDByUserName("zs");
}
}

  测试:

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch = new ThreadSynch();
//两个线程访问同一个对象
Thread thread01 = new Thread01(threadSynch);
Thread thread02 = new Thread02(threadSynch);
thread01.start();
thread02.start();
}
}

  结果:

zs setNum 100
zs setID 1
zs num ======100
zs id ======1

  说明:两个线程访问同一个对象的不同方法,两个方法都没有加锁,那么两个线程就不存在一个等待另一个执行完再执行的问题,即各执行各的,也不存在线程安全问题。

④ 多线程访问同一对象-----不同方法-----两个方法都加锁

  线程:thread01、thread02

  同一对象:ThreadSynch

  加锁的两个方法:getNumByUserName、getIDByUserName

public class ThreadSynch {
private int num;
private int id; public void getNumByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
} public void getIDByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
id = 1;
System.out.println("zs setID 1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
id = 2;
System.out.println("ls setID 2");
}
System.out.println(" " + userName + " id ======" + id);
}
}
}

  结果:

zs setNum 100
zs num ======100
zs setID 1
zs id ======1

  说明:两个线程访问同一对象的不同方法,这两个方法都加了锁,那么thread01执行getNumByUserName方法的时候,根据synchronized的原理,thread01是与threadSynch对象关联的monitor(监视器)的所有者,该monitor的entry count是1,所以虽然thread02执行的是另一个方法getIDByUserName,但还是处于阻塞状态,必须等待thread01执行完后,不再是monitor的所有者,thread02才能执行。

⑤ 多线程访问同一对象-----不同方法-----一个方法加锁,一个方法不加锁

  线程:thread01、thread02

  同一对象:ThreadSynch

  加锁的方法:getNumByUserName

  不加锁的方法:getIDByUserName

public class ThreadSynch {
private int num;
private int id; public void getNumByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
} public void getIDByUserName(String userName){
if("zs".equals(userName)){
try {
id = 1;
System.out.println("zs setID 1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
id = 2;
System.out.println("ls setID 2");
}
System.out.println(" " + userName + " id ======" + id);
}
}

  结果:

zs setNum 100
zs setID 1
zs id ======1
zs num ======100

  说明:两个线程访问同一对象的不同方法,getNumByUserName方法加了锁,getIDByUserName方法没加锁。那么thread01执行getNumByUserName方法的时候,根据synchronized的原理,先判断与threadSynch对象关联的monitor(监视器)是否被哪个线程所有,没有,thread01就是与threadSynch对象关联的monitor的所有者,该monitor的entry count是1。thread02执行getIDByUserName方法,因为该方法没有加锁,所以无需判断与threadSynch对象关联的monitor是否被哪个线程所有(此时该monitor被thread01所有,但与thread02无关),即thread01和thread02各执行各的,无需等待哪一方执行结束在执行。

⑥ 多线程访问不同对象-----同一方法-----不加锁

  线程:thread01、thread02

  不同对象:threadSynch01,threadSynch02

  不加锁的方法:getNumByUserName

  线程访问的对象:

public class ThreadSynch {
private int num; public void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

  thread01:

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("zs");
}
}

  thread02:

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("ls");
}
}

  测试:

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch1 = new ThreadSynch();
ThreadSynch threadSynch2 = new ThreadSynch();
//两个线程访问两个不同的对象
Thread thread01 = new Thread01(threadSynch1);
Thread thread02 = new Thread02(threadSynch2);
thread01.start();
thread02.start();
}
}

  结果:

zs setNum 100
ls setNum 200
ls num ======200
zs num ======100

  说明:两个线程访问不同对象(同一个类)的同一方法,getNumByUserName方法没有加锁。方法没加锁,说明不需要判断与threadSynch对象关联的monitor(监视器)被谁所有,两个线程各执行各的,所操作的num属性也是自己对象中的。

⑦ 多线程访问不同对象-----同一方法-----加锁

  线程:thread01、thread02

  不同对象:threadSynch01,threadSynch02

  加锁的方法:getNumByUserName

public class ThreadSynch {
private int num; public void getNumByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}
}

  结果:

zs setNum 100
ls setNum 200
ls num ======200
zs num ======100

  说明:两个线程访问不同对象的同一方法,getNumByUserName方法加了锁。那么thread01执行getNumByUserName方法的时候,根据synchronized的原理,先判断与threadSynch1对象关联的monitor(监视器)是否被哪个线程所有,没有,thread01就是与threadSynch1对象关联的monitor的所有者,该monitor的entry count是1。与此同时,thread02执行getNumByUserName方法,根据synchronized的原理,先判断与threadSynch2对象关联的monitor(监视器)是否被哪个线程所有,没有,thread02就是与threadSynch2对象关联的monitor的所有者,该monitor的entry count是1。如上,因为thread01和thread02访问的是两个不同的对象,各自获取与各自对象相关的monitor的所有权,所以两个线程各执行各的,不存在一个线程等待另一个线程的问题。

⑧ 多线程访问不同对象-----不同方法-----都不加锁

  线程:thread01、thread02

  不同对象:threadSynch01,threadSynch02

  都不加锁的方法:getNumByUserName,getIDByUserName

  线程访问的对象:

public class ThreadSynch {
private int num;
private int id; public void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
} public void getIDByUserName(String userName){
if("zs".equals(userName)){
try {
id = 1;
System.out.println("zs setID 1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
id = 2;
System.out.println("ls setID 2");
}
System.out.println(" " + userName + " id ======" + id);
}
}

  thread01:

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("zs");
}
}

  thread02:

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getIDByUserName("ls");
}
}

  测试:

zs setNum 100
ls setID 2
ls id ======2
zs num ======100

  说明:两个线程访问不同对象的不同方法,getNumByUserName方法和getIDByUserName方法都没加锁。那么thread01执行getNumByUserName方法的时候,无需判断与threadSynch1对象关联的monitor(监视器)是否被哪个线程所有,类似的,thread02执行getIDByUserName方法,也无需判断与threadSynch2对象关联的monitor(监视器)是否被哪个线程所有,所以两个线程各执行各的。

⑨ 多线程访问不同对象-----不同方法-----都加锁

  线程:thread01、thread02

  不同对象:threadSynch01,threadSynch02

  都加锁的方法:getNumByUserName,getIDByUserName

public class ThreadSynch {
private int num;
private int id; public void getNumByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
} public void getIDByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
id = 1;
System.out.println("zs setID 1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
id = 2;
System.out.println("ls setID 2");
}
System.out.println(" " + userName + " id ======" + id);
}
}
}

  测试:

zs setNum 100
ls setID 2
ls id ======2
zs num ======100

  说明:两个线程访问不同对象的不同方法,getNumByUserName方法和getIDByUserName方法都加锁。那么thread01执行getNumByUserName方法的时候,先判断与threadSynch1对象关联的monitor(监视器)是否被哪个线程所有,没有,thread01是与threadSynch1对象关联的monitor的所有者,该monitor的entry count是1,类似的,thread02执行getIDByUserName方法,先判断与threadSynch2对象关联的monitor(监视器)是否被哪个线程所有,没有,thread02是与threadSynch2对象关联的monitor的所有者,该monitor的entry count是1,因为thread01和thread02获取的是各自对象关联的monitor的所有权,所以两个线程各执行各的。

⑩ 多线程访问不同对象-----不同方法-----一个方法加锁,一个方法不加锁

  线程:thread01、thread02

  不同对象:threadSynch1,threadSynch2

  加锁的方法:getNumByUserName

  不加锁的方法:getIDByUserName

public class ThreadSynch {
private int num;
private int id; public void getNumByUserName(String userName){
synchronized(this){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
} public void getIDByUserName(String userName){
if("zs".equals(userName)){
try {
id = 1;
System.out.println("zs setID 1");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
id = 2;
System.out.println("ls setID 2");
}
System.out.println(" " + userName + " id ======" + id);
}
}

  测试:

zs setNum 100
ls setID 2
ls id ======2
zs num ======100

  说明:两个线程访问不同对象的不同方法,getNumByUserName方法加锁,getIDByUserName方法没加锁。那么thread01执行getNumByUserName方法的时候,先判断与threadSynch1对象关联的monitor(监视器)是否被哪个线程所有,没有,thread01是与threadSynch1对象关联的monitor的所有者,该monitor的entry count是1,thread02执行getIDByUserName方法,该方法没加锁,无需判断与threadSynch2对象关联的monitor(监视器)是否被哪个线程所有,所以两个线程各执行各的。

四、Synchronized的可重入性

  synchronized拥有锁重入的功能,所谓锁重入的意思就是:当一个线程得到一个对象锁后,再次请求该对象锁时可以再次得到该对象锁。这也证明在一个Synchronized方法/块的内部调用本类的其他Synchronized方法/块的时候,是永远可以得到锁的,因为锁都是同一个对象锁。

  举例:

public class ThreadSynch {
public synchronized void print1(){
System.out.println("print1========");
print2();
} public synchronized void print2(){
System.out.println("print2========");
print3();
} public synchronized void print3(){
System.out.println("print3========");
}
}

  thread01中调用上述对象的print1()方法

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.print1();
}
}

  测试:

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch = new ThreadSynch();
Thread thread01 = new Thread01(threadSynch);
thread01.start();
}
}

  结果:

print1========
print2========
print3========

  说明:thread01执行同步方法print1的时候,根据synchronized的原理,thread01先判断与threadSynch对象关联的monitor(监视器)是否被哪个线程所有,没有,thread01就是与threadSynch对象关联的monitor(监视器)的所有者,该monitor的entry count是1。

  同步方法print1中调用同步方法print2,根据synchronized的原理,thread01先判断与threadSynch对象关联的monitor(监视器)是否被哪个线程所有,被自己占有,那就可以重新进入该monitor,其entry count加一变成2。

  同步方法print2中调用同步方法print3,根据synchronized的原理,thread01先判断与threadSynch对象关联的monitor(监视器)是否被哪个线程所有,被自己占有,那就可以重新进入该monitor,其entry count加一变成3。

  同步方法print3执行结束,与threadSynch对象关联的monitor(监视器)的entry count减一变成2。

  同步方法print2执行结束,与threadSynch对象关联的monitor(监视器)的entry count减一变成1。

  同步方法print1执行结束,与threadSynch对象关联的monitor(监视器)的entry count减一变成0。thread01不在是与threadSynch对象关联的monitor(监视器)的所有者。

  这种锁重入的机制,也支持在父子类继承的环境中。子类同步方法中可以通过“锁重入”调用父类的同步方法。

五、异常自动释放锁

  当一个线程执行的代码发生异常时,其所占有的锁会自动释放。

  举例:两个线程访问同一对象的加锁方法

public class ThreadSynch {
public synchronized void exceptionTest(){
int num = 10000;
System.out.println("currentThread ===" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(true){
int n = 2 / num;
num--;
}
}
}

  thread01:

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.exceptionTest();
}
}

  thread02:

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.exceptionTest();
}
}

  测试:

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch = new ThreadSynch();
Thread thread01 = new Thread01(threadSynch);
Thread thread02 = new Thread02(threadSynch);
thread01.start();
thread02.start();
}
}

  结果:

currentThread ===Thread-0
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at com.dajia.test.ThreadSynch.exceptionTest(ThreadSynch.java:23)
at com.dajia.test.Thread01.run(Thread01.java:26)
currentThread ===Thread-1
Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero
at com.dajia.test.ThreadSynch.exceptionTest(ThreadSynch.java:23)
at com.dajia.test.Thread02.run(Thread02.java:25)

  说明:thread01进来后,打印出“currentThread ===Thread-0”后,sleep了两秒,因为sleep方法并不释放锁,所以此时thread02还处于阻塞状态,直到thread01继续执行,抛异常后释放锁,thread02才开始执行。

六、Synchronized用法总结

synchronized的作用主要有三个:

  1、确保线程互斥的访问同步代码

  2、保证共享变量的修改能够同步可见

  3、有效解决重排序问题

从语法上讲,Synchronized总共有三种用法:

  1、修饰普通方法

  2、修饰静态方法

  3、修饰代码块

synchronized修饰代码块上面列举的实例全部都是,下面说一下另外两种的用法:

1 修饰普通方法

  线程:thread01、thread02

  不同对象:threadSynch01,threadSynch02

  加锁的方法:getNumByUserName

public class ThreadSynch {
private int num; public synchronized void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

  thread01:

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("zs");
}
}

  thread02:

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("ls");
}
}

  测试:

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch1 = new ThreadSynch();
ThreadSynch threadSynch2 = new ThreadSynch();
//两个线程访问两个不同的对象
Thread thread01 = new Thread01(threadSynch1);
Thread thread02 = new Thread02(threadSynch2);
thread01.start();
thread02.start();
}
}

  结果:

zs setNum 100
ls setNum 200
ls num ======200
zs num ======100

  说明:多线程访问不同对象加锁的方法时,因为各自获取与各自对象关联的那个monitor,所以各执行各的,不存在一个等待另一个的问题。

2 修饰静态方法

  线程:thread01、thread02

  不同对象:threadSynch01,threadSynch02

  加锁的方法:getNumByUserName

  线程访问的对象:

public class ThreadSynch {
private static int num; public static synchronized void getNumByUserName(String userName){
if("zs".equals(userName)){
try {
num = 100;
System.out.println("zs setNum 100");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else if("ls".equals(userName)){
num = 200;
System.out.println("ls setNum 200");
}
System.out.println(" " + userName + " num ======" + num);
}
}

  thread01:

public class Thread01 extends Thread{
private ThreadSynch threadSynch; public Thread01(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("zs");
}
}

  thread02:

public class Thread02 extends Thread{
private ThreadSynch threadSynch; public Thread02(ThreadSynch threadSynch) {
this.threadSynch = threadSynch;
} @Override
public void run() {
threadSynch.getNumByUserName("ls");
}
}

  测试:

public class Test {
public static void main(String[] args) {
ThreadSynch threadSynch1 = new ThreadSynch();
ThreadSynch threadSynch2 = new ThreadSynch();
//两个线程访问两个不同的对象
Thread thread01 = new Thread01(threadSynch1);
Thread thread02 = new Thread02(threadSynch2);
thread01.start();
thread02.start();
}
}

  结果:

zs setNum 100
zs num ======100
ls setNum 200
ls num ======200

  说明:对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),虽然threadSynch1和threadSynch2是两个不同的对象,但都是ThreadSynch这个类的实例,thread01执行getNumByUserName方法的时候,会获取与ThreadSynch这个类关联的monitor的所有权,此时thread02执行getNumByUserName方法的时候,也会获取与ThreadSynch这个类关联的monitor的所有权,但该monitor已经被thread01所有,所以thread02只能进入阻塞状态,等待thread01执行完释放monitor的所有权后,才能执行getNumByUserName方法。

参考资料:

Java并发编程:Synchronized及其实现原理

Java线程同步:synchronized锁住的是代码还是对象

Java多线程4:synchronized锁机制

Java多线程5:Synchronized锁机制的更多相关文章

  1. java 多线程8 : synchronized锁机制 之 方法锁

    脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数 ...

  2. Java多线程学习——synchronized锁机制

    Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用) 例子:两个人操作同一个银行账户,丈夫在ATM ...

  3. java 多线程9 : synchronized锁机制 之 代码块锁

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  4. java 多线程并发 synchronized 同步机制及方式

    2. 锁机制 3. 并发 Excutor框架 4. 并发性与多线程介绍 1. synchronized  参考1. synchronized 分两种方式进行线程的同步:同步块.同步方法 1. 方法同步 ...

  5. Java多线程,对锁机制的进一步分析

    1 可重入锁 可重入锁,也叫递归锁.它有两层含义,第一,当一个线程在外层函数得到可重入锁后,能直接递归地调用该函数,第二,同一线程在外层函数获得可重入锁后,内层函数可以直接获取该锁对应其它代码的控制权 ...

  6. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  7. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

  8. Java多线程系列--“JUC锁”05之 非公平锁

    概要 前面两章分析了"公平锁的获取和释放机制",这一章开始对“非公平锁”的获取锁/释放锁的过程进行分析.内容包括:参考代码获取非公平锁(基于JDK1.7.0_40)释放非公平锁(基 ...

  9. Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

    概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 ...

随机推荐

  1. HashMap 1.8

    1.重要参数 和1.7中的相同,不在赘述.变化之处在于table不在是Entry类型而是Node类型,即1.8中拉链法中的节点类型变为Node.但其实结构并没有发生很大的变化,1.8中的HashMap ...

  2. centos7 搭建openvpn服务器

    OpenVPN是一个开源代码的VPN应用程序,可让您在公共互联网上安全地创建和加入专用网络.相比pptp,openvpn更稳定.安全. 本篇博客主要介绍下面两点: 1. Centos 7下安装与配置O ...

  3. leetcode 74. Search a 2D Matrix 、240. Search a 2D Matrix II

    74. Search a 2D Matrix 整个二维数组是有序排列的,可以把这个想象成一个有序的一维数组,然后用二分找中间值就好了. 这个时候需要将全部的长度转换为相应的坐标,/col获得x坐标,% ...

  4. IDEA+'mvn' 不是内部或外部命令

    问题描述: 提示'mvn' 不是内部或外部命令,也不是可运行的程序或批处理文件. 或者提示 The JAVA_HOME environment variable is not defined corr ...

  5. Docker镜像存储-overlayfs

    一.概述 Docker中的镜像采用分层构建设计,每个层可以称之为“layer”,这些layer被存放在了/var/lib/docker/<storage-driver>/目录下,这里的st ...

  6. Java多线程编程核心技术(二)对象及变量的并发访问

    本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...

  7. WPF仿网易云音乐系列(一、左侧菜单栏:Expander+RadioButton)

    1.简介 上一篇咱们说到,网易云音乐的左侧菜单栏可以通过Expander+RadioButton来实现,具体如何实现,咱们下面开始干: 首先来一张网易云音乐PC版原图(个人觉得PC版比UWP版左侧菜单 ...

  8. IT程序员的抉择:我要离开帝都了

    不知不觉在北京已经漂泊了近5年了,共为3家公司打过工,其中有几十人的小公司,也有几万人的大公司.随着工作技能的提升和工作经验的积累,薪水自然也涨了不少,但是看着北京的房价.物价飞涨,感觉自己赚多少都是 ...

  9. 《React Native 精解与实战》书籍连载「Node.js 简介与 React Native 开发环境配置」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  10. flask使用基础

    1.安装 pip install Flask 基本依赖库: jinja2:实现对模板的处理 werkzeug:本质是socket服务器,用于接收http请求,并对请求进行预处理,然后触发Flaks框架 ...