synchronized的使用

本文是学习 默课网-Java高并发之魂:synchronized深度解析 所做的笔记。

一、Synchronized

1.1 synchronized简介

  1. 官方解释:

同步方法支持一种简单的策略来防止线程干扰和内存一直想错误:如果一个对象对多个线程可见,则该对象变量的所有读取和写入都是通过同步方法完成的。

  1. 通俗易懂:

能够保证同一时刻只有一个线程执行该段代码,已达到并发安全的效果。

1.2 并发出现的问题

两个线程同时执行i++,最后的结果比我们预期的少。代码如下:

public class DisappearRequest1 implements Runnable{

    private static DisappearRequest1 instance = new DisappearRequest1();

    private static int i = 0;

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

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);

        t1.start();
        t2.start();
        // join()方法的作用是,当前线程(主线程)进入阻塞状态,等待执行join()方法的线程运行结束后,当前线程(主线程)才可以进入就绪状态。
        t1.join();
        t2.join();

        // 理想的情况是输出200000,但由于多线程并发问题,结果小于200000
        System.out.println(i);

        /** 原因分析:
         * i ++ ,看上去只有一个操作,实际上包含了三个动作
         * 1. 读取i的值
         * 2. 将i加1
         * 3. 将i的值写入内存
         * 由于在多线程情况下,上述三个步骤任何一步都有可能被打断(例如:t1读取到i的值为0,加1,
         * 还没来得及写入内存,此时t2线程进来,读取到i的值依然是0,加1,写入内存,最终i执行了
         * 两次加1,但是i的值不是2,而是1),所以造成最终结果与预期不一致。
         *
         *
         */
    }
}

二、Synchronized的两种用法

2.1 对象锁

包括方法锁(默认锁对象是this当前实例对象)和同步代码块锁(自己指定锁对象)

2.11 方法锁形式

synchronized修饰普通方法,锁对象默认是this

public class SynchronizedObjectMethod3 implements Runnable {

    private static SynchronizedObjectMethod3 instance = new SynchronizedObjectMethod3();

    @Override
    public void run() {
        method();
    }
    public synchronized void method() {
        System.out.println("我是对象锁的方法修饰方式,我叫 " + Thread.currentThread().getName());

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);

