在这篇博客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的女孩,然而她们也仅仅是名字一样而已。