Synchronized
线程锁:Synchronized
Synchronized的自我理解
Synchronized锁的是什么?
synchronized这个锁,它其实就是在后面括号中所跟对象的头部加入一定得代码,后续所有想要用这个锁对象的,都会去看一下有没有这个代码,如果有,说明就被锁住了,具体的逻辑分析在下图:
Synchronized锁对象的选用
常见的Synchronized用法
方式1:
public static void getInstance() { synchronized (DlcDoubleCheckLock.class) { .... } }
方式2:
public static synchronized void getInstance() { if (dlcDoubleCheckLock == null) { ..... } }
假设使用了方式1的形式进行上锁,那么又可以写很多的方式:
// 形式1 public static void getInstance() { synchronized (DlcDoubleCheckLock.class) { .... } } // 形式2 public static void getInstance() { synchronized (this) { .... } } // 形式3 Object o = new Object(); public static void getInstance() { synchronized (o) { .... } }
形式1的锁测试
public class SyncTestClass { void m1() throws InterruptedException { synchronized (SyncTestClass.class){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(SyncTestClass.class)); for(int i = 0;i<5;i++){ System.out.println("m1"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m2() throws InterruptedException { synchronized (SyncTestClass.class){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(SyncTestClass.class)); for(int i = 0;i<5;i++){ System.out.println("m2"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } public static void main(String[] args) throws InterruptedException { SyncTestClass syncTest1 = new SyncTestClass(); SyncTestClass syncTest2 = new SyncTestClass(); // t1、t2为不同实例化对象调用方法 // t3、t4为相同实例化对象调用方法 Thread t1 = new Thread(()-> { try { syncTest1.m1(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 = new Thread(()-> { try { syncTest2.m2(); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("======================"); Thread t3 = new Thread(()-> { try { syncTest1.m1(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t4 = new Thread(()-> { try { syncTest1.m2(); } catch (InterruptedException e) { e.printStackTrace(); } }); t3.start(); t4.start(); t3.join(); t4.join(); } }
输出结果如下:
Thread-1得到锁 当前锁对象:685325104 m20 m21 m22 m23 m24 Thread-1释放锁 Thread-0得到锁 当前锁对象:685325104 m10 m11 m12 m13 m14 Thread-0释放锁 ====================== Thread-2得到锁 当前锁对象:685325104 m10 m11 m12 m13 m14 Thread-2释放锁 Thread-3得到锁 当前锁对象:685325104 m20 m21 m22 m23 m24 Thread-3释放锁
结果分析:
如果是用类作为锁的对象,那么这把锁就是加在这唯一的一个类上面,无论实例化多少对象,这个类都是唯一的,所以锁对象是唯一的。
形式2的锁测试
public class SyncTestThis { void m1() throws InterruptedException { synchronized (this){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(this)); for(int i = 0;i<5;i++){ System.out.println("m1"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m2() throws InterruptedException { synchronized (this){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(this)); for(int i = 0;i<5;i++){ System.out.println("m2"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m3() throws InterruptedException { synchronized (this){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(this)); for(int i = 0;i<5;i++){ System.out.println("m1"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m4() throws InterruptedException { synchronized (this){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(this)); for(int i = 0;i<5;i++){ System.out.println("m2"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } public static void main(String[] args) throws InterruptedException { SyncTestThis syncTest1 = new SyncTestThis(); SyncTestThis syncTest2 = new SyncTestThis(); // t1、t2用来测试不同的实例化对象造成的影响 // t3、t4用来测试相同的实例化对象造成的影响 Thread t1 = new Thread(()-> { try { syncTest1.m1(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 = new Thread(()-> { try { syncTest2.m2(); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("=============================="); Thread t3 = new Thread(()-> { try { syncTest1.m3(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t4 = new Thread(()-> { try { syncTest1.m4(); } catch (InterruptedException e) { e.printStackTrace(); } }); t3.start(); t4.start(); t3.join(); t4.join(); } }
输出结果如下:
Thread-0得到锁 Thread-1得到锁 当前锁对象:422057313 当前锁对象:2115147268 m20 m10 m11 m21 m22 m12 m13 m23 m14 m24 Thread-1释放锁 Thread-0释放锁 ============================== Thread-2得到锁 当前锁对象:422057313 m10 m11 m12 m13 m14 Thread-2释放锁 Thread-3得到锁 当前锁对象:422057313 m20 m21 m22 m23 m24 Thread-3释放锁
结果分析:
this指代的就是当前实例化的对象,如果说当前实例化的对象不一致,那么这个锁就不会在两个线程之间相互影响,但是如果说是同一个实例化对象,那么,这个锁就会起到作用,且这把锁,在同一个实例化对象内部的不同方法间也会起到作用,具体可以从锁对象的值上面看到。
形式3的锁测试
public class SyncTestExtenalObj { private final Object o = new Object(); void m1() throws InterruptedException { synchronized (o){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(o)); for(int i = 0;i<5;i++){ System.out.println("m1"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m2() throws InterruptedException { synchronized (o){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(o)); for(int i = 0;i<5;i++){ System.out.println("m2"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m3() throws InterruptedException { Object innerObj = new Object(); synchronized (innerObj){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(innerObj)); for(int i = 0;i<5;i++){ System.out.println("m3"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } void m4() throws InterruptedException { Object innerObj = new Object(); synchronized (innerObj){ System.out.println(Thread.currentThread().getName() + "得到锁"); System.out.println("当前锁对象:" + System.identityHashCode(innerObj)); for(int i = 0;i<5;i++){ System.out.println("m4"+i); TimeUnit.SECONDS.sleep(1); } } System.out.println(Thread.currentThread().getName() + "释放锁"); } public static void main(String[] args) throws InterruptedException { SyncTestExtenalObj syncTest1 = new SyncTestExtenalObj(); // t1、t2为不同实例化对象调用方法 // t3、t4为相同实例化对象调用方法 Thread t1 = new Thread(()-> { try { syncTest1.m1(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 = new Thread(()-> { try { syncTest1.m2(); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("======================"); Thread t3 = new Thread(()-> { try { syncTest1.m3(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t4 = new Thread(()-> { try { syncTest1.m4(); } catch (InterruptedException e) { e.printStackTrace(); } }); t3.start(); t4.start(); t3.join(); t4.join(); } }
输出结果如下:
Thread-0得到锁 当前锁对象:503674368 m10 m11 m12 m13 m14 Thread-1得到锁 当前锁对象:503674368 m20 Thread-0释放锁 m21 m22 m23 m24 Thread-1释放锁 ====================== Thread-3得到锁 当前锁对象:650310003 m40 Thread-2得到锁 当前锁对象:1670595893 m30 m41 m31 m42 m32 m43 m33 m44 m34 Thread-3释放锁 Thread-2释放锁
结果分析:
如果用公用的一个内部Object,那么它实际上锁住的是那个实例化后的对象o,如果此时是不同的实例,那么这个内部对象o也就无意义了。
用整型Integer的形式做锁是否可行?
测试代码1
/**
* 不同实例对象下,调用同一个方法,观察是否能加锁成功
* 此时采用Integer作为锁,且值为1
*/
public class SyncTestInteger1 {
private final Integer lock = 1;
void m1() throws InterruptedException {
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "得到锁");
System.out.println("当前锁对象:" + System.identityHashCode(lock));
for(int i = 0;i<5;i++){
System.out.println("m1"+i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
public static void main(String[] args) throws InterruptedException {
SyncTestInteger1 syncTest1 = new SyncTestInteger1();
SyncTestInteger1 syncTest2 = new SyncTestInteger1();
Thread t1 = new Thread(()-> {
try {
syncTest1.m1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(()-> {
try {
syncTest2.m1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
返回结果如下:
Thread-1得到锁 当前锁对象:118531371 m10 m11 m12 m13 m14 Thread-1释放锁 Thread-0得到锁 当前锁对象:118531371 m10 m11 m12 m13 m14 Thread-0释放锁
测试代码2
/**
* 不同实例对象下,调用同一个方法,观察是否能加锁成功
* 此时采用Integer作为锁,且值为1024
*/
public class SyncTestInteger {
private final Integer lock = 1024;
void m1() throws InterruptedException {
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "得到锁");
System.out.println("当前锁对象:" + System.identityHashCode(lock));
for(int i = 0;i<5;i++){
System.out.println("m1"+i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
public static void main(String[] args) throws InterruptedException {
SyncTestInteger syncTest1 = new SyncTestInteger();
Thread t1 = new Thread(()-> {
try {
syncTest1.m1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(()-> {
try {
syncTest1.m1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
返回结果如下:
Thread-0得到锁 Thread-1得到锁 当前锁对象:118531371 当前锁对象:733365800 m10 m10 m11 m11 m12 m12 m13 m13 m14 m14 Thread-0释放锁 Thread-1释放锁
测试代码3
public class SyncTestInteger2 {
private final Integer lock1 = 1024;
private final Integer lock2 = 1024;
void m1() throws InterruptedException {
synchronized (lock1){
System.out.println(Thread.currentThread().getName() + "得到锁");
System.out.println("当前锁对象:" + System.identityHashCode(lock1));
for(int i = 0;i<5;i++){
System.out.println("m1"+i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
void m2() throws InterruptedException {
synchronized (lock2){
System.out.println(Thread.currentThread().getName() + "得到锁");
System.out.println("当前锁对象:" + System.identityHashCode(lock2));
for(int i = 0;i<5;i++){
System.out.println("m1"+i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
public static void main(String[] args) throws InterruptedException {
SyncTestInteger2 syncTest1 = new SyncTestInteger2();
Thread t1 = new Thread(()-> {
try {
syncTest1.m1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(()-> {
try {
syncTest1.m2();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
返回结果如下:
Thread-0得到锁 Thread-1得到锁 当前锁对象:72786614 当前锁对象:1430746659 m10 m10 m11 m11 m12 m12 m13 m13 m14 m14 Thread-0释放锁 Thread-1释放锁
结果分析
测试1和测试2中的两个锁对象,仅仅只是改变了lock的值,就运行出了截然不同的效果,测试1中可以发现两个方法是并行的,而测试2中却是逐步执行了,这是因为在Integer这个派生类内部维护着一个缓存池,缓存-128~127之间的值,这些值所指向的对象都是同一个,所以就导致了两种结果截然不同了,测试3中便印证了两个不同的Integer对象,值虽然相同,但是从锁对象上来看,完全是不同的,下面的截图也可以印证这一点
在Integer内部也有具体说明:
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
所以如果真的想用Integer来作为锁,那么范围一定不要再-128~127之间选择,当然,也可以根据实际情况进行选择,建议是不要使用基础类型来作为锁对象。
用String对象是否可行?
String对象下,内部存在一个缓存池的机制,如下:
即一个值被创建为String对象后,此次的jvm虚拟机中,就已经把当前值的地址记录下来了,后续不论怎么创建,都将继续采用这个地址,只要值一样,不论怎么创建。
所以通过这个原理,就可以知道,假设以String作为锁对象,那么,只要String内容一致,锁就是通用的,在各方法内都是这个地址的锁。