一张图讲解对象锁和关键字 synchronized 修饰方法 (代码块)

每个对象在出生的时候就有一把钥匙(监视器 Monitor),那么被 synchronized 修饰的方法相当于给方法加了一个锁,这个方法就可以进行同步,在多线程的时候,不会出现线程安全问题。

注:Monitor 是 Java 中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class 的锁。每一个对象都有,也仅有一个 Monitor。

下面通过一张图片进行讲解:

1. 一张图片

图片看不清,请点击这里 : 高清大图

2. 图片对应的代码


import java.util.Date;

/**
 * 测试的object类
 *
 * @author:dufy
 * @version:1.0.0
 * @date 2017/9/29
 * @email 742981086@qq.com
 */
public class ObjectTest {

    public synchronized  void methodA(){
        try {
            System.out.println("This is methodA ...." + Thread.currentThread().getName() + ": " + new Date());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public void methodB(){

        System.out.println("This is methodB ...." + Thread.currentThread().getName() + ": " + new Date());
    }

    public synchronized void methodC(){

        try {
            System.out.println("This is methodC ...." + Thread.currentThread().getName() + ": " + new Date());
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

package com.dufy.concurrentcode;

/**
 * 测试线程类
 *
 * @author:dufy
 * @version:1.0.0
 * @date 2017/9/29
 * @email 742981086@qq.com
 */
public class ThreadTest extends Thread{

    public static void main(String[] args) {
        ObjectTest ot = new ObjectTest();
        Thread1 t1 = new Thread1(ot,"thread1");
        Thread2 t2 = new Thread2(ot,"thread2");
        Thread3 t3 = new Thread3(ot,"thread3");
        Thread4 t4 = new Thread4(ot,"thread4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

    static class Thread1 extends Thread{
        private ObjectTest objectTest;

        public Thread1(ObjectTest objectTest,String name){
            setName(name);
            this.objectTest = objectTest;
        }
        @Override
        public void run() {
            super.run();
            objectTest.methodA();
        }
    }

    static class Thread2 extends Thread{
        private ObjectTest objectTest;

        public Thread2(ObjectTest objectTest,String name){
            setName(name);
            this.objectTest = objectTest;
        }
        @Override
        public void run() {
            super.run();
            objectTest.methodB();
        }
    }

    static class Thread3 extends Thread{
        private ObjectTest objectTest;

        public Thread3(ObjectTest objectTest,String name){
            setName(name);
            this.objectTest = objectTest;
        }
        @Override
        public void run() {
            super.run();
            objectTest.methodA();
        }
    }

    static class Thread4 extends Thread{
        private ObjectTest objectTest;

        public Thread4(ObjectTest objectTest,String name){
            setName(name);
            this.objectTest = objectTest;
        }
        @Override
        public void run() {
            super.run();
            objectTest.methodC();
        }
    }
}

运行结果:

This is methodB ....thread2: Fri Sep 29 23:21:17 CST 2017
This is methodA ....thread1: Fri Sep 29 23:21:17 CST 2017
This is methodC ....thread4: Fri Sep 29 23:21:18 CST 2017
This is methodA ....thread3: Fri Sep 29 23:21:21 CST 2017

注:
1、运行的结果可能和上图讲的线程流程不同,没有关系,只要理解对象锁和 synchronized 的核心思想就好,线程的运行本来就是具有随机性这个特点。
2、此段代码是同步方法,其实同步的代码块也是一个道理,同步代码块用 synchronized(this) 时候,当一个线程访问 object 的一个 synchronized(this) 同步代码块的时候,其他线程对 object 中所有其他的 synchronized(this) 同步的代码块访问都被阻塞 (阻塞的是同步代码块,线程依然可以进入同步代码块的方法)。

3. 总结

  1. 每个对象都有一把锁(对象监视器),关键字 synchronized 取得锁都是对象锁,而不是把一段代码或方法(函数)当做锁。
  2. 上图所示,哪个线程先执行带有 synchronized 关键字的方法,哪个线程就持有这个方法所属对象的钥匙。其他线程只能处于等待状态。
  3. 调用关键字 synchronized 声明的方法,一定是排队运行的。这才是使用 synchronized 关键字的作用,排队运行,如果有共享资源的话,那么共享资源的读取就是线程安全的。
  4. 如果一个线程持有 object 的钥匙,那么其他线程可以访问 object 对象没有上锁的方法,也就是非 synchronized 类型的方法。

4.Monitor 和线程关系

首先看一下线程和 Monitor 之间关系,以 及线程的状态转换图。通过图讲解一下整个过程。

上图分为三块:Entry Set(进入区) 、The Owner(拥有区)、Wait Set(等待区)。

  • Entry Set(进入区):表示线程通过 synchronized 要求获取对象的锁。如果对象未被锁住, 则迚入拥有者; 否则则在进入区等待。一旦对象锁被其他线程释放, 立即参与竞争。

  • The Owner(拥有区):表示某一线程成功竞争到对象锁。

  • Wait Set(等待区):表示线程通过对象的 wait 方法, 释放对象的锁, 并在等待区等待被唤醒。

从图中可以看出,一个 Monitor 在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。

上面的内容讲解引自 :Java 命令学习系列(二)——Jstack 中关于 Monitor 的讲解。

5. 参考

Java 多线程编程核心技术