初识 synchronized


前言

提示:后续补充 synchronized 关键字分析(重量级锁,cas等)

题主 synchronized 关键字一直用的少,今天开贴记录下其用法和特性。


提示:以下案例线程5 和其他线程用的不是同一个实例

一、synchronized 用来做什么?

示例:synchronized 关键字一直被用来做同步锁,其中加锁方式有许多,比如修饰代码块,修饰方法,其作用不尽相同,话不多说直接上示例

1. 对象锁

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPool {

    private static ThreadPoolExecutor threadPoolExecutor;

    private static String a = "a";

    public ThreadPool() {

    }

    public void syncObjectLock() throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("name: " + currentThread.getName() + " time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this) {
            System.out.println(currentThread.getName() + " -start:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            currentThread.sleep(1000);
            System.out.println(currentThread.getName() + " -end:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }

    static class ThreadDemo implements Runnable {
        private ThreadPool threadPool;

        public ThreadDemo(ThreadPool threadPool) {
            this.threadPool = threadPool;
        }

        @Override
        public void run() {
            try {
                threadPool.syncObjectLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool();
        ThreadPool threadPool1 = new ThreadPool();
        Thread thread1 = new Thread(new ThreadDemo(threadPool), "t_1");
        Thread thread2 = new Thread(new ThreadDemo(threadPool), "t_2");
        Thread thread3 = new Thread(new ThreadDemo(threadPool), "t_3");
        Thread thread4 = new Thread(new ThreadDemo(threadPool), "t_4");
        Thread thread5 = new Thread(new ThreadDemo(threadPool1), "t_5"); //这里使用的是另一个实例 threadpool1
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
    }
}

结果:可以看到 上文代码线程5 和线程 1 锁的是不同的对象,虽然对this 加锁,但是线程5 和 1 是同时进入同步代码块。可以看出对object 加锁在不同的实例上是锁不住的。
在这里插入图片描述

2.类锁(执行代码见图一的mian方法)

public void syncObjectLock() throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("name: " + currentThread.getName() + " time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (ThreadPool.class) { // 对 ThreadPool整个类上锁
            System.out.println(currentThread.getName() + " -start:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            currentThread.sleep(1000);
            System.out.println(currentThread.getName() + " -end:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }

结果:可以看到 线程5 和其他线程都是排队进入的
在这里插入图片描述

3. 对整个方法上锁

代码如下(示例):

public synchronized void syncObjectLock() throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("name: " + currentThread.getName() + " time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        System.out.println(currentThread.getName() + " -start:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        currentThread.sleep(1000);
        System.out.println(currentThread.getName() + " -end:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }

结果:可以看到,线程5 和线程3 是同时进来的,方法上锁在不同的对象里边是不存在同步的
在这里插入图片描述

3.1 对同一对象的不同方法上锁

public class Test {
    public static void main(String[] args) throws Exception {
        Test test = new Test();
        new Thread(() -> test.test1()).start();
        new Thread(() -> test.test2()).start();
    }

    public synchronized void test1() {
        System.out.println("锁方法一");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test1 end");
    }

    public synchronized void test2() {
        System.out.println("锁方法二");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 end");
    }

    class task1 implements Runnable {
        @Override
        public void run() {
            test1();
        }
    }
    
    class task2 implements Runnable {
        @Override
        public void run() {
            test2();
        }
    }
}

执行结果:
在这里插入图片描述
可以看到代码结果是顺序执行的,虽然test1() 休眠了200 毫秒,test2() 休眠100 毫秒,结果还是 先等test1() 执行完毕才能执行test2();
各位看到这是不是很疑惑,下面等我们解析完锁的原理相信大家就清晰了。

3.1 对同一对象的不同方法上锁(一个上锁方法,一个非上锁方法)

public class Test {
    public static void main(String[] args) throws Exception {
        Test test = new Test();
        new Thread(() -> test.test1()).start();
        new Thread(() -> test.test2()).start();
    }

    public synchronized void test1() {
        System.out.println("锁方法一");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test1 end");
    }

    public void test2() {
        System.out.println("锁方法二");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 end");
    }

    class task1 implements Runnable {
        @Override
        public void run() {
            test1();
        }
    }
    
    class task2 implements Runnable {
        @Override
        public void run() {
            test2();
        }
    }
}

结果:
在这里插入图片描述
充分说明了 monitor 监视器 是对象维度的,一个 实例对象/类对象 ,一个 monitor 监视器;

4. 锁静态变量

public void syncObjectLock() throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("name: " + currentThread.getName() + " time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (a) {
            System.out.println(currentThread.getName() + " -start:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            currentThread.sleep(1000);
            System.out.println(currentThread.getName() + " -end:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }

结果:可以看到所有线程都是顺序执行的,所以静态变量上锁也是类级别的锁,不管多少实例都是锁住的
在这里插入图片描述

5. 锁静态方法

public static synchronized  void syncObjectLock() throws InterruptedException {
        Thread currentThread = Thread.currentThread();
        System.out.println("name: " + currentThread.getName() + " time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        System.out.println(currentThread.getName() + " -start:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        currentThread.sleep(1000);
        System.out.println(currentThread.getName() + " -end:" + new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }

结果:所有线程顺序进出,作用和锁住静态变量一样
在这里插入图片描述


二、锁的实现原理

如果对上面的执行结果还有疑问,也先不用急,我们先来了解Synchronized的原理,再回头上面的问题就一目了然了。我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

1. 同步代码块

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

反编译结果:
在这里插入图片描述
关于这两条指令的作用,我们直接参考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 的所有者,当执行monitorexit 指令时,monitor 记录数-1,当 monitor 记录数为0 时候,其他线程可以获得当前monitor ;

通过这两段话可以看出来,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
在这里插入图片描述

2. 同步方法

我们再来看一下同步方法的反编译结果:

public class SynchronizedMethod {
    public synchronized void method() {
        System.out.println("Hello World!");
    }
}

反编译结果:
在这里插入图片描述
从上图可以看出,同步方法是通过 一个ACC_SYNCHRONIZED 标识来判定的,当线程进入方法的时候,给方法加上一个同步标识,如果设置了,则调用指令 获取monitor,在方法执行期间,任何线程都无法再获取 monitor,直到当前线程执行方法完毕,再释放monitor,其本质还是通过monitor 实现,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

三、原理解释上述示例代码

1. 对象锁 示例代码解释:
threadPool 和 threadPool1 是不同的实例,所以 t5 线程 和t1 线程自然是获取的不同的 monitor 对象,所以 t5 线程 和 t1 线程出现了并行的情况;

2. 类锁 示例代码解释:
ThreadPool.class 是对整个class 对象加的全局的 monitor 监视器,所以所有的 ThreadPool 实例都会受这把锁约束(不同线程不同实例访问当前代码块时候都会是串行执行)虽然threadPool 和 threadPool1 是不同的实例 但是也是被串行化了;

3. 对整个方法上锁 示例代码解释:
由于threadPool 和 threadPool1 是不同的实例,所以在执行同步方法的时候,所以 t5 线程 和t1 线程自然是获取的不同的 monitor 对象,所以在不同对象也是无法锁住的;

3.1 对同一对象的不同方法上锁 示例代码解释:
这个是最令我意外的,不同线程,同一实例,访问不同的同步方法,结果也出现了串行执行的现象;
在同一个实例中 一个时间只能一个线程获取monitor 监视器,只有在 访问同步方法时候才需要获取monitor 监视器,所以在不同线程访问不同的同步方法的时候,只能在第一个方法在完全执行完,才能执行第二个同步方法; 如果我们是多线程访问一个同步方法,一个非同步方法,则不会出现这个现象;3.2 代码示例做了验证;

4. 锁静态变量 静态方法和静态变量都是类维护,不同实例,不同线程都获取的 同一 monitor ,串行化;

总结

对对象和方法上锁,作用级别在同一个实例中才有效,对静态变量/静态方法上锁 为类级别的锁,在整个类级别生效,不管你是否同一个实例中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值