0.4、多线程学习——内置锁(监视器锁)、对象 和 Synchronized 代码块

本文深入解析Java中的synchronized关键字,探讨其在并发编程中的作用、字节码层面的实现原理、对象锁与监视器锁的区别,以及如何正确使用synchronized避免线程安全问题。通过实例演示synchronized在不同场景下的应用,包括静态方法、代码块和内置锁的公平性。

前言

体能状态先于精神状态,习惯先于决心,聚焦先于喜好。

synchronized 关键字

Java 提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block).
使用 synchronized 关键字修饰一个方法,或者在一个方法里修饰一个代码块。

synchronized 和字节码

synchronized 关键字在编译之后会被体现在字节码中,其对应两个字节码 monitorenter和 monitorexit,而这两个字节码分别对应了Java内存模型中的 8个基本命令的 lock 和 unlock 命令。lock命令会锁定主内存中涉及到的变量,unlock则会解锁,锁定之后,其他线程无法操作与该对象内置锁相关的代码块或方法的代码,同时也无法从主内存同步涉及到的变量值,unlock命令执行前,当前线程会将工作内存的相关变量同步到主内存。

对象和内置锁(监视器锁)

每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock).
当 synchronized 修饰一个方法时,那么其所以来的对象就是当前类的对象,只有通过这个对象调用该方法才可以同步不同的线程,当这个方法是static修饰的静态方法时,所有调用该方法的线程都将实现同步。
当synchronized 修饰一个方法中的代码块时需要明确指定一个对象,可以是单独声明的一个对象,也可以是 “this”表示本类,对于单独声明的对象来说,其如果被static 修饰,那么这个代码块可以启动同步任何调用该代码块的线程的作用,否则,其只能作用于拥有对应对象的线程的作用。

synchronized 需要借助于一个对象

synchronized 需要借助于一个Java 对象来获得内置锁。当这个对象被多个线程共享的时候,相关的线程在这个代码块是同步的。
所以务必注意,synchronized 修饰的方法或者代码块涉及的对象是否有被不同的线程关联到。一般而言,使用static 修饰的方法或者全局变量可以确保关联到同一个对象。

synchronized 修饰的方法可以重入

这个概念是和重入锁的概念比较的
你可以在一个 synchronized 修饰的方法中调用另一个 synchronized 修饰的方法,程序不会出现问题。

synchronized 的锁解除

一种是代码运行结束,unlock了,内置锁解除。
另一种是抛出异常,锁也会解除。
再一种是 Object.wait()方法,线程会释放锁,但是同时会等待其他线程Object.notify(),这种释放如果没有唤醒会造成线程永久阻塞。
但是没有其他主动解除锁的机制,比如锁中断机制——彻底的接触锁,而不是先释放然后再次尝试获取。

synchronized 内置锁的公平性

无法保证线程获得锁的公平性,也无法保证线程优先级。

wait 和 notify 必须在 synchronized 代码块中展示

wait 和 notify 必须在 synchronized 代码块中展示。
使用 wait 和 notify的前提是获取了对象锁,所以单独使用(而不是在synchronized 代码块)时,编译期不会报错,但是运行期会报错。

synchronized 使用举例

我们将使用线程池做几个不同的实验,感受不使用同步、使用不同方式的同步所带来的不同的效果。

没有 synchronized 同步的例子

可以从运行结果看到,结果是无序的,每个方法打印开始和结束是混杂在一起的。

  • 代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedTest {
    private final static Object obj=new Object();

    /**
     * 使用 synchronized 修饰整个方法体
     *
     * @param name
     */
    public static void blockMethod(String name){
        System.out.println(name+" begin :"+System.currentTimeMillis());
        try {
            Random random=new Random();
            Thread.sleep(random.nextInt(1000));
        }catch(InterruptedException e){
            System.out.println("exception msg:"+e);
        }
        System.out.println(name+" end :"+System.currentTimeMillis());
    }

    /**
     * 一个内部类写一个线程
     */
    static class MyThread extends Thread{
        public MyThread(){}
        public MyThread(String name){this.name=name;}
        private String name;
        @Override
        public void run() {
            //调用 synchronized 修饰的方法
            blockMethod(name);
        }
    }

    /**
     *
     * @param args
     */
    public static void main(String [] args){
        ExecutorService pool=Executors.newFixedThreadPool(3);
        try{
            for (int i=0;i<3;i++){
                MyThread m=new MyThread("thread"+i);
                pool.submit(m);
            }
        }finally{
            pool.shutdown();
        }
    }
}
  • 输出结果:每个线程的开始和结束混杂在一起
thread1 begin :1564148430029
thread0 begin :1564148430032
thread2 begin :1564148430033
thread1 end :1564148430275
thread0 end :1564148430730
thread2 end :1564148430730

