首页 > 技术文章 > 架构系列——你可能不知道的那些synchronized使用细节

shuhao66666 2020-05-20 18:55 原文

作者专注于Java、架构、Linux、小程序、爬虫、自动化等技术。 工作期间含泪整理出一些资料,微信搜索【程序员高手之路】,回复 【java】【黑客】【爬虫】【小程序】【面试】等关键字免费获取资料。技术交流、项目合作可私聊:shuhao-99999。

目录

前言

1、synchronized锁的重入性

2、不要使用字符串常量作为锁

3、锁对象的改变问题


前言

synchronized可以为任意对象加锁,用法比较灵活,语法如下

(1)修饰代码块,作用于调用的对象;

(2)修饰方法,作用于调用的对象;

(3)修饰静态方法,作用于所有对象;

(4)修饰类,作用于所有对象。

synchronized取得的锁都是对象锁,而不是把一段代码(或者方法)当成锁!

使用synchronized时,应该注意以下细节问题:

1、synchronized锁的重入性

如下列代码所示,类似于ReentrantLock

在method1中没有释放锁的情况下,可以继续调用synchronized修饰的method2

public class SyncDubbo {
    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("method1...");
    }
    
    public static void main(String[] args) {
        final SyncDubbo sd = new SyncDubbo();
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                sd.method1();
            }
            
        }, "t1");
        t1.start();
    }
}

 如下列代码所示,在子类与父类之间相互调用也运用了synchronized的重用性

public class FatherSon {
    static class Father {
        public int num = 10;
        public synchronized void method1(){
            try {
                num --;
                System.out.println("Father num = " + num);
                Thread.sleep(100);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
    
    static class Son extends Father {
        public synchronized void method2(){
            try {
                while (num >0) {
                    num --;
                    System.out.println("Son num = " + num);
                    Thread.sleep(100);
                    this.method1();
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Son son = new Son();
                son.method2();
            }
        }, "t1");
        t1.start();
    }
}

2、不要使用字符串常量作为锁

如下列代码所示,使用了字符串常量作为锁,那么t1和t2运行之后将会一直在t1中出现死循环,t2永远拿不到锁!

解决:可以使用 new String("字符串常量") 作为锁

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

3、锁对象的改变问题

如下列代码所示,使用synchronized锁住了一个对象,并且在代码里重新new了一个对象,导致锁对象改变。

这样在t1还没有运行完代码的时候,t2就已经可以拿到锁了,显然会出现问题!

解决:不要改变锁对象(但是改变对象的属性对代码不会有影响,比如锁是一个人,那么可以改变这个人的身高、年龄等)

public class ChangeLock {
    Object obj = new Object();
    
    public void method(){
        synchronized(obj){
            try {
                System.out.println("当前线程:" + Thread.currentThread().getName() + "开始");
                //从这里改变锁对象
                obj = new Object();
                Thread.sleep(3000);
                System.out.println("当前线程:" + Thread.currentThread().getName() + "结束");
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
    public static void main(String[] args) {
        final ChangeLock cl = new ChangeLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                cl.method();
            }
            
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                cl.method();
            }
            
        }, "t2");
        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        t2.start();
    }
}

推荐阅读