        t1.start();
        t2.start();
        // while空循环的作用是,让t1,t2线程执行结束后再执行主线程
        while (t1.isAlive() || t2.isAlive()) {

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

运行结果

我是对象锁的方法修饰方式,我叫 Thread-0
Thread-0  运行结束
我是对象锁的方法修饰方式,我叫 Thread-1
Thread-1  运行结束
finished
2.12 代码块锁形式

第一种,使用this作为锁对象

public class SynchronizedObjectBlock2 implements Runnable{

    private static SynchronizedObjectBlock2 instance = new SynchronizedObjectBlock2();

    @Override
    public void run() {
       synchronized (this) {
           System.out.println("我是对象锁的代码块形式,我叫 : " + Thread.currentThread().getName());
           try {
               Thread.sleep(3000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName() + "运行结束");
       }
    }
    
    public static void main(String[] args){
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        // while空循环的作用是,让t1,t2线程执行结束后再执行主线程
        while (t1.isAlive() || t2.isAlive()) {

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

运行结果:

我是对象锁的代码块形式,我叫 : Thread-0
Thread-0运行结束
我是对象锁的代码块形式,我叫 : Thread-1
Thread-1运行结束
finished

第二种,使用自定义对象作为锁对象(有多个代码块时,就需要自定义锁对象)

public class SynchronizedObjectBlock2 implements Runnable{

    private static SynchronizedObjectBlock2 instance = new SynchronizedObjectBlock2();
    Object lock1 = new Object();
    Object lock2 = new Object();
    
    @Override
    public void run() {
       synchronized (lock1) {
           System.out.println("我是lock1,我叫 : " + Thread.currentThread().getName());
           try {
               Thread.sleep(3000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName() + "  lock1部分运行结束");
       }
       synchronized (lock2) {
           System.out.println("我是lock2,我叫 : " + Thread.currentThread().getName());
           try {
               Thread.sleep(3000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName() + "  lock2部分运行结束");
       }
    }



    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("finished");
    }
}

运行结果

我是lock1,我叫 : Thread-1
Thread-1  lock1部分运行结束
我是lock2,我叫 : Thread-1
我是lock1,我叫 : Thread-0
Thread-0  lock1部分运行结束
Thread-1  lock2部分运行结束
我是lock2,我叫 : Thread-0
Thread-0  lock2部分运行结束
finished

2.2 类锁

指的是synchronized修饰静态的方法,或者指定锁为Class对象。

2.21 基础知识点
  1. Java类可能有很多个对象,但是只有一个Class对象(类类型class type),所谓的类锁,指的就是该类的Class对象的锁
  2. 类锁使用效果:类锁只能在同一时刻被一个对象拥有。
2.22 类锁的形式
  1. synchronized加在static方法上
public class SynchronizedClassStatic4 implements Runnable {

    private static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
    private static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();

    @Override
    public void run() {
        method();
    }

    // static方法
    public static synchronized void method() {
      System.out.println("我是类锁的第一种形式 :static形式,我叫 " + Thread.currentThread().getName());

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "  运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);

        t1.start();
        t2.start();

        // while空循环的作用是,让t1,t2线程执行结束后再执行主线程
        while (t1.isAlive() || t2.isAlive()) {

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

运行结果:

我是类锁的第一种形式 :static形式,我叫 Thread-0
Thread-0  运行结束
我是类锁的第一种形式 :static形式,我叫 Thread-1
Thread-1  运行结束
finished
  1. synchronized(*.class)代码块
public class SynchronizedClassClass5 implements Runnable {

    private static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
    private static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();

    @Override
    public void run() {
        method();
    }

    public void method() {
        synchronized (SynchronizedClassClass5.class) {
            System.out.println("我是类锁的第二种形式:synchronized(*.class)代码块形式,我叫 " + Thread.currentThread().getName());

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "  运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);

        t1.start();
        t2.start();

        // while空循环的作用是,让t1,t2线程执行结束后再执行主线程
        while (t1.isAlive() || t2.isAlive()) {

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

运行结果

我是类锁的第二种形式:synchronized(*.class)代码块形式,我叫 Thread-0
Thread-0  运行结束
我是类锁的第二种形式:synchronized(*.class)代码块形式,我叫 Thread-1
Thread-1  运行结束
finished

现在可以对1.2中的代码进行优化

  1. 使用对象锁的synchronized修饰普通方法形式
 @Override
    public synchronized void run() {
         
        for (int j = 0; j < 100000; j++) {
            i ++;
        }
        
    }
  1. 使用对象锁的代码块形式
 @Override
    public void run() {
        synchronized (this) {
            for (int j = 0; j < 100000; j++) {
                i ++;
            }
        }
    }
  1. 使用类锁的synchronized(*.class)代码块形式
 @Override
    public void run() {
        synchronized (DisappearRequest1.class) {
            for (int j = 0; j < 100000; j++) {
                i ++;
            }
        }
    }
  1. 使用类锁的synchronized修饰zestatic方法形式
  @Override
    public void run() {
        count();
    }

    public synchronized static void count(){
        for (int j = 0; j < 100000; j++) {
            i ++;
        }
    }

以上优化后的代码最终输出结果都是:200000

三、sychonized集几种常见面试题

3.1 多线程访问(synchronized)同步方法的七种情况

  1. 两个线程同时访问一个对象的同步方法(串行)
  2. 两个线程访问的是两个对象的同步方法(并行:锁对象不一致)
  3. 两个线程访问的是sychronized的静态方法(串行:类锁,锁对象一致)
  4. 同时访问同步和非同步的方法(并行:synchronized只会对被修饰的方法起作用)
  5. 访问一个类的不同的普通(非static)同步方法(串行:此种情况是对象锁,因此两个同步方法拿到的锁对象是一致的)
  6. 同时访问静态的sychronized和非静态的synchronized方法(并行:前者是对象锁this,后者是类锁*.class)
  7. sychronized方法抛出异常后,会释放锁对象吗?(JVM会释放锁)
  8. 当一个同步方法中调用了另一个非同步方法的时候,该同步方法还是线程安全的吗?(不安全)

3.2 对以上七种情况的总结

  1. 一把锁同时只能被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况)
  2. 每个实例都应有自己的一把锁,不同实例之间互不影响;例外:如果是类锁的时候,所有对象共用一把锁(对应第2、3、4、6种情况)
  3. 无论是方法正常执行完毕或者是方法抛出异常,都会释放锁对象(第7种情况)

四、Synchronized的性质

4.1 可重入性质

  1. 定义:可重入指的的是同一线程的外层函数获得锁之后,内层函数可以直接在此获取该锁。(可重入锁也叫递归锁)
  2. 好处:避免死锁,提升封装性
  3. 粒度(范围):线程而非调用(只要是在同一个线程内,都可以满足可重入性质,即无需释放锁、重新获取锁)

粒度总结:

  1. 同一个方法是可重入的,即递归调用同一个方法,是满足可重入性质的
  2. 可重入不要求是同一个方法,即一个同步方法调用其他同步方法,依然满足可重入性质
  3. 可重入不要求是同一个类,即一个类的同步方法调用其他类的同步方法也满足可重入性质
4.2 不可中断性质
  1. 定义:一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,知道别的线程释放这个锁,如果别人永远不释放,那我只能永远等待下去。

参考

默课网-Java高并发之魂:synchronized深度解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350