大厂之路一由浅入深、并行基础、源码分析一synchronized关键字,以及相应优化



  • synchronized概念
    • synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
    • 在java中,每一个 对象注意:对象的重要性。) 有且仅有一个同步锁 。这也意味着,同步锁是依赖于对象而存在
    • 当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了 “obj这个对象” 的同步锁。
    • 不同线程对同步锁的访问是互斥的。也就是说,同一个时间点,只有一个线程能得到对象锁。

  • synchronized的基本使用
  • synchronized的作用主要有三个:
    • 确保线程 互斥 的访问同步代码 (原子)
    • 保证共享变量的修改能够及时可见(因为同一时间只能一个线程访问共享变量) (可见)
    • 有效解决重排序问题。(重排序带来的结果错误)(有序)
    • 从语法上讲,Synchronized总共有三种用法:
      • 修饰普通方法 (实例锁)
      • 修饰代码块 (实例锁)
      • 修饰静态方法 (全局锁)(对类加锁)

  • synchronized基本规则(3条)
    当一个线程访问“某对象”的 “synchronized方法” 或者 “synchronized代码块” 时:
    • ①、其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
    • ②、其他线程仍然可以访问“该对象”的非同步代码块
    • ③、其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
    • 之所以如此,是因为我们锁的是对象!!!!

①、其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

package com.wwj.text;

