多线程的原子性,可见性,排序性
1、原子性(Atomicity)
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,
要不就不执行。如果一个操作时原子性的,那么多线程并发的情况下,就不会出现变量被修改的情
况.a++;
这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安
全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作
,那么我
们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这
些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
2、可见性(Visibility)
可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改.Java内存模型是
通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介
的方法来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的
区别是volatile的特殊规则保证了新值能立即同步到主内存以及每使用前立即从内存刷新。
1.Java还有两个关键字能实现可见性,它们是synchronized。同步块的可见性是由“对一个变量执行
unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这条规则获得的.
2.而final关键字的可见性是指:被final修饰的字段是构造器一旦初始化完成,并且构造器没有把“
this”引用传递出去,那么在其它线程中就能看见final字段的值。
用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直
接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰
内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这
个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。
3、有序性(Ordering)
Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。
4、
不能解决 原子性的原因 https://blog.csdn.net/xdzhouxin/article/details/81236356
是一个关键字,用于修饰成员变量,主要用于解决可见性和有序性问题,但是无法解决原子性问题。
强制将线程本地内存改变后的值刷新到主内存
public class MyThread extends Thread { //保证所有线程每次使用a时都会强制更新 public volatile static int a = 0; @Override public void run() { System.out.println("线程启动,休息2秒..."); try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("将a的值改为1"); a = 1; System.out.println("线程结束..."); } } public class TestDemo { public static void main(String[] args) { //1.启动线程 MyThread t = new MyThread(); t.start(); //2.主线程继续 while (true) { if (MyThread.a == 1) { System.out.println("主线程读到了a = 1"); } } } } 输出结果: 线程启动,休息2秒... 将a的值改为1 线程结束... 主线程读到了a = 1 主线程读到了a = 1 ...
5、
解决多句代码的原子性问题
a.synchronized是什么??
一个可以用于让多行代码保证原子性的关键字
b.synchronized的作用??
让多行代码"同步",当某个线程进入这多行代码执行时,其他线程是无法进入的,直到多行代码都运行完毕了,其他线程才能进入
c.
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
//原理和同步代码块基本是一致的,区别在于同步代码块的锁对象是我们指定的,而同步方法也需要锁对象,但是不需要我们指定
//由编译器自己指定,指定当前对象this
/** * 卖票任务 */ public class MyRunnable implements Runnable { /** * 票数 */ public int count = 100; @Override public void run() { while (true){ if (count > 0) { try { Thread.sleep(30);//模拟卖票掏钱消耗的时间 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖出第"+count+"张票"); count--; } } } } public class TestDemo { public static void main(String[] args) { //0.创建任务 MyRunnable mr = new MyRunnable(); //1.创建窗口123 Thread t1 = new Thread(mr); Thread t2 = new Thread(mr); Thread t3 = new Thread(mr); t1.start(); t2.start(); t3.start(); //出现多线程安全问题: //a.出现了重复数据 //b.出现了0,-1非法数据 } } 重复数据出现的原因: 当某个线程卖出某张后,还没来得及对票数减1,被其他线程抢走CPU,导致其他线程也卖出同张票!! 非法数据出现的原因: 当只剩下最后一张票时,由于多线程的随机切换可能多个线程都会通过大于0的判断, 最终导致卖出的票是0,-1,-2....这些张数!!!
抢票案例解决方案
格式: synchronized(任意对象){ //也叫做锁对象 需要同步的代码(需要保证原子性操作的那些代码) } /** * 卖票任务 */ public class MyRunnable implements Runnable { /** * 票数 */ public int count = 100; /** * 创建一个对象 */ public Object obj = new Object(); @Override public void run() { while (true){ //同步代码块 synchronized (obj) {//任意对象都可以作为锁对象 if (count > 0) { System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票"); count--; } } } } }
Lock实际上是一个接口,我们要使用它的实现类,ReentrantLock锁对象
其中有两个方法: public void lock(); //获取锁 public void unlock(); //释放锁 格式: Lock lock = new ReentrantLock(); lock.lock();//加锁 需要同步的代码,需要保证原子性的代码 lock.unlock();//解锁
扩展
在Java 中Lock 接口比synchronized 块的优势是什么?你需要实现一个高效的缓存,
它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实
现它?
整体上来说Lock 是synchronized 的扩展版,Lock 提供了无条件的、可轮询的
(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队
列的(newCondition 方法)锁操作。另外Lock 的实现类基本都支持非公平锁(默认)和
公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效
的选择。