使用 synchronized 修饰静态方法
  • 代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedTest {
    private final static Object obj=new Object();

    /**
     * 使用 synchronized 修饰整个方法体
     *
     * @param name
     */
    public static synchronized void blockMethod(String name){
        System.out.println(name+" begin :"+System.currentTimeMillis());
        try {
            Random random=new Random();
            Thread.sleep(random.nextInt(1000));
        }catch(InterruptedException e){
            System.out.println("exception msg:"+e);
        }
        System.out.println(name+" end :"+System.currentTimeMillis());
    }

    /**
     * 一个内部类写一个线程
     */
    static class MyThread extends Thread{
        public MyThread(){}
        public MyThread(String name){this.name=name;}
        private String name;
        @Override
        public void run() {
            //调用 synchronized 修饰的方法
            blockMethod(name);
        }
    }

    /**
     * @param args
     */
    public static void main(String [] args){
        ExecutorService pool=Executors.newFixedThreadPool(3);
        try{
            for (int i=0;i<3;i++){
                MyThread m=new MyThread("thread"+i);
                pool.submit(m);
            }
        }finally{
            pool.shutdown();
        }
    }
}

  • 输出结果:每个线程的开始和结束是连接在一起的
thread1 begin :1564148603370
thread1 end :1564148603813
thread2 begin :1564148603813
thread2 end :1564148603941
thread0 begin :1564148603941
thread0 end :1564148604358
使用 synchronized 修饰代码块

这里使用全局静态对象作为内置锁对象,所以含synchronized 代码块的方法也必须是静态的。

  • 代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedTest {
    private final static Object obj=new Object();
    
    /**
     * 使用 synchronized 修饰方法体
     *
     * @param name
     */
    public static void  blockMethod(String name){
        synchronized (obj){
            System.out.println(name+" begin :"+System.currentTimeMillis());
            try {
                Random random=new Random();
                Thread.sleep(random.nextInt(1000));
            }catch(InterruptedException e){
                System.out.println("exception msg:"+e);
            }
            System.out.println(name+" end :"+System.currentTimeMillis());
        }

    }



    /**
     * 一个内部类写一个线程
     */
    static class MyThread extends Thread{
        public MyThread(){}
        public MyThread(String name){this.name=name;}
        private String name;
        @Override
        public void run() {
            blockMethod(name);
        }
    }

    /**
     * 你可以尝试去除 blockMethod() 方法中  synchronized 后运行main方法,看看有什么不同
     * @param args
     */
    public static void main(String [] args){
        ExecutorService pool=Executors.newFixedThreadPool(3);
        try{
            for (int i=0;i<3;i++){
                MyThread m=new MyThread("thread"+i);
                pool.submit(m);
            }
        }finally{
            pool.shutdown();
        }
    }
}
  • 结果
thread1 begin :1564151473516
thread1 end :1564151473625
thread2 begin :1564151473631
thread2 end :1564151473692
thread0 begin :1564151473693
thread0 end :1564151474385
synchronized(this)的正确和错误举例

使用 synchronized(this) 表示直接使用 该代码块所在方法对应的对象作为内置锁对象。
这样一般需要我们将内置锁对象通过线程的构造方法传递给线程。如果多个线程传递进去的是同一个对象,那么万事大吉,否则不同对象会因为对象不同而持有不同的内置锁,从而不再同步——线程不安全了。
安全和已读的角度看,一般就别用synchronized(this)了,而是用显示的 Object obj=new Object(); synchronized(obj)

正确用法:所有线程共享同一个对象
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedTest {
    private final static Object obj=new Object();

    /**
     * 使用 synchronized 修饰方法体
     *
     * @param name
     */
    public void  blockMethod(String name){
        synchronized (this){
            System.out.println(name+" begin :"+System.currentTimeMillis());
            try {
                Random random=new Random();
                Thread.sleep(random.nextInt(1000));
            }catch(InterruptedException e){
                System.out.println("exception msg:"+e);
            }
            System.out.println(name+" end :"+System.currentTimeMillis());
        }

    }

    /**
     * @param args
     */
    public static void main(String [] args){
        ExecutorService pool=Executors.newFixedThreadPool(3);
        try{
            //只创建一个对象
            SynchronizedTest s=new SynchronizedTest();
            for (int i=0;i<3;i++){
                MyThread m=new MyThread("thread"+i,s);
                pool.submit(m);
            }
        }finally{
            pool.shutdown();
        }

    }
}

/**
 * 一个内部类写一个线程
 */
class MyThread extends Thread{
    private String name;
    private SynchronizedTest s;
    public MyThread(){}
    public MyThread(String name,SynchronizedTest s){
        this.name=name;
        this.s=s;
    }

    @Override
    public void run() {
        s.blockMethod(name);
    }
}
  • 结果
