【Java 并发】 Synchronized关键字


本文笔记来自MOOC课程 Java高并发之魂:synchronized深度解析

synchronized简介

Synchronized 是Java最基本的关键字,它保证同一时刻只有一个线程访问,属于独占锁

两种方法

//非同步方法示例
//两个线程一起做加法10000次,最后结果小于20000
public class DisappearRequest1 implements Runnable{
   static  DisappearRequest1 instance = new DisappearRequest1();
   static int i =0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 =new Thread(instance);
        Thread t2 =new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }

    @Override
    public void run() {
         for (int j = 0; j < 10000; j++) {
             i++;
         }
    }
  } 

1. 对象锁

对象的实例上锁,一个实例一把锁,不同实例的锁不同。
1.1 方法锁 public synchronized void method(){}

  @Override
    public void run() {
        method();
    }
    public synchronized void method() {
        //synchronized code
    }

1.2 同步代码块锁 synchronized (OBJ){}

@Override
   public void run() {
            synchronized (this)
            { 
            //synchronized code
            }
    }

2. 类锁

Java中有多个对象,但是只有一个class对象,所谓类锁就是class对象锁

2.1 静态方法 synchronized static method(){}

  @Override
    public void run() {
        method();
    }
         public static synchronized void method(){
          //synchronized code
         }
     }

2.2 Class对象 synchronized(*.class)

@Override
    public void run() {
            method();
    }
    private void method(){
        synchronized (CLASS_NAME.class){
         }
    }

多线程访问同步方法实例

  1. 两个线程访问一个对象——串行输出
    解释:一个实例一个锁
public class SynchronizedMethod3 implements Runnable{
    static  SynchronizedMethod3 instance =new SynchronizedMethod3();
    public static void main(String[] args) {
        Thread t1 =new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive()||t2.isAlive()){

        }
        System.out.println("all finished !");
    }

    @Override
    public void run() {
        method();
    }
    public synchronized void method() {
        System.out.println("Im object lock for method type,Im"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"finished !");
    }

}

*** 2. 两个线程同步访问两个对象——并行输出 解释:两个对象两个锁 ```java public class SynchronizedCodeBlock2 implements Runnable{ static SynchronizedCodeBlock2 instance1 =new SynchronizedCodeBlock2(); static SynchronizedCodeBlock2 instance2 =new SynchronizedCodeBlock2();
    @Override
    public void run() {
        synchronized (this){
            System.out.println(" I'm lock1 "+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" is run over");
        }
    }
public static void main(String[] args) {
    Thread t1 =new Thread(instance1);
    Thread t2 = new Thread(instance2);
    t1.start();
    t2.start();
    while (t1.isAlive() || t2.isAlive()){
    }
    System.out.println("finished!");
}

}


<img src="https://img-blog.csdnimg.cn/2019090410550176.png"  width="70%" />
***
3. 两个线程同步访问两个对象synchronized的静态方法——串行输出
解释:一个类锁
```java
public class SynchronizedClassStatic4 implements Runnable{
    static SynchronizedClassStatic4 instance1 =new SynchronizedClassStatic4();
    static SynchronizedClassStatic4 instance2 =new SynchronizedClassStatic4();
    public static void main(String[] args) throws InterruptedException{
        Thread t1 =new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println(" all finished !");
    }

    @Override
    public void run() {
        method();
    }
         public static synchronized void method(){
             System.out.println("im class lock type static im: "+Thread.currentThread().getName());
             try {
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName()+ " finished ");
     }
}

*** 4. 两个线程同时访问一个对象的同步方法和非同步方法——并行输出 解释:一个有锁,一个无锁
public class SynchronizedYesAndNo6 implements Runnable{
    static SynchronizedYesAndNo6 instance = new SynchronizedYesAndNo6();
    public static void main(String[] args) {
       Thread t1 = new Thread(instance);
       Thread t2 = new Thread(instance);
       t1.start();
       t2.start();
       while (t1.isAlive() || t2.isAlive()){

       }
        System.out.println("all finished !");
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }

    }

    public synchronized void method1(){
        System.out.println(" I'm synchornized method "+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" is run over");
    }
    public void method2(){
        System.out.println(" I'm none synchornized method "+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" is run over");
    }

}

