java中volatile关键字的介绍(个人使用-介绍从简)-CFK

1、volatile关键字修饰共享变量,所以变量在多线程中的可见性。

  • 实现共享变量的可见性,保证数据一致性,就实现了同步机制,但是volatile没有给变量加锁。所以volatile是轻量级的同步机制。
  • 当然synchronizied关键字给变量加锁,保证变量的同步。浪费性能
  • volatile可见性的原理:被volatile修饰的变量,在编译的时候,源代码对这个变量的读写操作的语句的前后,会被插入内存屏障,禁止了一些处理器对指令的重排序,所以能保证对这个变量的写操作总是在读操作之前完成。
  • volatile保证可见性的过程:线程取一个变量的值,为了提高存取的速率,一般会把变量从系统主内存中读取到线程的本地内存中,如果线程中改了这个变量的值,那么有时候更改后的值不能及时的写进主内存,而且有的线程是一起取得值,就算写到主内存中,其他线程也不知道,这就造成数据不一致。但是使用了volatile修饰的变量,一个线程从改变变量的值后,会立即更新主内存,利用多处理器的缓存一致性,其他处理器会发现自己的缓存行对应的内存地址被修改,就会之前获取的数据设为无效,重新获取。
  • volatile保证的是可见性,不能实现互斥和原子操作。互斥可以 加锁、信号量机制(semphore)实现。
public class TestVolatile {
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		while (true) {
			if (td.isFlag()) {
				System.out.println("########");
				break;
			}
			//System.out.println("1111");
		
		}
		}
	}
class ThreadDemo implements Runnable {
	private boolean flag = false;
	public void run() {
		try {
			// 该线程 sleep(200), 导致了main中的#####一直无法输出
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		flag = true;
		System.out.println("flag=" + isFlag());
	}
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

上面的程序中的main函数的######是不能输出的,因为可见性问题。
原因:首先因为新线程sleep(200)所以main取到的flag=false; 但是因为main中的while(true)里面直接就一个if(td.isFlag()),所以main主线程没时间去更新flag的值,只能从主线程的缓存中取,还是false。

  • 解决一:如果在main的while中添加其他的语句,那么if(td.isFlag())就不那么繁忙了,就 有喘息的时间去系统主内存更新flag的值了。
  • 解决二:加锁,这个方法 可能和第一个原因一样,每次main线程获取锁对象的时候给它时间去更新flag的值。当时浪费系统性能

public class TestVolatile {
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		while (true) {
			synchronized (td) {
				if (td.isFlag()) {
					System.out.println("########");
					break;
				}
			}
		}
	}
}

class ThreadDemo implements Runnable {
	private boolean flag = false;

	public void run() {
		try {
			// 该线程 sleep(200), 导致了main中的#####一直无法输出
			Thread.sleep(200);
		} catch (InterruptedException e) {
			Thread.interrupted();
			e.printStackTrace();
		}
		flag = true;
		System.out.println("flag=" + isFlag());
	}
	
	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

*解决三:用volatile修饰flag变量

public class TestVolatile {
	public static void main(String[] args) {
		ThreadDemo td = new ThreadDemo();
		new Thread(td).start();
		while (true) {
				if (td.isFlag()) {
					System.out.println("########");
					break;
				}
		}
	}
}

class ThreadDemo implements Runnable {
	private volatile boolean flag = false;

	public void run() {
		try {
			// 该线程 sleep(200), 导致了main中的#####一直无法输出
			Thread.sleep(200);
		} catch (InterruptedException e) {
			Thread.interrupted();
			e.printStackTrace();
		}
		flag = true;
		System.out.println("flag=" + isFlag());
	}
	
	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
}

2、虽然volatile不具有让数据保持原子性,但是java.util.concurrent.atomic包下提供了一系列的原子变量,这些原子的值就是用volatile修饰来实现的原子操作

  • 下面的第一个代码就有问题,因为serialNumber++;不是一个原子操作
public class TestAtomicDemo {
	public static void main(String[] args) {
		AtomicDemo ad = new AtomicDemo();
		for (int i = 0; i < 10; i++) {
			new Thread(ad).start();
		}
	}
}

class AtomicDemo implements Runnable {
	private int serialNumber = 0;

	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
		}
		System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
	}

	public int getSerialNumber() {
		return serialNumber++;
	}
}



  • 解决方法:使用java.utils.concurrent.automic提供的原子变量操作(用volatile的可见性实现)
import java.util.concurrent.atomic.AtomicInteger;

class AtomicDemo implements Runnable {
	
	private AtomicInteger serialNumber = new AtomicInteger();
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
		}
		System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
	}
	
	public int getSerialNumber() {
		// 自增运算
		return serialNumber.getAndIncrement();
	}
}

3、简单介绍volatile的注意事项

  • 首先锁的特性有两个:互斥性、可见性。
    • 互斥性保证一次只能一个线程访问,可见性保证,在第一个线程释放锁后,第二个线程获取锁进入共享代码执行的时候,现在得到的变量的值或者之前就获得的值是最新的值。
  • 首先volatile只能实现变量的可见性,但是有可见性也不一定就能保证同步,因为volatile没有原子性。
    • 比如实现计数器的时候:(i++) 这个操作,假如两个线程对volatile i ;具体不知道咋说,反正知道这个有问题就行了,必须加锁才能解决,或者用juc中的原子操作类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值