你真的了解 synchronized 吗?

线程安全概念

当多个线程访问某一个类(对象或方法)时,这个对象始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"

方法锁

public class MyThread extends Thread{
    
    private int count = 5 ;
    
    //synchronized加锁
    public synchronized void run(){
        count--;
        System.out.println(this.currentThread().getName() + " count = "+ count);
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread,"t1");
        Thread t2 = new Thread(myThread,"t2");
        Thread t3 = new Thread(myThread,"t3");
        Thread t4 = new Thread(myThread,"t4");
        Thread t5 = new Thread(myThread,"t5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}


当多个线程访问 myThreadrun 方法时,以排队的方式进行处理(这里排队是按照 CPU 分配的先后顺序而定的),一个线程想要执行synchronized 修饰的方法里的代码需要做两种操作:
1、尝试获得锁
2、如果拿到锁,执行synchronized代码体内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)

对象锁

public class MultiThread {

    private static int num = 0;
    
    /** static */
    public static synchronized void printNum(String tag){
        try {
            
            if(tag.equals("a")){
                num = 100;
                System.out.println("tag a, set num over!");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("tag b, set num over!");
            }
            
            System.out.println("tag " + tag + ", num = " + num);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    //注意观察run方法输出顺序
    public static void main(String[] args) {
        
        //俩个不同的对象
        final MultiThread m1 = new MultiThread();
        final MultiThread m2 = new MultiThread();
        
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.printNum("a");
            }
        });
        
        Thread t2 = new Thread(new Runnable() {
            @Override 
            public void run() {
                m2.printNum("b");
            }
        });     
        
        t1.start();
        t2.start();
        
    }
}

输出结果:

方法printNum()未加static 后的输出结果:

tag b, set num over!
tag a, set num over!
tag b, num = 200
tag a, num = 100

在方法printNum()加上static 后的输出结果:

tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200

1、在本例中关键字 synchronized 取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock
2、在静态方法上加 synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。

对象锁异步同步的问题

public class MyObject {

    public synchronized void method1(){
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    /** synchronized */
    public void method2(){
            System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        
        final MyObject mo = new MyObject();
        
        /**
         * 分析:
         * t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法
         * t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
         */
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.method1();
            }
        },"t1");
        
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                mo.method2();
            }
        },"t2");
        
        t1.start();
        t2.start();
        
    }
    
}

输出结果:

method2()未加synchronized关键字:

t2
t1

method2()加上synchronized关键字:

t1
t2

