使用synchronized的注意点(补充)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xyc_csdn/article/details/79920120

在这篇博客https://blog.csdn.net/xyc_csdn/article/details/78159259中我提到了一些使用synchronized时的注意点,但是还是比较浅薄,特别通过最近的面试,认识到以前的总结还是有些不足,所以在这里再次完善一下。

先看代码
abstract class AbstractSynchronized {
    protected Long id;
    protected String name;
    protected String stringLock = "LOCK";

    public AbstractSynchronized(Long id, String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        synchronized (this.stringLock) {
            return name;
        }
    }

    public abstract void setName(String name);
}

class SynchronizedA extends AbstractSynchronized {
    public SynchronizedA(Long id, String name) {
        super(id, name);
    }

    public void setName(String name) {
        synchronized (this.stringLock) {
            try {
                Thread.sleep(3000);
                System.out.println("暂停3s...");
                this.name = name;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SynchronizedB extends AbstractSynchronized {
    public SynchronizedB(Long id, String name) {
        super(id, name);
    }

    public void setName(String name) {
        synchronized (this.stringLock) {
            try {
                this.stringLock = null;
                Thread.sleep(3000);
                System.out.println("暂停3s...");
                this.name = name;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SynchronizedC extends AbstractSynchronized {
    public SynchronizedC(Long id, String name) {
        super(id, name);
    }

    public void setName(String name) {
        synchronized (this.stringLock) {
            try {
                Thread.sleep(3000);
                System.out.println("暂停3s...");
                this.stringLock = null;
                this.name = name;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SynchronizedD extends AbstractSynchronized {
    public SynchronizedD(Long id, String name) {
        super(id, name);
    }

    public void setName(String name) {
        synchronized (this.stringLock) {
            try {
                this.stringLock = "aaa";
                Thread.sleep(3000);
                System.out.println("暂停3s...");
                this.name = name;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class SynchronizedE extends AbstractSynchronized {
    public SynchronizedE(Long id, String name) {
        super(id, name);
    }

    public void setName(String name) {
        synchronized (this.stringLock) {
            try {
                Thread.sleep(3000);
                System.out.println("暂停3s...");
                this.stringLock = "aaa";
                this.name = name;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class SynchronizedTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("---------------测试A---------------");
        testSynchronized(new SynchronizedA(1L, "任务1"));
        Thread.sleep(5000);
        System.out.println("---------------测试B---------------");
        testSynchronized(new SynchronizedB(1L, "任务1"));
        Thread.sleep(5000);
        System.out.println("---------------测试C---------------");
        testSynchronized(new SynchronizedC(1L, "任务1"));
        Thread.sleep(5000);
        System.out.println("---------------测试D---------------");
        testSynchronized(new SynchronizedD(1L, "任务1"));
        Thread.sleep(5000);
        System.out.println("---------------测试E---------------");
        testSynchronized(new SynchronizedE(1L, "任务1"));
    }

    public static void testSynchronized(AbstractSynchronized abstractSynchronized) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                abstractSynchronized.setId(2L);
                abstractSynchronized.setName("任务2");
            }
        }, "t1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("id:" + abstractSynchronized.getId());
                System.out.println("name:" + abstractSynchronized.getName());
            }
        }, "t2").start();
    }
}
打印结果
Connected to the target VM, address: '127.0.0.1:58955', transport: 'socket'
---------------测试A---------------
id:2
暂停3s...
name:任务2
---------------测试B---------------
Exception in thread "t2" id:2
java.lang.NullPointerException
    at org.xiayc.lib.sbtts.AbstractSynchronized.getName(SynchronizedTest.java:23)
    at org.xiayc.lib.sbtts.SynchronizedTest$2.run(SynchronizedTest.java:156)
    at java.lang.Thread.run(Thread.java:745)
暂停3s...
---------------测试C---------------
id:2
暂停3s...
name:任务2
---------------测试D---------------
id:2
name:任务1
暂停3s...
---------------测试E---------------
id:2
Disconnected from the target VM, address: '127.0.0.1:58955', transport: 'socket'
暂停3s...
name:任务2

Process finished with exit code 0
分析

通过这篇博客https://blog.csdn.net/xyc_csdn/article/details/78159259我们知道:

  • 1、synchronized关键字不能修饰int、double等基本数据类型;
  • 2、synchronized关键字最好不要修饰String、Integer等基本数据对象类型,因为如果基本数据对象类型的值发生改变的话,原先加的锁可能会丢失;
  • 3、synchronized关键字修饰对象时,如果对象的属性值发生改变(对象本身发生改变例外)不会影响锁的稳定;

  我们又通过上面的打印结果知道测试C和测试E的打印结果与正确写法的测试A结果一致,说明测试C和测试E是正确的,而同样是修改stringLock的测试B和测试D却是错误的。这是为什么呢?同样是违背第2点,为什么测试C、测试E和测试B、测试D的结果截然不同,接下来我们进行一下深入的分析!

  首先测试C和测试E有一个共同点,就是它们都是在线程暂停3s后再修改的stringLock值,而这又意味着在线程t1还没来得及修改stringLock值的时候,线程t2在碰到synchronized(this.stringLock)的时候stringLock锁是有效的,所以线程t2会进行阻塞等待,哪怕线程t1后来把stringLock值改了(大家可以在测试C和测试E中线程暂停3s以及修改stringLock值后再次暂停3s看线程t2是否会拿到锁),这时候也不影响线程t2的阻塞等待。
而在测试B中线程t1已经先一步将stringLock值置为null,这是线程t2再执行synchronized(this.stringLock)就会报错了。

  同样再测试D中线程t1也是先一步将stringLock的值从"LOCK"修改为"aaa"(注意:因为String类型是不可变的,所以这里不仅是字符串内容变了,同时引用指向的内存地址也变了),这时线程t2再去执行synchronized(this.stringLock)的时候将会直接拿到锁,因为stringLock的值已经变了。这也是第3点成立的原因,对象的属性值可以改变,因为这个对象的引用地址没有变,所以还是不会影响锁的稳定。

总结一下使用synchronized的注意点其实上面3点,不过在这篇博客进行了进一步的分析,最后以两个故事结束这篇博客吧!

故事一:从前有两个男孩t1和t2,他们同时喜欢上了一个女孩stringLock,stringLock是如此的单纯可爱,善良美丽,令人向往。为了这个女孩,t1和t2展开了激烈的竞争,但结局总是残酷的,最后t1打败了t2并获取了stringLock的芳心,不久后他们就远走他乡,移民国外了,出国前t2连stringLock最后一面都没有见到,独留t2在这片伤感的土地回忆着过去。虽然t2后来再也没有见过stringLock,但是t2没有放弃,他一直在等待,因为stringLock在他的印象中还是初见的模样,还是那么美好,只是每天傻傻的想着说不定那天t1和stringLock分手了呢?

故事二:从前有两个男孩t1和t2,他们同时喜欢上了一个女孩stringLock,stringLock是如此的单纯可爱,善良美丽,令人向往。为了这个女孩,t1和t2展开了激烈的竞争,但结局总是残酷的,最后t1打败了t2并获取了stringLock的芳心,不久后他们就远走他乡,移民国外了,出国前t2见了stringLock一面,发现她是那么的幸福快乐,t2这时候才醒悟过来,stringLock和以前不一样了,她不在是当初的那个女孩了,他也应该放弃了。尽管如此,后来t2还是找了一个名字一样叫stringLock的女孩,然而她们也仅仅是名字一样而已。

猜你喜欢

转载自blog.csdn.net/xyc_csdn/article/details/79920120
今日推荐