public class SynchronizedDemo {
    public static void main(String[] args){
        ThreadDemo runnable = new ThreadDemo();

        Thread t1 = new Thread(runnable , "t1"); //t1基于runnable这个Runnable对象
        Thread t2 = new Thread(runnable , "t2"); //t2是基于runnable这个Runnable对象
        t1.start();
        t2.start();
    }
}
class ThreadDemo implements Runnable{
    @Override
    public void run() {
        synchronized (this){
            try{
                for(int i=0 ; i<5 ; i++){
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "loop" + i);
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
  • 运行结果:
t1loop0
t1loop1
t1loop2
t1loop3
t1loop4
t2loop0
t2loop1
t2loop2
t2loop3
t2loop4
Process finished with exit code 0
  • 结果分析:
    • run()方法中存在“synchronized(this)代码块”,而且t1和t2都是基于runnable这个Runnable对象"创建的线程。
    • 这就意味着,我们可以将synchronized(this)中的this看作是“runnable这个Runnable对象”;
    • 因此,线程t1和t2共享“runnable对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待“运行线程”释放“runnable的同步锁”之后才能运行。
  • 那什么叫锁对象呢?
    • 我们对上面的代码进行纠正
package com.wwj.text;

public class SynchronizedDemo {
    public static void main(String[] args){
       // ThreadDemo runnable = new ThreadDemo();

        Thread t1 = new Thread( new ThreadDemo() , "t1");
        Thread t2 = new Thread( new ThreadDemo(),"t2"); //t1、t2是基于不同的ThreadDeamo对象,
        t1.start();
        t2.start();
    }
}
class ThreadDemo implements Runnable{
    @Override
    public void run() {
        synchronized (this){
            try{
                for(int i=0 ; i<5 ; i++){
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + " loop" +":"+ i);
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
  • 结果:
t2 loop:0
t1 loop:0
t2 loop:1
t1 loop:1
t2 loop:2
t1 loop:2
t2 loop:3
t1 loop:3
t2 loop:4
t1 loop:4

  • 结果分析:
    • 本应该是t1先执行,t2再执行,而现在的结果却不是,文章中的synchronized(this)中的this代表的是ThreadDemo 对象而t1和t2是两个不同的MyThread对象,因为我们里面写的是 new ThreadDemo(),创建的是不同的对象 因此t1和t2在执行synchronized(this)时,获取的是不同对象的同步锁

  • ②和③就不写例子了,顾名思义。

  • synchronized方法 和 synchronized代码块
    • “synchronized方法”是用synchronized 修饰方法,
      • 在这里插入图片描述
      • 注意:这时 synchronized修饰的是实例方法,如果修饰静态方法,那就相当于修饰类了,也就是全局锁,后面会讲解。只要用synchronized修饰的方法,都可以当作静态方法执行的格式执行。
    • “synchronized代码块”则是用synchronized 修饰代码块
      • 在这里插入图片描述
  • 对比: synchronized代码块比synchronized方法 效率更高,因为synchronized代码块可以更精确的控制临界区(也就是共享资源)

  • 实例锁 和 全局锁
    • 实例锁锁在某一个实例对象上。如果 该类是单例,那么该锁也具有全局锁的概念 ,对应的就是synchronized关键字 (synchronized方法和synchronized代码块)
    • 全局锁 – 该锁针对的是类、或者静态方法无论实例多少个对象,那么线程都共享该锁。对应的就是static synchronized (或者是锁在该类的class或者classloader对象上)。

  • 全局锁与实例锁的比较:(通过代码)
    • 借鉴第一篇博客中的例子
    • 关于“实例锁”和“全局锁”有一个很形象的例子:
pulbic class Something {   //假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
    public synchronized void isSyncA(){}   //实例方法
    public synchronized void isSyncB(){}   //实例方法
    public static synchronized void cSyncA(){}   //静态方法
    public static synchronized void cSyncB(){}   //静态方法
}
  • 假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况
package com.wwj.text;

public class SynchronizedTest {

    SomeThing x = new SomeThing();
    SomeThing y = new SomeThing();

    private void test1(){
        Thread t11 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncA();
            }
        } , "t11");
        Thread t12 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncB();
            }
        },"t12");
        t11.start();
        t12.start();
    }
    private void test2(){
        Thread t21 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncA();
            }
        },"t21");
        Thread t22 = new Thread(new Runnable() {
            @Override
            public void run() {
                y.isSyncA();
            }
        },"t22");
        t21.start();
        t22.start();
    }
    private void test3(){
        Thread t31 = new Thread(new Runnable() {
            @Override
            public void run() {
                SomeThing.cSyncA();
            }
        },"t31");
        Thread t32 = new Thread(new Runnable() {
            @Override
            public void run() {
                SomeThing.cSyncB();
            }
        },"t32");
        t31.start();
        t32.start();
    }
    private void test4(){
        Thread t41 = new Thread(new Runnable() {
            @Override
            public void run() {
                x.isSyncA();
            }
        },"t41");
        Thread t42 = new Thread(new Runnable() {
            @Override
            public void run() {
                SomeThing.cSyncA();
            }
        },"t42");
        t41.start();
        t42.start();
    }
    /*
    test1:不能同时访问,因为isSyncA()和isSyncB()都是对同一个对象(对象x)的同步锁
    test2:可以同时访问,因为访问的不是同一个对象的同步锁,x.isSyncA()是对x的同步锁,而y.isSyncA()是对y的同步锁。
    test3:不可以同时访问,因为cSyncB()和cSyncA()都是静态方法,也就是对类(Something)进行加锁
    test4:可以同时访问,因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的
     */
    public static void main(String[] args){
        SynchronizedTest synchronizedTest = new SynchronizedTest();
        //synchronizedTest.test1();
        synchronizedTest.test2();
        //synchronizedTest.test3();
        //synchronizedTest.test4();
    }
}
class SomeThing{
    public synchronized  void isSyncA(){
        try{
            for(int i=0 ; i<5 ; i++){
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+" : isSyncA");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public synchronized  void isSyncB(){
        try{
            for(int i=0 ; i<5 ; i++){
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+" : isSyncB");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static synchronized void cSyncA(){
        try{
            for(int i=0 ; i<5 ; i++){
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+" : cSyncA");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static synchronized  void cSyncB(){
        try {
            for(int i=0 ; i<5 ; i++){
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+" : cSyncB");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
  • 通过test1()、test2()、test3()、test4()、我们可以得到四个结论:
    • test1:不能同时访问,因为isSyncA()和isSyncB()都是对同一个对象(对象x)的同步锁
    • test2:可以同时访问,因为访问的不是同一个对象的同步锁,x.isSyncA()是对x的同步锁,而y.isSyncA()是对y的同步锁。
    • test3:不可以同时访问,因为cSyncB()和cSyncA()都是静态方法,也就是对类(Something)进行加锁
    • test4:可以同时访问,因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的
  • 注意技巧:
    • 对于实例方法、实力类、谁调用这个方法,那么 锁定的就是谁的对象实例,比如isSyncA()是实例方法,x.isSyncA()锁定的就是该isSyncA()所在类的实例,也就是x.
    • 对于静态方法,那么锁定的就是,比如cSyncA()、cSyncB()都是静态方法,那么不管是x.cSyncA(),还是x.cSyncB(),因为它们都是对"类"加锁,而cSyncA()、cSyncB()都属于同一个类,所以不可能同时访问
    • 对于test4,我们 可以知道对这个类加锁,与对这个类的实例加锁,它们互不干涉, 所以可以同时访问。

  • synchronized修饰代码块底层原理
    • 通过字节码层面synchronized修饰代码块的作用
    • 在这里插入图片描述
    • 通过编码我们可以发现:synchronized修饰代码块对应的字节码为:monitorenter和monitorexit
    • 我们进一步分析这两个字节码

  • 字节码层面:monitorenter :
    • JVM规范中描述:
      • 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的所有权,过程如下:
      • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者
      • 如果该线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.(能反复加锁)
      • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

  • 字节码层面:monitorexit :
    • JVM规范中描述:
      • 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所对应的monitor的所有者。
      • 指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被 这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
  • 通过这两段描述,我们应该能很清楚的看出synchronized的实现原理,synchronized的语义底层是通过一个monitor的对象来完成

  • 字节码层面说明wait()/notify()方法为什么要在同步块和方法中才能调用?
  • 其实wait/notify()等方法也依赖于monitor对象这就是为什么只有在同步块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

  • synchronized修饰实例方法底层原理
    • 通过字节码层面synchronized修饰实例方法的作用
    • 在这里插入图片描述
    • 通过编码我们可以发现:synchronized修饰实例方法对应的字节码没有看到 monitorenter和monitorexit ,却额外多了 ACC_SYNCHRONIZED, 是用来标志用的,通过字节码无法看出是否使用了monitorenter和monitorexit,通过查阅发现:
    • 对于synchronized方法执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁

  • 总结:从反编译获得的字节码可以看出:
    • synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。
    • 对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。 如果有异常发生,线程自动释放锁。

  • 加锁出现异常:
  • 注意: 对于synchronized方法或者synchronized代码块,当出现 异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。



  • synchronized代码块的细节问题
    • 通过synchronized(this)来获得对象锁,那么如果线程1访问了一个对象方法A的synchronized块,线程2对于同一对象的同步方法B的访问应该是会被阻塞的,因为线程2访问同一对象的同步方法B的时候将会尝试去获取这个对象的对象锁,但这个锁却在线程1这里。写一个例子证明一下这个结论:
package com.wwj.text;

public class MyThread {

    public static void main(String[] args) throws InterruptedException{
        MyThread_doSomeThing myThread_doSomeThing = new MyThread_doSomeThing();
        MyThread_0 t1 = new MyThread_0(myThread_doSomeThing);
        MyThread_1 t2 = new MyThread_1(myThread_doSomeThing);
        t2.start();
        t1.start();

    }
}
class MyThread_0 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_0(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doLongTask();
    }
}
class MyThread_1 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_1(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doOtherWork();
    }
}
class MyThread_doSomeThing{
    public  synchronized void  doOtherWork(){
        for(int i=0 ; i<5 ; i++){
            try{
                Thread.sleep(5);
                System.out.println("t1执行的任务");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    public void doLongTask(){
        synchronized (this){
            for(int i=0 ; i<1000 ; i++){
                System.out.println("t2做的任务:"+ Thread.currentThread().getName()+",i="+i);
                try {
                    Thread.sleep(5);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 结果:
t1执行的任务
t1执行的任务
t1执行的任务
t1执行的任务
t1执行的任务
t2做的任务:Thread-0,i=0
t2做的任务:Thread-0,i=1
----
t2做的任务:Thread-0,i=999
  • 结果分析:说明结论成立,通过synchronized(this)来获得对象锁,即使是两个不同的办法,但是因为要获得同一个对象锁,所以它们是同步的
  • 为了进一步完善这个结论,把"doOtherWork()"方法的synchronized去掉再看一下运行结果:
class MyThread_doSomeThing{
    public  void  doOtherWork(){
        for(int i=0 ; i<5 ; i++){
            try{
                Thread.sleep(5);
                System.out.println("t1执行的任务");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
  • 结果:
t1执行的任务
t1执行的任务
t2做的任务:Thread-0,i=0
t1执行的任务
t2做的任务:Thread-0,i=1
t1执行的任务
t2做的任务:Thread-0,i=2
t1执行的任务
t2做的任务:Thread-0,i=3
-----------------
t2做的任务:Thread-0,i=999
  • 结果分析: 当我们去掉synchronized的doOtherWork(),我们方向这两个方法异步执行了。

  • synchronized(this)的this用别的取代: 将任意对象作为对象监视器
  • 总结一下前面的内容:
    • synchronized同步方法
      • 对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态
      • 同一时间只有一个线程可以执行synchronized同步方法中的代码
    • synchronized同步代码块
      • 对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态
      • 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
  • 前面都使用synchronized(this) 的格式来同步代码块,其实Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。看一下将任意对象作为对象监视器的使用例子:
package com.wwj.text;

public class MyThread {
    private  String anything = new String();
    public static void main(String[] args) throws InterruptedException{
        MyThread_doSomeThing myThread_doSomeThing = new MyThread_doSomeThing();
        MyThread_0 t1 = new MyThread_0(myThread_doSomeThing);
        MyThread_1 t2 = new MyThread_1(myThread_doSomeThing);
        t2.start();
        t1.start();

    }
}
class MyThread_0 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_0(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doOtherWork();
    }
}
class MyThread_1 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_1(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doOtherWork();
    }
}
class MyThread_doSomeThing{
    private String anything = new String();
    public void  doOtherWork(){
        synchronized (anything){
            for(int i=0 ; i<5 ; i++){
                try{
                    Thread.sleep(5);
                    System.out.println(Thread.currentThread().getId()+"执行的任务");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 结果:
16执行的任务
16执行的任务
16执行的任务
16执行的任务
16执行的任务
15执行的任务
15执行的任务
15执行的任务
15执行的任务
15执行的任务
  • 结果说明:我们通过非this对象也可以做到同步处理。但如果我们把其对象换了:讲代码改成:
public static void main(String[] args) throws InterruptedException{
        MyThread_doSomeThing myThread_doSomeThing = new MyThread_doSomeThing();
        MyThread_doSomeThing myThread_doSomeThing1 = new MyThread_doSomeThing();
        MyThread_0 t1 = new MyThread_0(myThread_doSomeThing);
        MyThread_1 t2 = new MyThread_1(myThread_doSomeThing1);
        t2.start();
        t1.start();
    }
  • 结果:
15执行的任务
16执行的任务
15执行的任务
16执行的任务
16执行的任务
15执行的任务
15执行的任务
16执行的任务
15执行的任务
16执行的任务
  • 结果分析:多个线程持有 “对象监视器”同一个对象 的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码。
  • 锁非this对象具有一定的优点
    • 如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。
    • 但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率
    • 代码如下:
package com.wwj.text;

public class MyThread {
    private  String anything = new String();
    public static void main(String[] args) throws InterruptedException{
        MyThread_doSomeThing myThread_doSomeThing = new MyThread_doSomeThing();
        MyThread_0 t1 = new MyThread_0(myThread_doSomeThing);
        MyThread_1 t2 = new MyThread_1(myThread_doSomeThing);
        t2.start();
        t1.start();

    }
}
class MyThread_0 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_0(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doOtherWork();
    }
}
class MyThread_1 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_1(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doLongTask();
    }
}
class MyThread_doSomeThing{

    private String anything = new String();

    public void  doOtherWork(){
        synchronized (anything){
            for(int i=0 ; i<5 ; i++){
                try{
                    Thread.sleep(5);
                    System.out.println(Thread.currentThread().getId()+"执行的任务");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public void doLongTask(){
        synchronized (this){
            for(int i=0 ; i<1000 ; i++){
                System.out.println("t2做的任务:"+ Thread.currentThread().getName()+",i="+i);
                try {
                    Thread.sleep(5);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 结果:
15执行的任务
t2做的任务:Thread-1,i=0
15执行的任务
t2做的任务:Thread-1,i=1
15执行的任务
t2做的任务:Thread-1,i=2
15执行的任务
t2做的任务:Thread-1,i=3
15执行的任务
t2做的任务:Thread-1,i=4
-----
t2做的任务:Thread-1,i=999
  • 结果分析说明synchronized(anything)和synchronized(this)是异步执行的

注意一下 “private String anyString = new String();” 这句话,现在它是一个全局对象,因此监视的是同一个对象。如果移到try里面,那么对象的监视器就不是同一个了,调用的时候自然是异步调用

package com.wwj.text;

public class MyThread {
    private  String anything = new String();
    public static void main(String[] args) throws InterruptedException{
        MyThread_doSomeThing myThread_doSomeThing = new MyThread_doSomeThing();
        MyThread_0 t1 = new MyThread_0(myThread_doSomeThing);
        MyThread_1 t2 = new MyThread_1(myThread_doSomeThing);
        t2.start();
        t1.start();

    }
}
class MyThread_0 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_0(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doOtherWork();
    }
}
class MyThread_1 extends  Thread {
    private  MyThread_doSomeThing t = new MyThread_doSomeThing();
    public MyThread_1(MyThread_doSomeThing t){
        this.t = t;
    }

    @Override
    public void run() {
        t.doOtherWork();
    }
}
class MyThread_doSomeThing{
    public void  doOtherWork(){
        String anything = new String();
        synchronized (anything){
            for(int i=0 ; i<5 ; i++){
                try{
                    Thread.sleep(5);
                    System.out.println(Thread.currentThread().getId()+"执行的任务");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 结果:
16执行的任务
15执行的任务
15执行的任务
16执行的任务
16执行的任务
15执行的任务
15执行的任务
16执行的任务
16执行的任务
15执行的任务
  • 最后提一点,synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的。

  • 细化synchronized(非this对象x)的三个结论
    • synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器( 和this没啥关系),有三个结论得出:
      • 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果
      • 当其他线程执行x对象中的synchronized同步方法时呈同步效果
      • 当其他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果
  • 第一点很明显,第二点和第三点意思类似,无非一个是同步方法,一个是同步代码块罢了,举个例子验证一下第三点:
    dsadasdsdsd在这里插入图片描述
  • 结果如下:
16执行的任务
16执行的任务
16执行的任务
16执行的任务
16执行的任务
15执行的任务
15执行的任务
15执行的任务
15执行的任务
15执行的任务

  • synchronized锁重入
    • 关键字synchronized拥有锁重入的功能。所谓锁重入的意思就是:当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到该对象的锁。看一个例子:
package com.wwj.text;

public class MyThread {
    private  String anything = new String();
    public static void main(String[] args) throws InterruptedException{
        Thread0_1 thread0_1 = new Thread0_1();
        thread0_1.start();
    }
}
class Thread0_1 extends  Thread{
    @Override
    public void run() {
        DoTherWork doTherWork = new DoTherWork();
        doTherWork.print1();
    }
}
class  DoTherWork {
    public synchronized  void print1(){
        System.out.println(Thread.currentThread()+" :print1");
        print2();
    }
    public synchronized  void print2(){
        System.out.println(Thread.currentThread()+" :print2");
        print3();
    }
    public synchronized  void print3(){
        System.out.println(Thread.currentThread()+" :print3");
    }
}
  • 结果:
Thread[Thread-0,5,main] :print1
Thread[Thread-0,5,main] :print2
Thread[Thread-0,5,main] :print3

结果: 这证明了对象可以再次获取自己的内部锁。这种锁重入的机制,也支持在父子类继承的环境中 (相关博客,就是说子类重写父类的synchronized方法,然后子类super调用父类该方法,但要的还是子类对象的锁)。


  • 异常自动释放锁
    • 当一个线程执行的代码出现异常时,其所持有的锁会自动释放
package com.wwj.text;

public class MyThread {
    public static void main(String[] args){
        Thread0_1 thread0_1 = new Thread0_1(new DoSomeWork());
        thread0_1.start();
    }
}
class Thread0_1 extends  Thread{
    DoSomeWork t = new DoSomeWork();
    public Thread0_1(DoSomeWork t){
        this.t = t;
    }

    @Override
    public void run() {
        t.div();
    }
}
class  DoSomeWork{
    public void div(){
        int x=5,y=1;
        try{
            while(true){
                x=x/y;
                y--;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • 结果:
java.lang.ArithmeticException: / by zero
	at com.wwj.text.DoSomeWork.div(MyThread.java:25)
	at com.wwj.text.Thread0_1.run(MyThread.java:17)

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值