在并发编程中经常会碰到多个执行线程共享资源的问题。例如多个线程同时读写文件,共用数据库连接,全局的计数器等。如果不处理好多线程之间的同步问题很容易引起状态不一致或者其他的错误。
同步不仅可以阻止一个线程看到对象处于不一致的状态,它还可以保证进入同步方法或者块的每个线程,都看到由同一锁保护的之前所有的修改结果。处理同步的关键就是要正确的识别临界条件(critical section),即多线程访问/修改共享资源的代码块。
关键字synchronized可以保证同一时刻,只有一个线程可以执行某个方法或者某个代码块。 此外Java语言规范保证读写一个变量是原子的,除非long或者double.虽然语言规范保证了线程在读取原子数据的时候,不会看到任意的数值,但是并不保证一个线程写入的值对于另一个线程是可见的,线程间的通信,同步是必要的。
synchronized同步方法:
我们可以把synchronized关键字放在方法前,保证同时只有一个线程执行该方法。需要注意的是如果一个类的静态方法和实例方法都有synchronized,那么同意时刻允许一个线程访问静态方法,另一个线程访问实例方法。例如:
private static synchronized void stopRequested(){ stopRequest = true; }
注意确保synchronized 方法仅仅是非常短的修改或者读取共享资源,否则会引起性能问题,因为synchronized本质上是互斥的。
synchronized同步块:
synchronized (this) { // Java code }
同步块可以让我们仅仅同步临界段,其余部分可以并行。 对于相互独立的共享资源,可以用不同的锁同步。例如同时售两个电影院的票:
public class Cinema { private long vacanciesCinema1; private long vacanciesCinema2; private final Object controlCinema1, controlCinema2; public Cinema() { controlCinema1 = new Object(); controlCinema2 = new Object(); vacanciesCinema1 = 20; vacanciesCinema2 = 20; } public boolean sellTickets1(int number) { synchronized (controlCinema1) { if (number < vacanciesCinema1) { vacanciesCinema1 -= number; return true; } else { return false; } } } public boolean sellTickets2(int number) { synchronized (controlCinema2) { if (number < vacanciesCinema2) { vacanciesCinema2 -= number; return true; } else { return false; } } } }
虽然读写primitive类型是原子的,但是如果线程之间不同步,还是会引起数据不一致问题,看下面这个例子:
public class TestStop { private static boolean stopRequest; public static void main(String[]args) throws InterruptedException{ Thread t = new Thread( new Runnable(){ public void run(){ int i = 0; while(!stopRequest){ i++; } } }); t.start(); TimeUnit.SECONDS.sleep(2); stopRequest = true; } }
这段代码有时候无法停止,因为子线程没看到主线程对stopRequest的改变。解决的办法是加上对共享变量的同步访问:
public class TestStop { private static boolean stopRequest; private static synchronized void stopRequested(){ stopRequest = true; } private static synchronized boolean getStopRequest(){ return stopRequest; } public static void main(String[]args) throws InterruptedException{ Thread t = new Thread( new Runnable(){ public void run(){ int i = 0; while(!getStopRequest()){ i++; } } }); t.start(); TimeUnit.SECONDS.sleep(2); stopRequested(); } }
如果读写都没有同步,同步就没有效果。 针对此例,还可以用volatile,去掉同步。
在后续章节中,我们将介绍更多关于synchronize及Lock的内容。