java线程相关的3个关键字synchronize、ThreadLocal、volatile也许在我们平常保持数据一致性场景中用到不多,因为它们都是基于JVM的特征,而我们的应用一般都是集群部署,分布式环境下线程的一致性几乎用不到它们,但在一些底层的源码中,还会看到他们的身影,比如ConcurrentHashMap、StringBuffer中,synchronize为线程安全保驾护航,那么接下来看下它们都有哪些特性
synchronize
用synchronize关键字来修饰一个方法、一个代码块的时候,能保证同一个时刻只能有一个线程可以执行,具体的特性:
1. 当2个并发线程访问同1个对象object中的这个synchronized方法时,一个时间内只能有1个线程得到执行。另一个线程必须等待当前线程执行完这个方法以后才能执行该方法 (潜台词是如果2个线程并发访问2个对象的synchronize方法时,是异步执行的,可见注释掉的代码)。
看下代码:
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
*
* @DESC 2个并发线程访问1个对象的synchronized方法时,一个时间内只能有一个线程得到执行
*/
public class SynchronizedDemo1 {
public synchronized void synMeth() {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
public static void main(String[] args) {
//2个线程同时访问1个对象的synchronize方法,一个时间只能有一个线程得到执行
final SynchronizedDemo1 t1 = new SynchronizedDemo1();
Thread ta = new Thread(new Runnable() {
public void run() {
t1.synMeth();
}
}, "A");
Thread tb = new Thread(new Runnable() {
public void run() {
t1.synMeth();
}
}, "B");
ta.start();
tb.start();
//2个线程同时访问2个对象的synchronize方法,互不影响(即一个时间可能有2个线程在执行)
/*final SynchronizedDemo1 t2 = new SynchronizedDemo1();
final SynchronizedDemo1 t3 = new SynchronizedDemo1();
Thread tc = new Thread(new Runnable() {
public void run() {
t2.synMeth();
}
}, "C");
Thread td = new Thread(new Runnable() {
public void run() {
t3.synMeth();
}
}, "D");
tc.start();
td.start();*/
}
}
执行结果如下,线程A、B是串行执行的,线程C、D是交叉执行的,因为C、D对应的是2个对象嘛,锁的不是同一个对象,当然互不影响了(每台机器CPU的执行结果的顺序会不同,为了得到真实的结果,建议多运行几次来观察)
2. 当1个线程访问object的一个synchronized方法时,另1个线程仍然可以访问该object中的非synchronized方法。
看下代码:
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
* @DESC 1个线程访问某个object的synchronized方法、synchronize代码块时,其他并发线程仍然可以访问该object的非synchronize方法
*/
public class SynchronizedDemo2 {
public synchronized void synMethod() {
for (int i=0; i<500; i++) {
System.out.println(Thread.currentThread().getName() + " syn loop " + i);
}
}
public void noSynMethod() {
for (int i=0; i<500; i++) {
System.out.println(Thread.currentThread().getName() + " nosyn loop " + i);
}
}
public static void main(String[] args) {
final SynchronizedDemo2 obj = new SynchronizedDemo2();
//1个线程访问1个object的synchronize方法,其他并发线程可以访问该object的非synchronize方法
Thread t1 = new Thread(new Runnable() {
public void run() {
obj.synMethod();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
public void run() {
obj.noSynMethod();
}
}, "t2");
t1.start();
t2.start();
}
}
执行结果如下:
synchronize方法和非synchronize方法交叉执行,互不影响。但这在某些场景下,会发生脏读,就是使用了synchronize方法,但得到的并不是我想要的结果,看下面这个脏读的case:
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
* @DESC 多线程访问中的脏读
*/
public class SynchronizedDemo6 {
private int num = 10;
public synchronized void synMethod() {
try {
//模拟业务处理
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num += 1;
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod, num=" + num);
}
public void noSynMethod() {
System.out.println("thread name : " + Thread.currentThread().getName() + ", noSynMethod, num=" + num);
}
public static void main(String[] args) {
final SynchronizedDemo6 obj = new SynchronizedDemo6();
new Thread(new Runnable() {
public void run() {
obj.synMethod();
}
}, "t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发生脏读
new Thread(new Runnable() {
public void run() {
obj.noSynMethod();
}
}, "t2").start();
}
}
执行结果如下:
线程t1执行了synMethod后,num应该变为11,但是线程t2通过noSynMethod去打印,发现还是之前的值,那该怎么处理呢?那就接下来看看下一个case吧
3. 当1个线程访问object的一个synchronized方法时,其他线程对object中所有其它synchronized方法的访问将被阻塞。
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
*
* @DESC 1个线程访问某个object的synchronized方法时,其他并发线程对该object的所有其他
* synchronize方法的访问都将被阻塞
*/
public class SynchronizedDemo3 {
private int num = 10;
public synchronized void synMethod1() {
try {
//模拟业务处理
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num += 1;
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod1, num=" + num);
}
public synchronized void synMethod2() {
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod2, num=" + num);
}
public static void main(String[] args) throws Exception{
final SynchronizedDemo3 obj = new SynchronizedDemo3();
new Thread(new Runnable() {
public void run() {
obj.synMethod1();
}
}, "t1").start();
Thread.sleep(1000);
new Thread(new Runnable() {
public void run() {
obj.synMethod2();
}
}, "t2").start();
}
}
看下结果,发现脏读消失了,两个线程是同步执行的,因为线程对方法的调用是串行执行的,随之而来的就是性能的消耗,那能不能有更细粒度的锁,比如对某个变量加锁,而不是对整个对象加锁?当然是有的,看下个例子
4. 不同的锁之间是异步的,比如变量锁和对象锁,互不影响,但每一种锁都有以上1、2、3种特性。 (锁就是一种对象监视器,不同的对象监视器之间是异步的)
看下代码:
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
*
* @DESC 1个线程访问1个object的synchronized变量时,另1个线程可以访问该object的synchronize方法
* 这是因为1个是变量锁,一个是对象锁,它们之间不冲突
*/
public class SynchronizedDemo4 {
private String lock = new String();
//object对象锁
public synchronized void synMeth() {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName() + " synMeth loop " + i);
}
}
public void noSynMeth() {
//变量锁
synchronized (lock) {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName() + " synPara loop " + i);
}
}
}
public static void main(String[] args) {
//1个线程访问synchronize的变量锁时,另一个并发线程可以访问synchronize方法
final SynchronizedDemo4 t = new SynchronizedDemo4();
Thread ta = new Thread(new Runnable() {
public void run() {
t.synMeth();
}
}, "A");
Thread tb = new Thread(new Runnable() {
public void run() {
t.noSynMeth();
}
}, "B");
ta.start();
tb.start();
}
}
执行结果:
synchronize(lock)是一种变量锁,非对象锁,那这种方式有什么好处呢?
如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。
说明:synchronize修饰方法跟synchronize(this){}是一样的,它们都是针对object对象加锁。当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
看下代码:
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
*
* @DESC synchronize修饰方法时,是this对象锁,跟synchronize(this)一样,都是this对象锁
*/
public class SynchronizedDemo5 {
//object对象锁
public synchronized void synMeth1() {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName() + " synMeth1 loop " + i);
}
}
public void synMeth2() {
//object对象锁
synchronized (this) {
for (int i = 0; i < 500; i++) {
System.out.println(Thread.currentThread().getName() + " synMeth2 loop " + i);
}
}
}
public static void main(String[] args) {
final SynchronizedDemo5 t = new SynchronizedDemo5();
Thread ta = new Thread(new Runnable() {
public void run() {
t.synMeth1();
}
}, "A");
Thread tb = new Thread(new Runnable() {
public void run() {
t.synMeth2();
}
}, "B");
ta.start();
tb.start();
}
}
执行结果如下:
5. 锁重入:当一个线程得到一个对象锁之后,再次请求此对象锁时是可以再次得到该对象的锁的。这也说明在一个synchronize方法/块的内部调用本类的其他synchronize方法/块时,是永远可以得到锁的
package com.syj.test.thread;
/**
* Created by syj on 2019/1/7.
* @DESC 锁重入
*/
public class SynchronizedDemo7 {
public synchronized void synMethod1() {
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod1 bengin");
synMethod2();
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod1 end");
}
public synchronized void synMethod2() {
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod2 bengin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod2 end"); }
public static void main(String[] args) {
final SynchronizedDemo7 obj = new SynchronizedDemo7();
//锁重入
new Thread(new Runnable() {
public void run() {
obj.synMethod1();
}
}, "t1").start();
new Thread(new Runnable() {
public void run() {
obj.synMethod1();
}
}, "t2").start();
}
}
执行结果如下,线程t1拿到对象锁后,可以直接调用其他synchronize方法,其他线程等待t1释放锁后,才可以执行:
volatile
待完善