thread1 begin :1564153234192
thread1 end :1564153234459
thread2 begin :1564153234459
thread2 end :1564153235232
thread0 begin :1564153235232
thread0 end :1564153236279
错误用法:每个线程传入一个新对象
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedTest {
    private final static Object obj=new Object();

    /**
     * 使用 synchronized 修饰方法体
     *
     * @param name
     */
    public void  blockMethod(String name){
        synchronized (this){
            System.out.println(name+" begin :"+System.currentTimeMillis());
            try {
                Random random=new Random();
                Thread.sleep(random.nextInt(1000));
            }catch(InterruptedException e){
                System.out.println("exception msg:"+e);
            }
            System.out.println(name+" end :"+System.currentTimeMillis());
        }

    }

    /**
     * @param args
     */
    public static void main(String [] args){
        ExecutorService pool=Executors.newFixedThreadPool(3);
        try{

            for (int i=0;i<3;i++){
                //每个线程传入一个新对象
                SynchronizedTest s=new SynchronizedTest();
                MyThread m=new MyThread("thread"+i,s);
                pool.submit(m);
            }
        }finally{
            pool.shutdown();
        }

    }
}

/**
 * 一个内部类写一个线程
 */
class MyThread extends Thread{
    private String name;
    private SynchronizedTest s;
    public MyThread(){}
    public MyThread(String name,SynchronizedTest s){
        this.name=name;
        this.s=s;
    }

    @Override
    public void run() {
        s.blockMethod(name);
    }
}

  • 结果
thread1 begin :1564152677663
thread0 begin :1564152677665
thread2 begin :1564152677665
thread2 end :1564152678085
thread1 end :1564152678284
thread0 end :1564152678376

synchronized实战举例

wait 和 notify 必须在 synchronized 代码块中展示
使用两个线程,交替打印字符A和B,各打印10次

synchronized(对象)
public static void main(String[] args) {
        TestB testB = new TestB();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {
                    synchronized (testB){
                        if(flag!=1){
                            try{
                                testB.wait();
                            }catch (InterruptedException e){

                            }
                        }
                        System.out.println("A"+i);
                        flag=2;
                        testB.notify();

                    }

                }

            }

        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {

                    synchronized (testB){
                        if(flag!=2){
                            try{
                                testB.wait();
                            }catch (InterruptedException e){

                            }
                        }
                        System.out.println("B"+i);
                        flag=1;
                        testB.notify();

                    }
                }

            }

        };
        executorService.execute(t1);
        executorService.execute(t2);

    }
synchronized(类.class)
public static void main2(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {
                    synchronized (TestB.class){
                        if(flag!=1){
                            try{
                                TestB.class.wait();
                            }catch (InterruptedException e){

                            }
                        }
                        System.out.println("A"+i);
                        flag=2;
                        TestB.class.notify();

                    }

                }

            }

        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {

                    synchronized (TestB.class){
                        if(flag!=2){
                            try{
                                TestB.class.wait();
                            }catch (InterruptedException e){

                            }
                        }
                        System.out.println("B"+i);
                        flag=1;
                        TestB.class.notify();

                    }
                }

            }

        };
        executorService.execute(t1);
        executorService.execute(t2);

    }
synchronized(this)

this 本质还是对象,即代码块所在的对象,所以本场景下,为了保证两个线程使用同一个对象锁,第一个使用this,第二个线程需要使用第一个对象(即this代码块所在对象)进行 synchronized 同步

public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {
                    //this 指的是当前线程
                    synchronized (this){
                        if(flag!=1){
                            try{
                                this.wait();
                            }catch (InterruptedException e){

                            }
                        }
                        System.out.println("A"+i);
                        flag=2;
                        this.notify();

                    }

                }

            }

        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 10; i++) {

                    synchronized (t1){
                        if(flag!=2){
                            try{
                                t1.wait();
                            }catch (InterruptedException e){

                            }
                        }
                        System.out.println("B"+i);
                        flag=1;
                        t1.notify();

                    }
                }

            }

        };
        executorService.execute(t1);
        executorService.execute(t2);

    }
使用 ReentrantLock + condition 来实现
 static ReentrantLock reentrantLock = new ReentrantLock();
    static Condition c1 = reentrantLock.newCondition();
    static Condition c2 = reentrantLock.newCondition();
    static Condition c3 = reentrantLock.newCondition();
    static volatile int flag =0 ;
    public static void main(String[] args) {
        Thread t1 =new Thread(()->{
                for(int i=0;i<10;i++){
                    reentrantLock.lock();
                    try{
                        if(flag==0){

                        }else{
                            try {
                                c1.await();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        System.out.println("a");
                        flag=1;
                        c2.signal();
                    }finally{
                        reentrantLock.unlock();
                    }

                }
            });
        Thread t2= new Thread(() -> {
            for(int i=0;i<10;i++){
                reentrantLock.lock();
                try{
                    if(flag==1){

                    }else{
                        try {
                            c2.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println("b");
                    flag=2;
                    c3.signal();
                }finally{
                    reentrantLock.unlock();
                }

            }
        });
        Thread t3 = new Thread(() -> {
            for(int i=0;i<10;i++){
                reentrantLock.lock();
                try{
                    if(flag==2){

                    }else{
                        try {
                            c3.await();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println("c");
                    flag=0;
                    c1.signal();
                }finally{
                    reentrantLock.unlock();
                }

            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

参考资料

[1]、Java 并发编程实战
[2]、Java 高并发程序设计
[3]、深入理解 Java 虚拟机

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值