Synchronized 关键字的使用


为什么要使用sychronized?

在单线程运行下无需使用同步方法,但是在多个线程访问同一块代码时就常常会出现以下问题:

1、线程冲突 : 多个线程操作相同的数据上的操作交替执行时就可能产生线程冲突如下代码。

package com.zrh.thread;

public class Count {

	private int c = 0;
	
	public void increment(String threadName){
		System.out.println(threadName+"_before:"+c);
		c++;
		System.out.println(threadName+"_after:"+c);
	}
	
	public void decrement(String threadName){
		System.out.println(threadName+"_before:"+c);
		c--;
		System.out.println(threadName+"_after:"+c);
	}
	
	public int getValue(){
		return c;
	}
}

开启两个线程操作同对象交替操作数据如下:

package com.zrh.thread;

public class ThreadTest {

	public static void main(String[] args) throws InterruptedException {
		final Count count = new Count();
		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 100000; i++) {
					count.increment(Thread.currentThread().getName());
				}	
			}
		});
		
		thread1.setName("incrementThread");
		Thread thread2 = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 100000; i++) {
					count.decrement(Thread.currentThread().getName());
				}				
			}
		});		
		
		thread2.setName("decremenetThread");		
		thread1.start();
		thread2.start();
		Thread.sleep(10000);
		System.out.println("count:"+count.getValue());
	}

}

打印结果中可以发现 理想结果是0,实际打印非0,说明在多个线程值交替的操作相同数据时就可能出现线程冲突,数据被覆盖的情况:

……

incrementThread_before:-14
incrementThread_after:-13
incrementThread_before:-13
incrementThread_after:-12
count:-12

2、内存一致性错误 : 也叫内存可见性错误。产生的原因是cpu对于任何的操作总是把数据从主存读到寄存器让后将结果写回到主存中,若在这个过程中有其他线程读取到主存中的数据。就可能会读到脏数据。



3.Synchronized的使用:为了避免以上两种情况,我们通常会用到synchronized关键字。Java有提供两种同步方式:同步方法和同步语句。

同步方法:只要在方法的声明中加上synchronized关键字。

package com.zrh.thread;
public class SynchronizedCount {

	private int c = 0;
	
	public synchronized void increment(){
		c++;
	} 
	
	public synchronized void decrement(){
		c--;
	}
	
	public synchronized int getValue(){
		return c;
	}
}
测试

package com.zrh.thread;

public class ThreadTest {

	public static void main(String[] args) throws InterruptedException {
		final SynchronizedCount count = new SynchronizedCount();
		Thread thread1 = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 100000; i++) {
					count.increment();
				}	
			}
		});
		
		thread1.setName("incrementThread");
		Thread thread2 = new Thread(new Runnable() {
			public void run() {
				for (int i = 0; i < 100000; i++) {
					count.decrement();
				}				
			}
		});		
		
		thread2.setName("decremenetThread");		
		thread1.start();
		thread2.start();
		Thread.sleep(10000);
		System.out.println("count:"+count.getValue());
	}

}
运行结果如下,显然没有出现上面线程冲突的问题:

count:0


同步块:

		syschronized(synObject){
            //同步的代码块
          }

有时我们并不需要将整个方法都同步,当这个方法比较大时这样操作是很影响效率,所以我们可以尽量之间需要同步的代码同步就好,这个时候就可以使用同步块,线程要执行同步块,必须要获得synObject的锁,同步块可以灵活的指定任意要上锁的对象。在同一时间只能有一个线程执行同步块的代码,其他线程可以执行非同步块的代码。

在用同步块时最好能够更细粒度的同步,如下面代码中的c1和从c2,这两个对象的操作互不干扰,这种情况就可以不必使用同步方法。我们可以创建两个"锁对象"。

package com.zrh.thread;

public class MsLunch {
	private long c1 = 0;
	private long c2 = 0;
	private Object lock1 = new Object();
	private Object lock2 = new Object();
	
	public void inc1(){
		synchronized (lock1) {
		c1++;	
		}
	}
	
	public void inc2(){
		synchronized (lock2) {
		c2++;	
		}
	}

}

4.内部锁与同步 :上面提交的synObject的锁指的是对象锁(也叫内部锁或监视锁的实体),在Java API规范中称为监视器。同步机制就是建立与对象锁上的,其实每个对象都有监视器,但在往常的代码执行中监视器并不会产生任何影响,只有在同步的范围内监视器才起作用。同步锁有两个方面的作用:1.对一个对象的排他性访问建立happen-before的关系。这对可见性是十分重要的。当一个线程需要排他性的访问一个对象的域时,线程首先要请求该对象的对象锁,在线程获得内部锁到释放内部锁的这段时间里,我们就说该线程持有这个对象锁。当访问结束线程释放内部锁,在此之前其它线程将不能获得对象锁,如果其它线程尝试获得该对象锁,就会被阻塞。

 








猜你喜欢

转载自blog.csdn.net/sinat_36265222/article/details/78262372