volatile关键字以及使用场景

在多线程环境下,如果编程不当,可能会出现程序运行结果混乱的问题。
出现这个原因主要是,JMM 中主内存和线程工作内存的数据不一致,以及多个线程执行时无序,共同导致的结果。
volatile关键字以及使用场景_第1张图片
同时也提到引入synchronized同步锁,可以保证线程同步,让多个线程依次排队执行被synchronized修饰的方法或者方法块,使程序的运行结果与预期一致。
采用synchronized同步锁确实可以保证线程安全,但是它对服务性能的消耗也很大,synchronized是一个独占式的同步锁,比如当多个线程尝试获取锁时,其中一个线程获取到锁之后,未获取到锁的线程会不断的尝试获取锁,而不会发生中断,当冲突严重的时候,线程会直接进入阻塞状态,不能再干别的活。

为了实现线程之间更加方便的访问共享变量,Java 编程语言还提供了另一种同步机制:volatile域变量,在某些场景下使用它会更加方便。

public class DataEntity{
	
	private boolean isRunning = true;

	public void addCount(){
		System.out.println("线程运行开始……");
		while(isRunning){
		
		}
		System.out.println("线程运行结束……");
		
	}

	public boolean isRunning(){
		return isRunning;
	}

	public void setRunning(boolean running){
		isRunning = running;
	}
}
public class MyThread extends Thread{
	
	private DataEntity entity;

	public MyThread(DataEntity entity){
		this.entity = entity;
	}
	
	@Override
	public void run(){
		entity.addCount();
	}
}
public class MyThreadTest{
	
	public static void main(String[] args) throws InterruptedException{
		
		//初始化数据实体
		DataEntity entity new DataEntity();

		MyThread threadA = new MyThread(entity);
		threadA.start();

		//主线程则色1秒
		Thread.sleep(1000);

		//将运行状态设置为false
		entity.setRunning(false);
	}
}

从实际运行结果来看,程序进入死循环状态,虽然最后一行手动设置了entity.setRunning(false),但是没有起到任何的作用。
原因其实也很简单,虽然主线程main将isRunning变量设置为false,但是线程threadA 里面的isRunning变量还是true,两个线程看到的数据不一致。

/**
 * 在 isRunning 变量上加一个 volatile 关键字
 */
private volatile boolean isRunning = true;

添加上volatile关键字,线程正常结束
说明当主线程main将isRunning变量设置为false时,线程threadA 里面的isRunning值也随着发生变化。

说明被volatile修饰的变量,在多线程环境下,可以保证所有线程看到这个变量都是同一个值。

场景演示

某些场景volatile并不适用。

public class DataEntity{
	
	private volatile int count = 0;
	public void addCount(){
		for(int i=0;i<100000;i++){
			count++;
		}
	}

	public int getCount(){
		return count;
	}
}
public class MyThreadTest{
	
	public static void main(String[] args) throws InterruptedException{
		
		//初始化数据实体
		DataEntity entity = new DataEntity();

		//初始化5个线程
		CountDownLatch latch = new CountDownLatch(5);

		for(int i=0;i<5;i++){
			new Thread(new Runnable(){
				@Override
				public void run(){
					entity.addCount();
					//线程运行完毕减1
					latch.countDown();
				}
			}).start();
		}

		//等待以上线程执行完毕,再获取结果
		latch.await();
		System.out.println("result: " + entity.getCount());
	}
}

volatile关键字以及使用场景_第2张图片
理论上使用 5 个线程分别执行了100000自增,我们预期的结果应该是5*100000=500000,从实际的运行结果可以看出,与预期不一致。
这是因为volatile的作用其实是有限的,它只能保证多个线程之间看到的共享变量值是最新的,但是无法保证多个线程操作共享变量时依次有序,无法保证原子性操作。

上面的例子中count++不是一个原子性操作,在处理器看来,其实一共做了三个步骤的操作:读取数据、对数据加 1、回写数据,在多线程随机执行情况下,输出结果不能达到预期值。

三种解决方案:

一、采用synchronized

public clas DataEntityC2{
	
	private int count = 0;

	/**
		采用synchronized同步锁,实现多个线程执行方法时串行
	*/
	public synchronized void addCount(){
		for(int i = 0;i < 100000;i++){
			count++;
		}
	}

	public int getCount(){
		return count;
	}
}

二、采用Lock锁

public class DataEntityC2{
	
	private int count = 0;
	
	private Lock lock = new ReentrantLock();

	/**
		采用lock锁,实现多个线程执行方法时串行
	*/
	public void addCount(){
		for(int i=0;i<100000;i++){
			lock.lock();
			try{
				count++;
			}finally{
				lock.unlock();
			}
		}
	}

	public int getCount(){
		return count;
	}
}

三、采用JUC包中的原子操作

public class DataEntity{
	
	private AtomicInteger inc = new AtomicInteger();

	/**
		采用原子操作类,通过CAS循环方式保证操作原子性
	*/
	public void addCount(){
		for(int i=0;i<100000;i++){
			inc.getAndIncrement();
		}
	}

	public int getCount(){
		return inc.get();
	}
}

你可能感兴趣的:(java,开发语言,volatile关键字)