关于Java中的synchronized用法介绍

介绍

synchronized分为对象锁和类锁

  • 对象锁:在java中每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。

  • 类锁:在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。

以上的实现分别作用于实例方法,静态方法,代码块中锁this或者class。 由于synchronized是可重入锁,就带上这个性质进行写demo了,话不多说看例子吧。

锁实例方法v1

package sync;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zhangyong
 * @Date 2020/8/8 17:21
 */
public class SyncTest implements Runnable{

    private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        try {//调用synchronized实例方法,两次调用的都是对象t
            if ("one_thread".equals(Thread.currentThread().getName())){
                System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
                sync1();
            }else {
                System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
                sync2();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void sync1() throws InterruptedException {

        System.out.println("sync1"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
        Thread.sleep(1000);
        sync2();
        System.out.println("sync1"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public synchronized void sync2() throws InterruptedException {

        System.out.println("sync2"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
        Thread.sleep(1000);
        System.out.println("sync2"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public static void main(String[] args) {
        SyncTest t = new SyncTest(); //传入的对象是一个t
        Thread thread = new Thread(t,"one_thread");  //thread传入的runnable会被执行
        Thread thread1 = new Thread(t,"two_thread");
        thread.start();
        thread1.start();
    }
}

执行结果
one_threadrun on > 2020-08-09 17:12:30
two_threadrun on > 2020-08-09 17:12:30
sync1one_thread start>>>>2020-08-09 17:12:30
sync2one_thread start>>>>2020-08-09 17:12:31
sync2one_thread end >>>>2020-08-09 17:12:32
sync1one_thread end >>>>2020-08-09 17:12:32
sync2two_thread start>>>>2020-08-09 17:12:32
sync2two_thread end >>>>2020-08-09 17:12:33

总结:如上代码,两个线程(one,two)使用的是一个对象t ,在执行同步方法时会进行同步,先执行到达的线程(one)。然后占有锁,由于synchronized是可重入锁,在sync1方法中我又调用了sync2,目的是证明可重入(当前线程可以再调带锁实例方法,计数器栈深+1),通过执行结果可以判断出two是等到线程one执行完释放锁才能获取锁运行的。

锁实例方法v2

锁实例方法,如果调用的线程是不同对象呢?看下面demo(只改了main里面的代码,不同对象)

package sync;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zhangyong
 * @Date 2020/8/8 17:21
 */
public class SyncTest implements Runnable{

    private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        try {//调用synchronized实例方法,两次调用的都是对象t
            if ("one_thread".equals(Thread.currentThread().getName())){
                System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
                sync1();
            }else {
                System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
                sync2();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void sync1() throws InterruptedException {

        System.out.println("sync1"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
        Thread.sleep(1000);
        sync2();
        System.out.println("sync1"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public synchronized void sync2() throws InterruptedException {

        System.out.println("sync2"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
        Thread.sleep(1000);
        System.out.println("sync2"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new SyncTest(),"one_thread");  //thread传入的runnable会被执行
        Thread thread1 = new Thread(new SyncTest(),"two_thread");
        thread.start();
        thread1.start();
    }
}

one_threadrun on > 2020-08-09 17:18:48
two_threadrun on > 2020-08-09 17:18:48
sync2two_thread start>>>>2020-08-09 17:18:48 ---------
sync1one_thread start>>>>2020-08-09 17:18:48
sync2one_thread start>>>>2020-08-09 17:18:49
sync2two_thread end >>>>2020-08-09 17:18:49 ---------
sync2one_thread end >>>>2020-08-09 17:18:50
sync1one_thread end >>>>2020-08-09 17:18:50

总结:根据输出结果可以看出,one/two两个线程各跑各的,根本没同步。

综上得出结论:多线程环境下,锁实例方法需要是同一对象。这样synchronized实例方法才能达到同步的效果。

锁静态方法

package sync;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zhangyong
 * @Date 2020/8/8 17:21
 */
public class SyncTest implements Runnable{

    private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        try {//调用synchronized实例方法,两次调用的都是对象t
            if ("one_thread".equals(Thread.currentThread().getName())){
                System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
                sync1();
            }else {
                System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
                sync2();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized static void sync1() throws InterruptedException {
        System.out.println("sync1"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
        Thread.sleep(1000);
        sync2();
        System.out.println("sync1"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public synchronized static void sync2() throws InterruptedException {
        System.out.println("sync2"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
        Thread.sleep(1000);
        System.out.println("sync2"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest t = new SyncTest();
        Thread thread = new Thread(t,"one_thread");  //thread传入的runnable会被执行
        Thread thread1 = new Thread(t,"two_thread");
        thread.start();
        thread1.start();

        Thread.sleep(5000);
        System.out.println("######################################");

        Thread one = new Thread(new SyncTest(),"one_thread");  //thread传入的runnable会被执行
        Thread two = new Thread(new SyncTest(),"two_thread");
        one.start();
        two.start();
    }
}


two_threadrun on > 2020-08-09 17:32:29
one_threadrun on > 2020-08-09 17:32:29
sync2two_thread start>>>>2020-08-09 17:32:30
sync2two_thread end >>>>2020-08-09 17:32:31
sync1one_thread start>>>>2020-08-09 17:32:31
sync2one_thread start>>>>2020-08-09 17:32:32
sync2one_thread end >>>>2020-08-09 17:32:33
sync1one_thread end >>>>2020-08-09 17:32:33
######################################
one_threadrun on > 2020-08-09 17:32:34
two_threadrun on > 2020-08-09 17:32:34
sync1one_thread start>>>>2020-08-09 17:32:34
sync2one_thread start>>>>2020-08-09 17:32:35
sync2one_thread end >>>>2020-08-09 17:32:36
sync1one_thread end >>>>2020-08-09 17:32:36
sync2two_thread start>>>>2020-08-09 17:32:36
sync2two_thread end >>>>2020-08-09 17:32:37

总结:上面synchronized都是加到了static静态方法上面,为了方便看,我把相同对象和不同对象调用都写在上面的代码中了,可以看到,无论是相同的对象还是不同的对象,两个线程都是同步执行的。对于sync1调用,可重入性也成立。

锁代码块this

package sync;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zhangyong
 * @Date 2020/8/8 17:21
 */
public class SyncTest implements Runnable{

    private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        try {//调用synchronized实例方法,两次调用的都是对象t
            System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
            sync4();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void sync4() throws InterruptedException {
        synchronized (this){
            System.out.println("sync4"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
            Thread.sleep(1000);
            System.out.println("sync4"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
        }
        Thread.sleep(100);   //测试代码块外面的代码同步情况
        System.out.println("sync4 outside >>> "+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));

    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest t = new SyncTest();
        Thread thread = new Thread(t,"one_thread");  //thread传入的runnable会被执行
        Thread thread1 = new Thread(t,"two_thread");
        thread.start();
        thread1.start();

        Thread.sleep(5000);
        System.out.println("######################################");

        Thread one = new Thread(new SyncTest(),"one_thread");  //thread传入的runnable会被执行
        Thread two = new Thread(new SyncTest(),"two_thread");
        one.start();
        two.start();
    }
}

1.one_threadrun on > 2020-08-09 17:46:28
2.two_threadrun on > 2020-08-09 17:46:28
3.sync4one_thread start>>>>2020-08-09 17:46:28
4.sync4one_thread end >>>>2020-08-09 17:46:29
5.sync4two_thread start>>>>2020-08-09 17:46:29
6.sync4 outside >>> one_thread end >>>>2020-08-09 17:46:29
7.sync4two_thread end >>>>2020-08-09 17:46:30
8.sync4 outside >>> two_thread end >>>>2020-08-09 17:46:30
######################################
9.one_threadrun on > 2020-08-09 17:46:33
10.two_threadrun on > 2020-08-09 17:46:33
11.sync4one_thread start>>>>2020-08-09 17:46:33
12.sync4two_thread start>>>>2020-08-09 17:46:33
13.sync4two_thread end >>>>2020-08-09 17:46:34
14.sync4one_thread end >>>>2020-08-09 17:46:34
15.sync4 outside >>> one_thread end >>>>2020-08-09 17:46:34
16.sync4 outside >>> two_thread end >>>>2020-08-09 17:46:34

总结:synchronized锁this代码块跟最开始测试的锁实例方法相比结果是一样的,相同对象可以同步,不同对象同步不了。 区别在于:锁代码块时当前方法除了代码块的代码是非同步的。这样可以灵活使用,提供效率。

锁代码块class

package sync;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zhangyong
 * @Date 2020/8/8 17:21
 */
public class SyncTest implements Runnable{

    private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        try {//调用synchronized实例方法,两次调用的都是对象t
            System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
            sync4();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void sync4() throws InterruptedException {
        synchronized (SyncTest.class){
            System.out.println("sync4"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
            Thread.sleep(1000);
            System.out.println("sync4"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
        }
        Thread.sleep(100);
        System.out.println("sync4 outside >>> "+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));

    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest t = new SyncTest();
        Thread thread = new Thread(t,"one_thread");  //thread传入的runnable会被执行
        Thread thread1 = new Thread(t,"two_thread");
        thread.start();
        thread1.start();

        Thread.sleep(5000);
        System.out.println("######################################");

        Thread one = new Thread(new SyncTest(),"one_thread");  //thread传入的runnable会被执行
        Thread two = new Thread(new SyncTest(),"two_thread");
        one.start();
        two.start();
    }
}

two_threadrun on > 2020-08-09 21:23:08
one_threadrun on > 2020-08-09 21:23:08
sync4two_thread start>>>>2020-08-09 21:23:08
sync4two_thread end >>>>2020-08-09 21:23:09
sync4one_thread start>>>>2020-08-09 21:23:09
sync4 outside >>> two_thread end >>>>2020-08-09 21:23:09
sync4one_thread end >>>>2020-08-09 21:23:10
sync4 outside >>> one_thread end >>>>2020-08-09 21:23:10
######################################
one_threadrun on > 2020-08-09 21:23:13
two_threadrun on > 2020-08-09 21:23:13
sync4one_thread start>>>>2020-08-09 21:23:13
sync4one_thread end >>>>2020-08-09 21:23:14
sync4two_thread start>>>>2020-08-09 21:23:14
sync4 outside >>> one_thread end >>>>2020-08-09 21:23:14
sync4two_thread end >>>>2020-08-09 21:23:15
sync4 outside >>> two_thread end >>>>2020-08-09 21:23:15

总结:synchronized锁class代码块的时候跟锁static方法类似,不同对象都能触发同步。缩小代码范围了。

锁实例&&静态

package sync;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @Author zhangyong
 * @Date 2020/8/8 17:21
 */
public class SyncTest implements Runnable{

    private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        try {//调用synchronized实例方法,两次调用的都是对象t
            System.out.println(Thread.currentThread().getName() + "run on > "+dtf.format(LocalDateTime.now()));
            if ("one_thread".equals(Thread.currentThread().getName())){
                sync4();
            }else{
                sync3();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void sync3() throws InterruptedException {
        synchronized (SyncTest.class){
            System.out.println("sync3"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
            Thread.sleep(1000);
            System.out.println("sync3"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
        }
        Thread.sleep(100);
        System.out.println("sync3 outside >>> "+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public void sync4() throws InterruptedException {
        synchronized (this){
            System.out.println("sync4"+Thread.currentThread().getName()+"  start>>>>"+dtf.format(LocalDateTime.now()));
            Thread.sleep(1000);
            System.out.println("sync4"+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
        }
        Thread.sleep(100);
        System.out.println("sync4 outside >>> "+Thread.currentThread().getName()+"  end  >>>>"+dtf.format(LocalDateTime.now()));
    }

    public static void main(String[] args) throws InterruptedException {
        SyncTest t = new SyncTest();
        Thread thread = new Thread(t,"one_thread");  //thread传入的runnable会被执行
        Thread thread1 = new Thread(t,"two_thread");
        thread.start();
        thread1.start();

        Thread.sleep(5000);
        System.out.println("######################################");

        Thread one = new Thread(new SyncTest(),"one_thread");  //thread传入的runnable会被执行
        Thread two = new Thread(new SyncTest(),"two_thread");
        one.start();
        two.start();
    }
}

one_threadrun on > 2020-08-09 21:28:51
two_threadrun on > 2020-08-09 21:28:51
sync4one_thread start>>>>2020-08-09 21:28:51
sync3two_thread start>>>>2020-08-09 21:28:51
sync4one_thread end >>>>2020-08-09 21:28:52
sync3two_thread end >>>>2020-08-09 21:28:52
sync4 outside >>> one_thread end >>>>2020-08-09 21:28:52
sync3 outside >>> two_thread end >>>>2020-08-09 21:28:52
######################################
one_threadrun on > 2020-08-09 21:28:56
two_threadrun on > 2020-08-09 21:28:56
sync4one_thread start>>>>2020-08-09 21:28:56
sync3two_thread start>>>>2020-08-09 21:28:56
sync4one_thread end >>>>2020-08-09 21:28:57
sync3two_thread end >>>>2020-08-09 21:28:57
sync4 outside >>> one_thread end >>>>2020-08-09 21:28:57
sync3 outside >>> two_thread end >>>>2020-08-09 21:28:57

总结:对于锁class和this这种静态and非静态的代码,不同对象和相同对象都是互不干扰的。可以理解为两个锁对象。

详解

  • synchronized用的锁是存在Java对象头里的。
  • Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。
  • monitor object 充当着维护 mutex以及定义 wait/signal API 来管理线程的阻塞和唤醒的角色。
  • Java 对象存储在内存中,分别分为三个部分,即对象头、实例数据和对齐填充,而在其对象头中,保存了锁标识;同时,java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法,这些方法的具体实现,依赖于一个叫 ObjectMonitor 模式的实现,这是 JVM 内部基于 C++ 实现的一套机制
  • JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。
  • 根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

Demo

下面是一个demo,i++是非原子性操作,在执行的时候需要加锁,如下demo所示。

package transfer;

import java.util.concurrent.CountDownLatch;

/**
 * @Author zhangyong
 * @Date 2020/8/14 16:15
 */
public class Add implements Runnable{

    private static int count = 0;   //静态变量多线程操作不安全
    private int instanceCount = 0;  //实例变量多线程操作不安全

    public static void main(String[] args) throws InterruptedException {
        Add a = new Add();
        for (int i = 0; i < 1000; i++) {
            new Thread(a).start();
        }
        Thread.sleep(3000);       //等线程跑完,用CountDownLatch也行,为了不增加复杂度不加了就。
        System.out.println(count+"---"+a.instanceCount);   //998801---998928
    }

    @Override
    public void run() {
//        synchronized (this){    //解开注释,进行同步就是正确的数值1000000了、
            for (int i = 0; i < 1000; i++) {
                count++;
                instanceCount++;
            }
//        }
    }
}

对于静态变量还是成员变量,静态方法还是成员方法都无所谓,不加锁就是多线程不安全的。

参考:
https://segmentfault.com/a/1190000016417017
https://zhuanlan.zhihu.com/p/29866981

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页