并发编程之关键字(synchronized、volatile)
并发编程主要设计两个关键字:一个是synchronized,另一个是volatile。下面主要讲解这两个关键字,并对这两个关机进行比较。
synchronized
Monitor对象
- 每一个对象都与一个Monitor相关联,一个monitor的lock在某一刻只能被一个线程获取。【排他性】
- monitor有一个计数器,当为0时,该monitor的lock未被获取;当有线程持获取monitor时,则monitor计数器加一,释放时减一。
- Monitor分为This Monitor和Class Monitor。This Monitor对应类的实例方法,Class Monitor对应类的静态方法。
- synchronized关键字实例方法时,争取的是同一个monitor的锁,与之关联的引用是ThisMonitor的实例引用。即:同一个类中的不同多线程方法,使用的是同一个锁。
- 将静态方法声明为synchronized。该静态方法被调用后,对应的class对象将会被锁住(使用的是ClassMonitor)。其他线程无法调用该class对象的所有静态方法, 直到资源被释放。
synchronized原理
synchronized本质上是对monitor对象的获取,通过JMV种的monitorenter和monitorexit指令实现同步。monitorenter指令是在编译后插入到同步代码的开始位置,而monitorexit插入到同步代码的结束位置和异常位置。每一个对象都与一个monitor相关联,当monitor被持有后,它将处于锁定状态。
- 编码
package concurrency; public class SynchronizedDemo { public static void test() { synchronized (SynchronizedDemo.class) { System.out.println("Synchronized Demo"); } } public static void main(String[] args) { test(); } }
- 查看反编译汇编命令
javap -c SynchronizedDemo.class
锁对象
当一个线程试图访问同步代码时,它必须先获得锁;退出或者抛出异常时,必须释放锁。Java中,每一个对象都可以作为锁。具体的表现形式有3种:
- 对于普通的同步方法,锁是当前的实例对象(this对象)
权限修饰符 synchronized 返回值类型 函数名(形参列表..){ //函数体 }
- 对于静态同步方法,锁是当前类的Class对象
权限修饰符 static synchronized 返回值类型 函数名(形参列表..){ //函数体 }
- 对于同步方法块,锁是Synchronized括号中配置的对象
- 锁对象必须是多线程共享的对象,否则锁不住
Synchronized(锁){ //需要同步的代码块 }
注意:在同步代码块/同步方法中调用sleep()不会释放锁对象,调用wait()会释放锁对象
synchronized的缺陷及解决方案
Synchronized提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候可能会被阻塞,而这种阻塞有两个明显的缺陷:1. 无法控制阻塞时长; 2. 阻塞不能被中断
public class SyncDefect { /** *线程休眠一个小时 */ public synchronized void syncMethod(){ try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { SyncDefect defect = new SyncDefect(); new Thread(defect::syncMethod,"t1").start(); //休眠3毫秒后启动线程t2,确保t1先进入同步方法 TimeUnit.MILLISECONDS.sleep(3); Thread t2 = new Thread(defect::syncMethod, "t2"); t2.start(); //休眠3毫秒后中断线程t2,确保t2已经启动 TimeUnit.MILLISECONDS.sleep(3); t2.interrupt(); System.out.println(t2.isInterrupted()); //true System.out.println(t2.getState()); //BLOCKED } }
针对synchronized的两个缺点,可以使用BooleanLock来解决
public interface Lock { void lock() throws InterruptedException; /** * 指定获取锁的超时时间 * @param mills 等待获取锁的最大时间 * @throws InterruptedException * @throws TimeoutException */ void lock(long mills) throws InterruptedException, TimeoutException; void unlock(); List<Thread> getBlockedThreads(); }
public class BooleanLock implements Lock { /** * 记录取得锁的线程 */ private Thread currentThread; /** * Bollean开关,标志锁是否已经被获取 */ private boolean locked = false; private List<Thread> blockedList = new ArrayList<>(); @Override public void lock() { //使用同步代码块的方式获取锁 synchronized (this) { Thread currentThread = Thread.currentThread(); //当锁已经被某个线程获取,将当前线程加入阻塞队列,并使用this.wait()释放thisMonitor while (locked){ try { if(!blockedList.contains(currentThread)){ blockedList.add(currentThread); } this.wait(); } catch (InterruptedException e) { blockedList.remove(currentThread); e.printStackTrace(); } } blockedList.remove(currentThread); this.locked = true; this.currentThread = currentThread; } } @Override public void lock(long mills) throws InterruptedException, TimeoutException { synchronized (this){ if(mills <= 0) {//时间不合法,调用默认的lock() this.lock(); } else { long remainingMills = mills; long endMills = System.currentTimeMillis() + remainingMills; while (locked) { if (remainingMills <= 0) {//在指定的时间内未获取锁或者当前线程被其它线程唤醒,抛出异常 throw new TimeoutException(Thread.currentThread().getName()+" can't get lock during "+mills); } if(!blockedList.contains(Thread.currentThread())){ blockedList.add(Thread.currentThread()); } //等待remainingMills后重新尝试获取锁 this.wait(remainingMills); remainingMills = endMills - System.currentTimeMillis(); } blockedList.remove(Thread.currentThread()); this.locked = true; this.currentThread = Thread.currentThread(); } } } @Override public void unlock() { synchronized (this) { if(Thread.currentThread() == currentThread) { this.locked = false; this.notifyAll(); } } } @Override public List<Thread> getBlockedThreads() { return Collections.unmodifiableList(blockedList); } }
/** * 测试阻塞中断 */ public class BooleanLockInterruptTest { private final Lock lock = new BooleanLock(); public void syncMethod() { try { lock.lock(); System.out.println(Thread.currentThread().getName()+" get lock."); TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("BLOCKED THREAD :"+lock.getBlockedThreads()); lock.unlock(); } } public static void main(String[] args) throws InterruptedException { BooleanLockInterruptTest test = new BooleanLockInterruptTest(); new Thread(test::syncMethod,"t1").start(); TimeUnit.MILLISECONDS.sleep(3); Thread t2 = new Thread(test::syncMethod, "t2"); t2.start(); TimeUnit.MILLISECONDS.sleep(3); t2.interrupt(); System.out.println(t2.isInterrupted()); //true System.out.println(t2.getState()); //RUNNABLE } }
/** * 测试超时 */ public class BooleanLockTimeOutTest { private final Lock lock = new BooleanLock(); public void syncTimeOutMethod() { try { lock.lock(1000); System.out.println(Thread.currentThread().getName()+" get lock."); TimeUnit.HOURS.sleep(1); } catch (InterruptedException | TimeoutException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { BooleanLockTimeOutTest test = new BooleanLockTimeOutTest(); new Thread(test::syncTimeOutMethod,"t1").start(); TimeUnit.MILLISECONDS.sleep(3); new Thread(test::syncTimeOutMethod, "t2").start(); } }
volatile
volatile特性
-
内存可见性:是指线程修改了变量的值之后,其他线程能立即知道此值发生了改变。而线程在对volatile修饰的变量进行读写操作时,每次都会从主存中把数据读到线程缓存里(保证访问到的值是最新的)。【确保所有的线程看到这个变量的值时一致的】
//没有volatile,if判断时,flag一直会读取本地缓存中的值,一直是false。 //private static boolean flag = false; //有volatile,if判断时,flag会从内存中重新读取值,flag被线程修改后会读取到新的值。 private static volatile boolean flag = false; @Test public void testVolatile() throws InterruptedException { new Thread(() -> { flag = false; System.out.println("Flag is modified"); }).start(); while (true) { if (flag) { System.out.println("Flag is true."); break; } } }
- 禁止命令重排:指令重排是指在执行程序时,编译器和处理器常常会对指令进行重排序,已到达提高程序性能的目的。volatile会告诉JVM当前变量的值不需要被编译器优化。
private static int a,b,x,y; @Test public void testResort() throws InterruptedException { for (int i = 0; i < Integer.MAX_VALUE; i++) { new Thread(() -> { // 有可能发生指令重排,先 x=a 再 b=1 b=1; x=a; }).start(); new Thread(() -> { // 有可能发生指令重排,先 y=b 再 a=1 a=1; y=b; }).start(); Thread.sleep(100); if(x==0 && y==0) { System.out.printf("第%d次,x==0 && y==0\n",i); break; }else { System.out.printf("第%d次,x==%d && y==%d\n",i,x,y); } }
- volatile不是线程安全的,不能提供原子性【volatile不适用于修饰用于算数运算的变量,多次运行结果都不相同】
private static volatile int num = 0; @Test public voidtestAtomic() throws InterruptedException { for (int i = 0; i < 100; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { num++; } }).start(); } Thread.sleep(2000); System.out.println(num); }
实现原子性,需要使用Atomic【Atomic是线程安全的。每次执行结果都等于100000】
private static AtomicInteger count = new AtomicInteger(0); @Test public void testAtomic() throws InterruptedException { for (int i = 0; i < 100; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { count.incrementAndGet(); } }).start(); } Thread.sleep(2000); System.out.println(count); }
比较synchronized和volatile
|
volatile
|
synchronized
|
作用对象
|
实例变量、类变量
|
方法、代码块
|
原子性
|
不具备
|
具备
|
可见性
|
具备
|
具备
|
可见性原理
|
使用机器指令的方式迫使其它工作内存中的变量失效
|
利用monitor锁的排它性实现
|
是否会指令重排
|
否
|
是
|
是否造成线程阻塞
|
否
|
是
|
更多内容,请访问:http://www.cnblogs.com/BlueStarWei