熟悉 Java 的多线程的一般都知道会有数据不一致的情况发生,比如两个线程在操作同一个类变量时,而保护数据不至于错乱的办法就是让方法同步或者代码块同步。同步时非原子操作就得同步,比如一个简单的 1+1 运算也该同步,以保证一个代码块或方法成为一个原子操作。
1. 当一个线程访问了对象的synchronized或synchronized(xxx)代码块时,非同步方法是否会阻塞?
2.不论是静态的或非静态的方法都加上 synchronized 关键字,那静态的方法和非静态的方法前加上 synchronized 关键字有区别吗?
4. 对方法加了 synchronized 关键字或用 synchronized(xxx) 包裹了代码,就一定能避免多线程环境下的数据破坏吗?
5. 对方法加 synchronized 关键字与用 synchronized(xxx) 同步代码块两种规避方法又有什么分别和联系呢?
为了理解上面的问题,我们还得了解基本的东西,就是监视区域:从 Java 对线程同步的原理上说起, Java 直接在语言级上支持多线程的,在多线程环境中我们要小心的数据是:
知道上面的针对上面的问题,我们通过下面代码进行逐一验证:
1)对于多个线程并发访问对象来说,非同步方法不会阻塞。
2)静态和非静态方法加上synchronized的区分是,监视区域的范围,非静态方法监视范围是对象级的,而非静态方法监视范围是类级的,和我们平常认识的类方法及实例方法一样。
代码说明:
public class Test3 {
private static int flag = 1;
public synchronized static void accum(int time){
flag++;
try{
Thread.sleep(time);
}catch(InterruptedException e){
e.printStackTrace();
}
flag--;
System.out.println("Thread: " + Thread.currentThread().getName()+ " /Current flag: " + flag);
}
public void acc(){
System.out.println("非同步方法:"+flag);
}
public synchronized void ac(){
flag--;
System.out.println("ac:"+flag);
}
public static void main(String[] args){
/*final Test3 test = new Test3(); ---同一个对象 监视范围为对象的线程可以正常阻塞
new Thread("one"){
public void run(){
test.accum(2000);
}
}.start();
new Thread("two"){
public void run(){
test.accum(10);
}
}.start();*/
/*new Thread("tt"){
public void run(){
test.acc();
}
}.start();*/
new Thread("one"){
public void run(){
new Test3().accum(2000);
}
}.start();
new Thread("two"){
public void run(){
new Test3().accum(100);
}
}.start();
new Thread("tt"){
public void run(){
new Test().acc();
}
}.start();
}
}
运行结果:
非同步方法:1
Thread: one /Current flag: 1 //等待2秒出现
Thread: two /Current flag: 1
去掉static标识,运行结果如下:
非同步方法:1
Thread: two /Current flag: 2
Thread: one /Current flag: 1 //等待2秒出现
3. 或者在可疑的代码块两旁用 synchronized(this) 或 synchronized(someObject) 包裹起来,而选用 this 还是某一个对象--someObject,又有什么不同呢?
验证代码如下:
private static int flag = 1;
private static Object obj = new Object();
public void accum(int time){
synchronized(this){ ------
flag++;
try{
Thread.sleep(time);
}catch(InterruptedException e){
e.printStackTrace();
}
flag--;
System.out.println("Thread: " + Thread.currentThread().getName()+ " /Current flag: " + flag);
}
}
public void acc(){
System.out.println("非同步方法:"+flag);
}
public void ac(){
synchronized(this){
flag--;
}
System.out.println("ac:"+flag);
}
public static void main(String[] args){
new Thread("one"){
public void run(){
new Test2().accum(1000);
}
}.start();
new Thread("two"){
public void run(){
new Test2().accum(20);
}
}.start();
}
运行结果:
Thread: two /Current flag: 2 //证明第2个线程没有被阻塞
Thread: one /Current flag: 1
-----对于synchronized(this)来说,其监视区域为对象,但上例中因为都是用new出来的对象,所以不同对象的监视区域不同,所以第二个线程不会被第一个线程阻塞掉
synchronized(this){ 把改行代码改成synchronized(object)
5. 对方法加 synchronized 关键字与用 synchronized(xxx) 同步代码块两种规避方法又有什么分别和联系呢?
----给方法加个关键字 synchronized 其实就是相当于把方法中的所有代码行框到了 synchronized(xxx) 块中。同步肯定会影响到效率,这也是大家知道的,因为它会造成方法调用的等待。方法中有些代码可能是线程安全的,所以可不用包裹在 synchronized(xxx) 中,提高线程安全的额外代码执行效率。