Java多线程5:Synchronized锁机制

时间:2024-07-10 17:05:14

一、前言

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

二、引入Synchronized锁机制

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

Java多线程5: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);
}
}
}

反编译的结果:

Java多线程5:Synchronized锁机制

Java多线程5:Synchronized锁机制

关于图中标红的两条指令,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);
}
}

反编译结果:

Java多线程5:Synchronized锁机制

  从反编译结果来看,方法的同步并没有通过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);
}
}

反编译结果:

Java多线程5:Synchronized锁机制

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

通过实例验证:

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

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

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

  上述讲解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锁机制