一、Volatile关键字
主要作用是使变量在多个线程间可见。
(一)、普通死循环
package com.it.po.thread07; public class PrintString { private boolean isContinuePrint=true; public boolean isContinuePrint(){ return isContinuePrint; } public void setContinuePrint(boolean isContinuePrint){ this.isContinuePrint=isContinuePrint; } public void printString(){ String threadName=Thread.currentThread().getName(); try { while (isContinuePrint==true){ System.out.println("线程: "+threadName+" 进入printString方法。。。"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.it.po.thread07; public class PrintRun { public static void main(String[] args){ PrintString po = new PrintString(); po.printString(); System.out.println("我是主线程,我要停止printString方法"); po.setContinuePrint(false); } }
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
。。。。
线程一直在处理while循环无法到达
po.setContinuePrint(false);
(二)、解决同步死循环
package com.it.po.thread07; public class PrintThread implements Runnable { private boolean isContinuePrint=true; public boolean isContinuePrint(){ return isContinuePrint; } public void setContinuePrint(boolean isContinuePrint){ this.isContinuePrint=isContinuePrint; } public void printString(){ String threadName=Thread.currentThread().getName(); try { while (isContinuePrint==true){ System.out.println("线程: "+threadName+" 进入printString方法。。。"); Thread.sleep(1000);//当前类是线程即可切换到main线程 } } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { printString(); } }
package com.it.po.thread07; public class PrintRun2 { public static void main(String[] args){ try { PrintThread printThread = new PrintThread(); new Thread(printThread).start();//自动执行run方法 Thread.sleep(5000); System.out.println("我是主线程,我要停止printString方法"); printThread.setContinuePrint(false); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程: Thread-0 进入printString方法。。。 线程: Thread-0 进入printString方法。。。 线程: Thread-0 进入printString方法。。。 线程: Thread-0 进入printString方法。。。 线程: Thread-0 进入printString方法。。。 我是主线程,我要停止printString方法
当上面代码的格式,一旦运行在-server服务器模式中64bitd JVM上时,会出现四循环解决办法是使用volatile关键字
该关键字的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中获取变量的值。
(二)、解决异步死循环
先看一个小实验
package com.it.po.thread07; public class RunThread extends Thread { private boolean isRunning=true; public boolean isRunning(){ return isRunning; } public void setIsRunning(boolean isRunning){ this.isRunning=isRunning; } @Override public void run() { super.run(); String threadName=Thread.currentThread().getName(); while (isRunning==true){ System.out.println("线程: "+threadName+" 进入printString方法。。。"); } System.out.println("run方法被停止了。。。"); } }
package com.it.po.thread07; import com.sun.org.apache.bcel.internal.generic.NEW; public class Run3 { public static void main(String[] args) throws InterruptedException { RunThread runThread = new RunThread(); new Thread(runThread,"A").start(); Thread.sleep(100); runThread.setIsRunning(false); System.out.println("main 线程停止了 runThread"); } }

注意这样的代码,在开发工具JVM设置为Server服务器中的环境,是会出现四循环的
由于 变量 private boolean isRunning=true; 存在于公共堆栈及线程的私有堆栈中
在JVM设置为-server模式中,为了线程运行的效率,线程一直在私有栈中取得
isRunning=true ,而代码 runThread.setIsRunning(false) 虽然被执行,更新的确是
公共堆栈中的isRunning 变量值变成false ,所以一直死循环。
问题原因:
私有堆栈中和公共堆栈中变量不同步造成的,解决这个问题就得用volatile了
其作用:当线程访问isRunning这个变量时,强制性从公共堆栈中取值。

优化如下
package com.it.po.thread07; public class RunThread extends Thread { volatile private boolean isRunning=true; public boolean isRunning(){ return isRunning; } public void setIsRunning(boolean isRunning){ this.isRunning=isRunning; } @Override public void run() { super.run(); String threadName=Thread.currentThread().getName(); while (isRunning==true){ System.out.println("线程: "+threadName+" 进入printString方法。。。"); } System.out.println("run方法被停止了。。。"); } }
volatile 关键字强制从公共堆栈读取值如下图
总结:volatile最致命的缺点是不支持原子性
volattile和 synchronized 的比较
1)volattile是线程同步轻量级实现,所以性能比synchronized 要好,但是volattile只能修饰于变量,
随着jdk的更新,synchronized 性能在提升。
2)多线程访问volattile不会发生阻塞,synchronized 会。
3)volattile保证数据的可见性,但是不能保证原子性,synchronized 是可以保证数据原子性的,
synchronized 也可以间接保证可见性,因为它可将私有内存和公共内存的数据做同步。
4)注意:volattile解决的是变量在多个线程之间的可见性;
而synchronized 关键字解决的是多个线程之间访问资源的同步性。
注:线程安全包含原子性和可见性,java的同步机制都是围绕着两个方面来确保线程安全。
二、volatile非原子的特性
该关键字增加了实例变量在多线程之间的可见性,但是不具备同步性,也就不具备原子性,
package com.it.po.thread07; public class MyThread4_1 extends Thread{ volatile public static int count;//多线程这里需要共享变量 private static void addCount(){//用static修饰的count 方法必须是static for(int i=1;i<=100;i++){ count++; } System.out.println("count= "+count); } @Override public void run() { super.run(); addCount(); } }
package com.it.po.thread07; public class Run4 { public static void main(String[] args){ MyThread4_1[] arr=new MyThread4_1[100]; for(int i=0;i<100;i++){ arr[i]= new MyThread4_1(); } for(int i=0;i<100;i++){ arr[i].start(); } } }

优化如下
package com.it.po.thread07; public class MyThread4_1 extends Thread{ public static int count;//多线程这里需要共享变量 synchronized private static void addCount(){ //方法必须有static这样多线程锁的是类,实现同步效果 for(int i=1;i<=100;i++){ count++; } System.out.println("count= "+count); } @Override public void run() { super.run(); addCount(); } }
总结 :
volatile主要使用场合是在多线程中可以感知实例变量被更改了,而且可以获得最新的值使用。
(一)、图演示volatile非线程安全的原因(变量在内存中的工作过程)

1)read和load阶段:从主内存变量到当前线程工作内存
2)use和assign阶段:执行代码,改变共享变量值
3)storeh和write阶段:用工作内存数据刷新主存对应变量的值
多线程环境中,useh和assign是多次出现的,但是这一操作不熟原子性,
也就是read和load之后,如果主内存变量发生修改,线程工作内存的值由于已经
加载,不会发生对应变化,也就是私有内存和主内存不同步。
对于volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存
的值是最新的,例如线程1和线程2在进行read和load的操作中,发现主内存中
count的值都是5,那么都会加载到5,所以volatile解决的是变量读取时的可见性问题。
(二)、使用原子类进行i++
操作i++,除了使用 synchronized实现同步之外,还可以使用 AtomicInteger 原子类实现
原子类是不能分割的整体,没有其他线程可以中断或检查正在原子操作的变量。
它可以在没有锁的情况下做到线程安全。
package com.it.po.thread07; import java.util.concurrent.atomic.AtomicInteger; public class AddCountThread extends Thread { private AtomicInteger atomicInteger =new AtomicInteger(); @Override public void run() { super.run(); for(int i=0;i<100;i++){ System.out.println(atomicInteger.incrementAndGet()); } } }
package com.it.po.thread07; public class Run5 { public static void main(String[] args){ AddCountThread countThread = new AddCountThread(); Thread t1 = new Thread(countThread); Thread t2 = new Thread(countThread); Thread t3 = new Thread(countThread); Thread t4 = new Thread(countThread); Thread t5 = new Thread(countThread); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }

(三)、原子类并不完全安全
package com.it.po.thread07; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class MyService3 { public static AtomicLong po =new AtomicLong(); public void addNum(){ String name= Thread.currentThread().getName(); System.out.println("线程 "+name+" 加了100之后: "+po.addAndGet(100)); po.addAndGet(1); } }
package com.it.po.thread07; public class MyThread05 extends Thread { private MyService3 myService3; public MyThread05(MyService3 myService3) { this.myService3 = myService3; } @Override public void run() { super.run(); myService3.addNum(); } }
package com.it.po.thread07; public class Run6 { public static void main(String[] args) throws InterruptedException { MyService3 myService3 = new MyService3(); MyThread05[] myThread05s=new MyThread05[5]; for(int i=0;i<5;i++){ myThread05s[i]= new MyThread05(myService3); } for(int i=0;i<5;i++){ myThread05s[i].start(); } Thread.sleep(4000); System.out.println("myService3= "+MyService3.po.get()); } }
线程 Thread-0 加了100之后: 100 线程 Thread-3 加了100之后: 200 线程 Thread-4 加了100之后: 302 线程 Thread-1 加了100之后: 402 线程 Thread-2 加了100之后: 504 myService3= 505
,修改
package com.it.po.thread07; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class MyService3 { public static AtomicLong po =new AtomicLong(); synchronized public void addNum(){ String name= Thread.currentThread().getName(); System.out.println("线程 "+name+" 加了100之后: "+po.addAndGet(100)); po.addAndGet(1); } }
线程 Thread-2 加了100之后: 100 线程 Thread-1 加了100之后: 201 线程 Thread-0 加了100之后: 302 线程 Thread-4 加了100之后: 403 线程 Thread-3 加了100之后: 504 myService3= 505

浙公网安备 33010602011771号