观心静

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

前言

  synchronized的理解通俗的讲就是java中用来在多线程的情况下协调资源、协调工作的。你可以想象成在流水线上每一个工人都是一个线程。而一个工人拿起产品进行组装就等于给产品增加了锁定。其他工人是无法去抢夺他正在组装的产品。只有他组装完成了,下一道工序的工人才会从流水线上接过他处理过的产品。

使用情景

synchronized只有四种使用方式:

  • 修饰代码块  
  • 修饰类
  • 修饰方法
  • 修饰静态方法

但是,说是有4种使用方式。但是其实真正的区别就2种(分别是实例与静态,怎么理解?看完此博客全部举例后你就会明白了),如下代码:

    final Demo demo = new Demo(); //不加final 会提示警告“Synchronization on a non-final field 'demo'” 意思是demo有可能会重新赋值导致锁失效

    private void t(){
        //第一种传入对象参数
        synchronized (demo){

        }
        //第二种传入类型
        synchronized (Demo.class){

        }
    }

另外,你需要记住一点这两种锁形式是不会互相影响的。

修饰代码块

代码:

    private static Integer sProduct = 0;

    public static void main(String[] args) {
        Object lockFlag = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 Start >>> ");
                synchronized (lockFlag) {
                    for (int i = 0;i< 3;i++){
                        try {
                            Thread.sleep(100);
                            sProduct++;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("T1 >>> " + sProduct);
                    }
                }
                System.out.println("T1 End >>> ");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2 Start >>> ");
                synchronized (lockFlag) {
                    sProduct++;
                    System.out.println("T2 >>> " + sProduct);
                }
                System.out.println("T2 End >>> ");
            }
        });
        t1.start();
        t2.start();
    }

结果:

从上面的代码里可以看到,启动了2个线程。按理说线程是会同步执行去处理sProduct。 但是我们给t1线程增加了synchronized。 所以t2线程的锁的代码块与后续的代码,都必须等待t1的锁的代码块种完成才能执行。

T1 Start >>> 
T2 Start >>> 
T1 >>> 1
T1 >>> 2
T1 >>> 3
T1 End >>> 
T2 >>> 4
T2 End >>> 

修饰类

代码:

    private static Integer sProduct = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 Start >>> ");
                synchronized (Demo.class) {
                    for (int i = 0; i < 3; i++) {
                        try {
                            Thread.sleep(100);
                            sProduct++;
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("T1 >>> " + sProduct);
                    }
                }
                System.out.println("T1 End >>> ");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2 Start >>> ");
                synchronized (Demo.class) {
                    sProduct++;
                    System.out.println("T2 >>> " + sProduct);
                }
                System.out.println("T2 End >>> ");
            }
        });
        t1.start();
        t2.start();
    }

结果:

T1 Start >>> 
T2 Start >>> 
T1 >>> 1
T1 >>> 2
T1 >>> 3
T1 End >>> 
T2 >>> 4
T2 End >>> 

修饰方法

    private static Integer sProduct = 0;

    public static void main(String[] args) {
        Demo demo1 = new Demo();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 Start >>> ");
                demo1.runOne();
                System.out.println("T1 End >>> ");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2 Start >>> ");
                demo1.runTwo();
                System.out.println("T2 End >>> ");
            }
        });
        t1.start();
        t2.start();
    }

    public synchronized void runOne(){
        for (int i = 0;i< 3;i++){
            try {
                Thread.sleep(100);
                sProduct++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T1 >>> " + sProduct);
        }
    }

    public synchronized void runTwo(){
        sProduct++;
        System.out.println("T2 >>> " + sProduct);
    }

结果:

T1 Start >>> 
T2 Start >>> 
T1 >>> 1
T1 >>> 2
T1 >>> 3
T1 End >>> 
T2 >>> 4
T2 End >>> 

修饰方法,只作用于当前类的实例,如果有多个类的实例(比如在上面的代码种new多个Demo类),不同的实例他们不会被互相影响。从上面的代码的运行结果我们可以知道,其实修饰方法的锁效果于在方法里添加synchronized(this){ } 一直,代码如下:

    private static Integer sProduct = 0;

    public static void main(String[] args) {
        Demo demo1 = new Demo();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 Start >>> ");
                demo1.runOne();
                System.out.println("T1 End >>> ");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2 Start >>> ");
                demo1.runTwo();
                System.out.println("T2 End >>> ");
            }
        });
        t1.start();
        t2.start();
    }

    public void runOne() {
        synchronized (this) {
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(100);
                    sProduct++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T1 >>> " + sProduct);
            }
        }
    }

    public void runTwo() {
        synchronized (this) {
            sProduct++;
            System.out.println("T2 >>> " + sProduct);
        }
    }

修饰静态方法

    private static Integer sProduct = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 Start >>> ");
                Demo.runOne();
                System.out.println("T1 End >>> ");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2 Start >>> ");
                Demo.runTwo();
                System.out.println("T2 End >>> ");
            }
        });
        t1.start();
        t2.start();
    }

    public synchronized static void runOne() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);
                sProduct++;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T1 >>> " + sProduct);
        }

    }

    public synchronized static void runTwo() {
        sProduct++;
        System.out.println("T2 >>> " + sProduct);
    }

结果:

其实修饰静态方法就类似于 synchronized (Demo.class){ } 的形式。

T2 Start >>> 
T1 >>> 1
T1 >>> 2
T1 >>> 3
T1 End >>> 
T2 >>> 4
T2 End >>> 

原理详解

synchronized 的原理关键是 锁的状态与线程id。 synchronized 一共有四种锁的状态,他们分别是:

  • 无锁  对象头中锁的状态为001,并且没有线程id

  • 偏向锁 对象头中锁的状态为101,有线程id。 偏向锁只有2种存在情况:第一种,类刚被实例这个时候会被默认设置为偏向锁。 第二种,只有一个线程使用当前类作为锁的情况下,锁的状态会修改为偏向锁 

  • 轻量锁    对象头中锁的状态为010,有线程id。轻量锁的出现情况:当有两个线程开始竞争这个锁对象,情况发生变化了,某个线程不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象,锁对象的 Mark Word 就指向哪个线程的栈帧中的锁记录。企图抢占的线程会通过自旋的形式尝试获取锁,不会阻塞抢锁线程,以便提高性能。自旋其实就是让CPU保持运作,等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

  • 重量锁    重量级锁会让其他申请的线程之间进入阻塞,性能降低。重量级锁也就叫做同步锁,这个锁 对象 Mark Word 再次发生变化,会指向一个监视器(Monitor)对象,该监视器对象用集合的形 式,来登记和管理排队的线程。

锁的简易原理图如下:

现在需要一个方式来验证我们的理解。那就是查看Java类的对象头,我们以Android studio工程为例查看对象头,在项目的build里添加依赖:

    implementation 'org.openjdk.jol:jol-core:0.11'

无锁

偏向锁

轻量锁

重量锁

 

End

posted on 2022-08-25 12:18  观心静  阅读(163)  评论(0编辑  收藏  举报