本文共 4550 字,大约阅读时间需要 15 分钟。
volatile关键字的作用是使得变量在多个线程间可见。
/** * Volatile关键字:使变量在多个线程间可见 */ public class J18Volatile { public static void main(String[] args) throws InterruptedException { Volatile volatile1 = new Volatile(); volatile1.start(); Thread.sleep(1000);
// //2、第一次运行:会发现设置为false依然是无效的,依然是还是处于运行状态 //看回之前的线程运行图 //在JDK1.5给线程加了个独立线程空间,每次线程执行都是执行自己的独立空间的数据, //这也说明 了即便设置为false,执行结果依然是false,还是没有停止,因为此时该的是主内存的数据, //而不是线程独立空间的这个线程的值,即线程执行和自己的独立空间变量关系。 //main方法的线程,volatile1.start()线程,总共2个线程 volatile1.setRun(false); System.out.println(volatile1.isRun());
//第二次运行,在添加了volatile关键字,会发现更改为false,就正常停止了,因为此时每次都是和主内存的变量交互 } } class Volatile extends Thread{ //1、未加volatile关键字 // private boolean isRun = true;
//3、添加volatile关键字 private volatile boolean isRun = true;
public void setRun(boolean isRun) { this.isRun = isRun; }
public boolean isRun() { return isRun; }
@Override public void run() { System.out.println("run..."); int i = 0; while (isRun) {
} System.out.println("线程停止"); } } |
以上的例子中可以看到,volatile关键字虽然拥有多个线程之间的可见性,即都从主内存中去拿同一个变量,但是不具备同步性(原子性),可以算得上是一个轻量级的synchronized,性能要比synchronized强,不会造成阻塞,但不能替代synchronized的同步功能,并且很多开源框架的代码都会用到volatile(netty)。
但是需要原子性操作的时候,可以用Atomic系列类对象,这个对象就是具备原子性操作的,以下案例可以看到,10个线程同时操作一个方法,不用加锁,方法内是对int变量进行++操作,会出现数字不正确,而Atomic类型是可以保证数据的原子性。
Atomic系列类对象是保证方法的结果的原子性,并不保证多次操作的原子性,即每个线程的操作结果不是按顺序的。
/** * Volatile关键字不具备synchronized关键字的原子性(同步性) */ public class J19VolatileNotAtomic { public static void main(String[] args) throws InterruptedException { //1、开启10个线程,会发现结果并不是10000 VolatileNotAtomic volatileNotAtomic = new VolatileNotAtomic(); for (int i = 0; i < 10; i++) { new Thread(volatileNotAtomic).start(); }
Thread.sleep(2000);
//2、开启10个线程,会发现结果是10000 VolatileAtomic volatileAtomic = new VolatileAtomic(); for (int i = 0; i < 10; i++) { new Thread(volatileAtomic).start(); }
//3、Atomic系列类对象是保证方法的结果的原子性,并不保证多次操作的原子性,即每个线程的操作结果不是按顺序的。 } } class VolatileNotAtomic implements Runnable {
private volatile int count;
public void addCount() { for (int i = 0; i < 1000; i++) { count++; } System.out.println("Not:" + count); }
@Override public void run() { addCount(); } } class VolatileAtomic implements Runnable {
private AtomicInteger count = new AtomicInteger();
public void addCount() { for (int i = 0; i < 1000; i++) { count.incrementAndGet(); } System.out.println("Have:" + count); }
@Override public void run() { addCount(); } } |
/** * Atomic系列类踩坑 * Atomic类只保证本身(一个)方法的原子性,不保证多个操作的原子性 */ public class J20AtomicClass { public static void main(String[] args) { AtomicClass atomicClass = new AtomicClass(); for (int i = 0; i < 10; i++) { new Thread(atomicClass,"t" + i).start(); } } } class AtomicClass implements Runnable {
private AtomicInteger count = new AtomicInteger(0);
public int add() { count.addAndGet(1); count.addAndGet(2); count.addAndGet(3); return count.get(); }
/* * 未加synchronized时,多个addAndGet在一个方法内是非原子性的。 * 因此需要加synchronized修饰,保证多个addAndGet方法的整体原子性。 */ @Override // public void run() { public synchronized void run() { System.out.println(Thread.currentThread().getName() + " - " + add()); } } |
Atomic系列类的原理是通过自旋CAS(Compare And Swap)操作volatile变量实现的,即比较内存中的旧值与预期值是否相同进而决定是否交换。
CAS就是比较内存中的对象和当前对象的值是否相同,相同的话才回更新内存中的值,不同的话则会返回失败。
Atomic的问题,一是ABA问题,即对于一个旧变量值A,线程2将A的值改成B又改成A,此时线程1通过CAS看到A并没有变化,实际A已经发生变化了,而要解决这个问题的可以通过记录一下变量的版本就可以了,在变量值发生变化时,比较一下版本就知道变量有没有变化,其中AtomicStampedReference就是这样的思路,其中MySQL中的Innodb的多版本并发锁也是这样做的。
二是Atomic系列类的自旋问题,Atomic类会尝试多次CAS操作,直至失败或成功,这个过程叫自旋;通过自旋过程可以看出自旋操作并不会把线程挂起,从而避免了内核线程切换,但是自旋的过程也可以看成CPU死循环,会一直占用CPU资源,在竞争并不激烈的情况下效率会大大提高,相反竞争激烈的情况下,失败率会大大提高,因此一般会有个失败次数限制的,也可以使用LongAddr类来替换,这个类采用的思想是分段锁来解决并发竞争的问题。
ThreadLocal是用来定义线程局部变量的,是一种在多线程并发访问变量的间接方法,与synchronized等加锁方式不同,ThreadLocal完全不提供所,而使用以空间换时间的手段,为每个线程提供便利的独立副本,以保证线程安全。
从性能上来看,ThreadLocal并不具备优势,在并发不是很高的时候,加锁的性能会更好,但作为一套与所完全无关的线程安全解决方案,在高并发量或竞争激励的场景中,使用ThreadLocal可以在一定程度上减少所的竞争。
/** * ThreadLocal案例 */ public class J21ThreadLocal { public static void main(String[] args) throws InterruptedException { ThreadLocalTest test = new ThreadLocalTest();
new Thread(new Runnable() { @Override public void run() { test.setValue("AA"); System.out.println(Thread.currentThread().getName() + " " + test.getValue()); } },"t1").start();
Thread.sleep(1000);
new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " " + test.getValue()); } },"t2").start();
} } class ThreadLocalTest {
private ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
public Object getValue() { return this.threadLocal.get(); }
public void setValue(Object value) { threadLocal.set(value); } } |
转载地址:http://llmxi.baihongyu.com/