一、前言

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

二、引入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. PHP HMAC_SHA1 算法 生成算法签名

    HMAC_SHA1(Hashed Message Authentication Code, Secure Hash Algorithm)是一种安全的基于加密hash函数和共享密钥的消息认证协议. 它可 ...

  2. day14(1)--递归、匿名函数、内置函数

    一.递归 定义:本质上是回溯和递推 回溯:询问答案的过程 递推:推出答案的过程 前提: 回溯到一个有结果的值开始递推 回溯与递推的条件要有规律 方式: 直接递归:自己调用自己 间接递归:通过别人来调用 ...

  3. eclipse导入maven项目,资源文件位置显示不正确

    eclipse导入maven项目后,资源文件位置显示不正确,如下图所示 解决方法: 在resources上右键Build Path,选择Use as Source Folder即可正确显示资源文件

  4. lightoj-1128-Greatest Parent(二分+LCA)

    传送门 首先我要实力吐槽这个lightoj 它给我的注册密码藏在不为人所见的地方 注册注册了10多分钟 qwq -------------------------------------------- ...

  5. macOS10.14 Mojave无法打开和预览jpg的解决方法

    分析:之所以会出现这样的问题.是因为你用了独显 而没有驱动核显导致的.想要预览正常只要用核显或者开启核显加速就OK了就可以正常预览了. 解决办法:换无核显的机型试试,比如MacPro6.1,iMac ...

  6. WPF touch Scroll -触摸滚动

    借鉴地址:http://matthamilton.net/touchscrolling-for-scrollviewer 改造后支持上下和左右鼠标拖动滚动: using System; using S ...

  7. Shiro安全框架入门笔记

    入门 1.simpleRealmTest package cn.realm; import org.apache.shiro.SecurityUtils; import org.apache.shir ...

  8. C#.NET 大型通用信息化系统集成快速开发平台 4.1 版本 - 严格的用户账户审核功能

    整个集团有几万个用户,一个个用户添加是不现实的,只有每个公司的系统管理员添加.或者用户申请帐户,然后有相应的管理员审核,才会更准确一些. 每个公司.分公司.部门的账户情况只有所在公司的管理员是最清楚的 ...

  9. Web测试和App测试有什么区别

    WEB测试和App测试从流程上来说,没有区别.都需要经历测试计划方案,用例设计,测试执行,缺陷管理,测试报告等相关活动.从技术上来说,WEB测试和APP测试其测试类型也基本相似,都需要进行功能测试.性 ...

  10. 前端自动化 shell 脚本命令 与 shell-node 脚本命令 简单使用 之 es6 转译

    (背景: 先用 babel 转译 es6 再 用 browserify 打包 模块化文件,来解决浏览器不支持模块化 )(Browserify是一个让node模块可以用在浏览器中的神奇工具) 今天折腾了 ...