*** 5. 两个线程对同一个对象的不同普通同步方法(非static)——串行输出 解释:同一个对象锁
public class SynchronizedDifferentMethod7 implements Runnable{
    static SynchronizedDifferentMethod7 instance = new SynchronizedDifferentMethod7();
    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("all finished !");
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }

    }

    public synchronized void method1(){
        System.out.println(" I'm synchornized method "+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" is run over");
    }
    public synchronized void method2(){
            System.out.println(" I'm synchornized 2 method "+Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" is run over");
   }
}
*** 6. 同时访问一个对象的静态synchronized 和非静态synchronized方法——并行输出 解释:一个class对象锁,一个普通方法锁,不冲突 ```java public class SynchronizedStaticAndNo8 implements Runnable{ static SynchronizedStaticAndNo8 instance =new SynchronizedStaticAndNo8(); public static void main(String[] args) { Thread t1 =new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive()||t2.isAlive()){
    }
    System.out.println("all finished !");
}

@Override
public void run() {
    if(Thread.currentThread().getName().equals("Thread-0")){
        method1();
    }else {
        method2();
    }
}
public static synchronized void method1() {
    System.out.println("im static Synchronized method ,im"+Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+" finished !");
}
public synchronized void method2(){
    System.out.println("im normal Synchronized method ,im"+Thread.currentThread().getName());
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName()+" finished !");
}

}


 <img src=" https://img-blog.csdnimg.cn/20190904110509931.png"  width="70%" />

 ***
 7. 方法抛出异常后,会释放锁
 解释:与Lock对比,Lock需要手动释放,synchronized无论是执行完毕或者方法抛出异常 都会释放锁
 

```java
//展示不抛出异常和抛出异常后的对比: 一但抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放
public class SynchronizedException9 implements Runnable{
    static SynchronizedException9 instance =new SynchronizedException9();
    public static void main(String[] args) {
        Thread t1 =new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive()||t2.isAlive()){

        }
        System.out.println("all finished !");
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }
    }
    public  synchronized void method1() {
        System.out.println("Im Throw exception method ,im"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException();//runtime 异常不会被捕获,下一句不会被执行
       // System.out.println(Thread.currentThread().getName()+" finished !");
    }
    public synchronized void method2(){
        System.out.println("Im Synchronized method 2,im"+Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" finished !");
    }

}

总结

1. 一把锁只能同时被一个线程获取,其他线程等待(例子1,5)

2. 每个实例对应自己的一把锁(例外:锁对象(*.class)和static synchronized 方法,所有对象共用同一把类锁)(例子 2,3,4,6)

3. 无论正常执行完毕或者方法抛出异常,都会释放锁(例子 7)


与Lock对比

lock类有中断能力,可中断获得锁的线程,也可以自我中断


synchronized 原理

synchronized关键字 属于递归锁,具有可重入性、不可中断性、可见性


可重入性

可重入:同一线程内 外层函数获得锁后,内层函数可再次获得锁
不可重入:线程需要在释放锁之后重新竞争获取锁
好处:避免死锁,提升封装性
粒度:线程 (pthread的粒度是调用)

//可重入粒度测试,调用父类方法
public class SynchronizedSuperClass12 {
    public synchronized void dosome(){
        System.out.println("Im Super Class");
    }

    public static void main(String[] args) {
        child child = new child();
        child.dosome();
    }

}

//同线程中去调用,可用同一把锁
class child extends SynchronizedSuperClass12{
    public synchronized void dosome(){
        System.out.println("Im child Class");
        super.dosome();
    }

}

反编译

javap -verbose xxx.class
主要看monitorenter 和 monitorexit

monitor 的lock 锁一次只能被一个线程所获得;

  • monitor锁的三种情况:
  1. 当monitor计数器为0,目前没有线程获得锁,请求锁。monitor+1 成功获得锁;
  2. 线程获得锁,monitor计数器≠0,当前线程重入,计数器+1;
  3. 已被其他线程持有,该线程等待释放;

可重入性原理

  • Java JVM跟踪对象被加锁的次数:
  1. 线程第一次给对象加锁,计数变为1;
  2. 每当这个相同的线程在此对象上再次获得锁时,计数递增;每当任务离开时,计数递减;
  3. 当计数为零,锁被完全释放。

可见性原理

Java内存模型中线程结构体内放有共享变量的副本(线程结构可看笔记知识碎片-线程
在这里插入图片描述
本地内存B的数据=A放入主内存的数据=本地内存A的数据
在这里插入图片描述

缺陷

效率低: 锁的释放情况少,不能设置超时退出,不能中断
不够灵活: 加锁和释放锁的时机单一,每个锁只有单一的条件(某个对象)。无法知道是否成功获取到锁
反观lock类有 lock.lock() lock.unlock()

常见面试问题

1.锁对象不能为空,作用域不宜过大,避免死锁
2. 如何选择lock和synchronized 关键字? blah blah 尽量使用 util.concurrence 包内方法
3. 多线程访问同步方法具体情况 上述7种

发布了27 篇原创文章 · 获赞 1 · 访问量 697

猜你喜欢

转载自blog.csdn.net/SUKI547/article/details/100533347
今日推荐