在本例中
1、method2()方法未加synchronized关键字, t1 线程先持有object对象的Lock锁,t2 线程可以以异步的方式调用对象中的非synchronized修饰的方法 2、如果在method2()方法上加上synchronized关键字此时,t1 线程先持有object对象的Lock`锁,t2 线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步

脏读、原子性问题

在某些情况下业务整体需要使用完整的synchronized,保持业务的原子性。

public class DirtyRead {

    private String username = "loofer";
    private String password = "123";
    
    public synchronized void setValue(String username, String password){
        this.username = username;
        
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        this.password = password;
        
        System.out.println("setValue最终结果:username = " + username + " , password = " + password);
    }
    
    public void getValue(){
        System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
    }
    
    
    public static void main(String[] args) throws Exception{
        
        final DirtyRead dr = new DirtyRead();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                dr.setValue("z3", "456");       
            }
        });
        t1.start();
        Thread.sleep(1000);
        
        dr.getValue();
    }
    
}

输出结果:

getValue方法得到:username = z3 , password = 123
setValue最终结果:username = z3 , password = 456

由于使用了sleep()导致getValue()方法先执行,原本我们期望的应该是setValue()方法先执行,并且 password 应该都为 456 才对

synchronized的重入

例子1

public class SyncDubbo1 {

    public synchronized void method1(){
        System.out.println("method1..");
        method2();
    }
    public synchronized void method2(){
        System.out.println("method2..");
        method3();
    }
    public synchronized void method3(){
        System.out.println("method3..");
    }
    
    public static void main(String[] args) {
        final SyncDubbo1 sd = new SyncDubbo1();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                sd.method1();
            }
        });
        t1.start();
    }
}

例子2

public class SyncDubbo2 {

    static class Main {
        public int i = 10;
        public synchronized void operationSup(){
            try {
                i--;
                System.out.println("Main print i = " + i);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    static class Sub extends Main {
        public synchronized void operationSub(){
            try {
                while(i > 0) {
                    i--;
                    System.out.println("Sub print i = " + i);
                    Thread.sleep(100);      
                    this.operationSup();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                Sub sub = new Sub();
                sub.operationSub();
            }
        });
        
        t1.start();
    }
}

输出结果:

例子 1:

method1..
method2..
method3..

例子2:

Sub print i = 9
Main print i = 8
Sub print i = 7
Main print i = 6
Sub print i = 5
Main print i = 4
Sub print i = 3
Main print i = 2
Sub print i = 1
Main print i = 0

synchronized 异常

public class SyncException {

    private int i = 0;
    public synchronized void operation(){
        while(true){
            try {
                i++;
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + " , i = " + i);
                if(i == 20){
                    //Integer.parseInt("a");
                    throw new RuntimeException();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        
        final SyncException se = new SyncException();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                se.operation();
            }
        },"t1");
        t1.start();
    }
}

输出结果

t1 , i = 1
t1 , i = 2
t1 , i = 3
t1 , i = 4
t1 , i = 5
t1 , i = 6
t1 , i = 7
t1 , i = 8
t1 , i = 9
Exception in thread "t1" java.lang.RuntimeException
    at com.loofer.base.sync005.SyncException.operation(SyncException.java:18)
    at com.loofer.base.sync005.SyncException$1.run(SyncException.java:32)
    at java.lang.Thread.run(Thread.java:745)
t1 , i = 10

这里我们除了 main 线程外还有一个 t1 线程,当 t1 中抛出一个异常时,由于我们在operation()方法上加了锁,所以我们整个应用程序最后退出了,对于这种问题一般情况下可以采取 continue 写入日志发布告警,让应用程序继续执行。

线程细节问题

使用 synchronized 声明的方法在某些情况下是有弊端的,比如 A 线程调用同步的方法执行一个很长时间的任务,那么 B 线程必须等待比较长的时间才能执行,这样的情况下可以使用 synchronized去优化代码执行时间,也就是通常所说的减小锁的粒度。

锁对象的改变的问题

锁对象的改变问题,当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁就不同。如果对象本身不发生改变,那么依然是同步的,即使是对象的属性发生了改变。

public class ChangeLock {

    private String lock = "lock";
    
    private void method(){
        synchronized (lock) {
            try {
                System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");
                lock = "change lock";
                Thread.sleep(2000);
                System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
    
        final ChangeLock changeLock = new ChangeLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                changeLock.method();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                changeLock.method();
            }
        },"t2");
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

输出结果

当前线程 : t1开始
当前线程 : t2开始
当前线程 : t1结束
当前线程 : t2结束

在本例中由于 lock 成员变量在线程执行的过程中被改变了,导致锁失效了

死锁问题

在设计程序时就应该避免双方相互持有对方的锁的情况

public class DeadLock implements Runnable{

    private String tag;
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();
    
    public void setTag(String tag){
        this.tag = tag;
    }
    
    @Override
    public void run() {
        if(tag.equals("a")){
            synchronized (lock1) {
                try {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
                }
            }
        }
        if(tag.equals("b")){
            synchronized (lock2) {
                try {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
                }
            }
        }
    }
    
    public static void main(String[] args) {
        
        DeadLock d1 = new DeadLock();
        d1.setTag("a");
        DeadLock d2 = new DeadLock();
        d2.setTag("b");
         
        Thread t1 = new Thread(d1, "t1");
        Thread t2 = new Thread(d2, "t2");
         
        t1.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }   
}

输出结果

当前线程 : t1 进入lock1执行
当前线程 : t2 进入lock2执行

在程序运行的运行时启动了两个线程,每个线程中都持有 lock1、lock2 两个锁,当 t1 运行时持有了 lock1 当 t2 运行的时候 lock2 被持有了,当 t1 运行到后面去尝试获得 lock2 就会一直等待,而 t2 继续运行尝试去获得 t1 发现被占用了也进入等待,这时就进入了互相等待的状态。也就是我们所说的死锁。

同一对象属性的修改不会影响锁的情况

public class ModifyLock {
    
    private String name ;
    private int age ;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    public synchronized void changeAttributte(String name, int age) {
        try {
            System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 开始");
            this.setName(name);
            this.setAge(age);
            
            System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 修改对象内容为: " 
                    + this.getName() + ", " + this.getAge());
            
            Thread.sleep(2000);
            System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        final ModifyLock modifyLock = new ModifyLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                modifyLock.changeAttributte("张三", 20);
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                modifyLock.changeAttributte("李四", 21);
            }
        },"t2");
        
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

输出结果

当前线程 : t1 开始
当前线程 : t1 修改对象内容为: 张三, 20
当前线程 : t1 结束
当前线程 : t2 开始
当前线程 : t2 修改对象内容为: 李四, 21
当前线程 : t2 结束

使用synchronized代码块减小锁的粒度,提高性能

public class Optimize {

    public void doLongTimeTask(){
        try {
            
            System.out.println("当前线程开始:" + Thread.currentThread().getName() + 
                    ", 正在执行一个较长时间的业务操作,其内容不需要同步");
            Thread.sleep(2000);
            
            synchronized(this){
                System.out.println("当前线程:" + Thread.currentThread().getName() + 
                    ", 执行同步代码块,对其同步变量进行操作");
                Thread.sleep(1000);
            }
            System.out.println("当前线程结束:" + Thread.currentThread().getName() +
                    ", 执行完毕");
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        final Optimize otz = new Optimize();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                otz.doLongTimeTask();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                otz.doLongTimeTask();
            }
        },"t2");
        t1.start();
        t2.start();
        
    }
}

输出结果

当前线程开始:t1, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程开始:t2, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程:t1, 执行同步代码块,对其同步变量进行操作
当前线程:t2, 执行同步代码块,对其同步变量进行操作
当前线程结束:t1, 执行完毕
当前线程结束:t2, 执行完毕

字符串加锁

public class StringLock {

    public void method() {
        //new String("字符串常量")
        synchronized (new String("字符串常量")) {
            try {
                while(true){
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");
                    Thread.sleep(1000);     
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        final StringLock stringLock = new StringLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                stringLock.method();
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                stringLock.method();
            }
        },"t2");
        
        t1.start();
        t2.start();
    }
}

输出结果

当前线程 : t1开始
当前线程 : t1结束
当前线程 : t1开始
当前线程 : t1结束
...

t1 永远持有锁,t2 永远进不去
synchronized 代码块对字符串的锁,注意String常量池的缓存功能

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

推荐阅读更多精彩内容

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,693评论 0 11
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,333评论 3 87
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,442评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 在一般性开发中,笔者经常看到很多同学在对待java并发开发模型中只会使用一些基础的方法。比如Volatile,sy...
    张勇_bf29阅读 695评论 0 1