缘由:Java 语言规范中指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而 volatile 关键字就是提示 VM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在 synchronized 代码块中,或者为常量时,不必使用。
线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步,因此存在A和B不一致的情况。volatile就是用来避免这种情况的。 volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到)
Volatile 变量具有 de style="font-style: normal; ">synchronizedde> 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。
sleep() vs wait()
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
(如果变量被声明为volatile,在每次访问时都会和主存一致;如果变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁时变量被同步。)
四、例子:
Demo1:
package test.thread;
/**
*
* @author ydj
*
*/
class SynTest{
//非同步
static void method(Thread thread){ System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } //同步方式一:同步方法
synchronized static void method1(Thread thread){//这个方法是同步的方法,每次只有一个线程可以进来 System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } //同步方式二:同步代码块
static void method2(Thread thread){
synchronized(SynTest.class) { System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } } //同步方式三:使用同步对象锁 private static Object _lock1=new Object(); private static byte _lock2[]={};//据说,此锁更可提高性能。源于:锁的对象越小越好 static void method3(Thread thread){ synchronized(_lock1) { System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } } public static void main(String[] args){ //启动3个线程,这里用了匿名类
for(int i=0;i<3;i++){
new Thread(){
public void run(){ method(this); //method1(this); //method2(this); //method3(this); } }.start(); } } } /** 执行method()方法结果:
begin Thread-0
begin Thread-2
begin Thread-1
end Thread-1
end Thread-0
end Thread-2
说明了:在没有同步限制的条件下,多个线程可以同时进入一个对象的方法中操作。
这样对共享可变数据是不安全的,即常说的:非线程安全(non thread-safe)。
*/
/**
执行method1()/method2()/method3()方法结果(可能线程进入的顺序不同):
begin Thread-0
end Thread-0
begin Thread-1
end Thread-1
begin Thread-2
end Thread-2
说明了:在同步限制的条件下,同时只可有一个线程进入一个对象的方法中操作,其它线程必须等待先它的线程退出后才可进入。
这样可保证共享可变数据是安全的,即常说的:线程安全(thread-safe )。
*/
Demo2:
package test.thread;
import com.util.LogUtil;
使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在 synchronized 代码块中,或者为常量时,不必使用。
线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步,因此存在A和B不一致的情况。volatile就是用来避免这种情况的。 volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到)
Volatile 变量具有 de style="font-style: normal; ">synchronizedde> 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。
sleep() vs wait()
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
(如果变量被声明为volatile,在每次访问时都会和主存一致;如果变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁时变量被同步。)
四、例子:
Demo1:
package test.thread;
/**
*
* @author ydj
*
*/
class SynTest{
//非同步
static void method(Thread thread){ System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } //同步方式一:同步方法
synchronized static void method1(Thread thread){//这个方法是同步的方法,每次只有一个线程可以进来 System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } //同步方式二:同步代码块
static void method2(Thread thread){
synchronized(SynTest.class) { System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } } //同步方式三:使用同步对象锁 private static Object _lock1=new Object(); private static byte _lock2[]={};//据说,此锁更可提高性能。源于:锁的对象越小越好 static void method3(Thread thread){ synchronized(_lock1) { System.out.println("begin "+thread.getName()); try{ Thread.sleep(2000); }catch(Exception ex){ ex.printStackTrace(); } System.out.println("end "+thread.getName()); } } public static void main(String[] args){ //启动3个线程,这里用了匿名类
for(int i=0;i<3;i++){
new Thread(){
public void run(){ method(this); //method1(this); //method2(this); //method3(this); } }.start(); } } } /** 执行method()方法结果:
begin Thread-0
begin Thread-2
begin Thread-1
end Thread-1
end Thread-0
end Thread-2
说明了:在没有同步限制的条件下,多个线程可以同时进入一个对象的方法中操作。
这样对共享可变数据是不安全的,即常说的:非线程安全(non thread-safe)。
*/
/**
执行method1()/method2()/method3()方法结果(可能线程进入的顺序不同):
begin Thread-0
end Thread-0
begin Thread-1
end Thread-1
begin Thread-2
end Thread-2
说明了:在同步限制的条件下,同时只可有一个线程进入一个对象的方法中操作,其它线程必须等待先它的线程退出后才可进入。
这样可保证共享可变数据是安全的,即常说的:线程安全(thread-safe )。
*/
Demo2:
package test.thread;
import com.util.LogUtil;