多线程编程在项目中的应用极为重要,深刻理解JAVA在多线程的实现原理,可以有效解决各种并发问题。
在多线程编程中,经常会用到关键字volatile和synchronized,下面将详细说一下这两个关键字的作用和区别。
1、volatile
在JAVA内存模型中,分为主内存(main memory or stack memory)和工作内存(working memory),主内存保存着JAVA的实例变量、静态变量和数组元素;每个线程都有自己的工作内存,工作内存由缓存和栈组成,其中缓存保存着主存中变量的拷贝,栈保存着线程中的局部变量。线程之间无法直接通信,必须通过主内存进行通信。
下图说明了两个线程进行通信的过程,假设主内存有共享变量i,线程A和线程B如果都调用到了主内存的共享变量i,则线程A和线程B的工作内存都会保存这个共享变量i的拷贝,线程A在工作内存中修改了共享变量i后,会将其同步到主内存,再给线程B去调用。
volatile的作用是:告诉JAVA虚拟机,使用volatile修饰的变量,只会存在主内存中,工作内存中并不会保存它的拷贝。
volatile变量只能保证变量的可见性,即只存在主内存中,但无法保证变量修改的原子性,下面我们举一个例子来说明。
public class ThreadA extends Thread { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 100000; i++) { Example.add(); } System.out.println("Thread A finished"); } }
public class ThreadB extends Thread { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 100000; i++) { Example.add(); } System.out.println("Thread B finished"); } }
public class Example { public static volatile int count = 0; public static void add(){ count = count + 1; } public static void main(String[] args) { new ThreadA().start(); new ThreadB().start(); try { Thread.sleep(5*1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("count="+Example.count); } }
在上面的例子中,我们让两个线程对共享变量count都自增100000次,期望的结果是200000,但程序输出的结果是:
Thread A finished Thread B finished count=153167
与我们的期望是不一致的,可以volatile无法保证volatile变量修改的原子性。惹要保持变量的数据同步,则在方法中加入synchronized则可。如下:
public class Example { public static volatile int count = 0; public synchronized static void add(){ count = count + 1; } public static void main(String[] args) { new ThreadA().start(); new ThreadB().start(); try { Thread.sleep(5*1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("count="+Example.count); } }
程序输出的结果是:
Thread A finished Thread B finished count=200000
2、synchronized
synchronized 用来锁住一个方法、代码块、对象和类,被锁住的内容同时最多被一个线程访问。
2.1 锁住类
synchronized 关键字实现锁住类的方式有两种,一种是锁住静态方法,一种是锁住类变量,两种方式的作用一样,当锁住一个类时,该类所有同步的静态方法或代码块都会被锁住,但不会锁住非同步方法或同步的非静态方法。
2.1.1 锁住静态方法
public class ThreadExampleA extends Thread { public ThreadExampleA(String name){ super.setName(name); } @Override public void run() { SynchronizedExample.lockMethodA(this); } }
public class ThreadExampleB extends Thread { public ThreadExampleB(String name){ super.setName(name); } @Override public void run() { SynchronizedExample.lockMethodB(this); } }
public class SynchronizedExample { public synchronized static void lockMethodA(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodA:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public synchronized static void lockMethodB(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodB:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public static void main(String[] args) { new ThreadExampleA("A").start(); new ThreadExampleB("B").start(); } }
上面的例子我们使用两个线程来访问类SynchronizedExample 中的两个静态方法,程序输出的结果是:
lockMethodA:A:1 lockMethodA:A:2 lockMethodA:A:3 lockMethodA:A:4 lockMethodA:A:5 lockMethodB:B:1 lockMethodB:B:2 lockMethodB:B:3 lockMethodB:B:4 lockMethodB:B:5
从程序的输出结果可以看出,两个同步的静态方法可以实现互斥,即当一线程访问类中的同步静态方法时,会给该类中的所有同步的静态方法加上锁,其他线程不能访问该类中同步的静态方法,一直到该锁被释放,但其他线程可以访问该类中的同步非静态方法或非同步静态方法,看以下例子:
public class ThreadExampleA extends Thread { public ThreadExampleA(String name){ super.setName(name); } @Override public void run() { SynchronizedExample.lockMethodA(this); } }
public class SynchronizedExample { public synchronized static void lockMethodA(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodA:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public synchronized void lockMethodB(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodB:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public static void main(String[] args) { SynchronizedExample example = new SynchronizedExample(); new ThreadExampleA("A").start(); new ThreadExampleB("B", example).start(); } }
程序的输出结果是:
lockMethodA:A:1 lockMethodB:B:1 lockMethodB:B:2 lockMethodA:A:2 lockMethodB:B:3 lockMethodA:A:3 lockMethodB:B:4 lockMethodA:A:4 lockMethodB:B:5 lockMethodA:A:5
看以下例子,锁住一个同步的静态方法,但线程可以访问其他非同步的静态方法。
public class SynchronizedExample { public synchronized static void lockMethodA(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodA:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public static void lockMethodB(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodB:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public static void main(String[] args) { new ThreadExampleA("A").start(); new ThreadExampleB("B").start(); } }
程序输出的结果是:
lockMethodA:A:1 lockMethodB:B:1 lockMethodA:A:2 lockMethodB:B:2 lockMethodA:A:3 lockMethodB:B:3 lockMethodA:A:4 lockMethodB:B:4 lockMethodA:A:5 lockMethodB:B:5
2.1.2 锁住类变量
通过锁住类变量来锁住代码块,其作用与锁住静态方法相同,会同时锁住该类中的所有同步的静态方法或同步的静态代码块,但不会锁住非同步的静态方法或同步的非静态方法。
public class SynchronizedExample { public synchronized static void lockMethodA(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodA:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public static void lockMethodB(Thread thread){ synchronized(SynchronizedExample.class){ int i = 1; while(i < 6){ try { System.out.println("lockMethodB:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } } public static void main(String[] args) { new ThreadExampleA("A").start(); new ThreadExampleB("B").start(); } }
程序的输出结果是:
lockMethodA:A:1 lockMethodA:A:2 lockMethodA:A:3 lockMethodA:A:4 lockMethodA:A:5 lockMethodB:B:1 lockMethodB:B:2 lockMethodB:B:3 lockMethodB:B:4 lockMethodB:B:5
2.2 锁住对象
synchronized锁住对象也有两种方法,一种是锁住非静态方法,另一种是锁住对象变量,两种方法的作用是一样的,可以实现互斥。如果synchronized用来锁住一个非静态方法或对象变量,则同时会锁住这个对象的所有同步的非静态方法和同步的代码块。
2.2.1 锁住非静态方法
public class SynchronizedExample { public synchronized void lockMethodA(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodA:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public synchronized void lockMethodB(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodB:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public static void main(String[] args) { SynchronizedExample example = new SynchronizedExample(); new ThreadExampleA("A", example).start(); new ThreadExampleB("B", example).start(); } }
程序输出的结果是:
lockMethodA:A:1 lockMethodA:A:2 lockMethodA:A:3 lockMethodA:A:4 lockMethodA:A:5 lockMethodB:B:1 lockMethodB:B:2 lockMethodB:B:3 lockMethodB:B:4 lockMethodB:B:5
2.2.2 锁住对象变量
public class SynchronizedExample { public synchronized void lockMethodA(Thread thread){ int i = 1; while(i < 6){ try { System.out.println("lockMethodA:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } public void lockMethodB(Thread thread){ synchronized(this){ int i = 1; while(i < 6){ try { System.out.println("lockMethodB:"+thread.getName() + ":" + i); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } } public static void main(String[] args) { SynchronizedExample example = new SynchronizedExample(); new ThreadExampleA("A", example).start(); new ThreadExampleB("B", example).start(); } }
程序输出的结果是:
lockMethodA:A:1 lockMethodA:A:2 lockMethodA:A:3 lockMethodA:A:4 lockMethodA:A:5 lockMethodB:B:1 lockMethodB:B:2 lockMethodB:B:3 lockMethodB:B:4 lockMethodB:B:5