java的多线程编程中,常常会有由多个线程共享的变量,如果这些线程的操作不是原子的,那么就会出现问题,比如说多个线程都有这样一个操作:balance = balance + 1; 显然这不是原子操作,其中一个线程更新了balance,另一个线程却没有收到,最终会导致错误的结果。这种错误是由并发访问代码块导致的,为了阻止这一点,java语言提供了synchronized关键字来达到这一目的。
synchronized有两种出现形式,分别是synchronized methods同步方法和synchronized statements/block 同步语句/同步代码块 。
首先说明一下synchronized是什么,在java1.0版本之后java中每个对象都有一个内部锁,如果一个方法声明时有synchronnized关键字,那么对象的锁将保护整个方法,当一个线程在执行一个对象的同步方法时,所有其他线程如果调 用同一对象的同步方法块,则会挂起执行,直到第一个线程针对此对象的操 作完成 。对于同步块,.锁能确保与其他请求获取相同对象锁的线程互斥访问,但如果其他线程没有使用synchronized(obj)或者利用了不同的锁,那么同步会失效。
下面先看一下同步方法,当线程调用同步方法时,它会自 动获取该方法所在对象的内部锁,并在方法返回时释放它。
下面是同步方法的通用格式
public synchronized void method()
{
method body
}
在上面的代码中,因为run被设置为同步方法,5个线程调用同一个对象threadTest的同步代码块,所以每个线程在执行run方法时并不会被打断,值得注意的是如果将单个对象threadTest改为new Thread_01b(),那么5个线程调用就是不同对象,每个线程在执行run方法时都有被打断的可能。
对于同步块,当线程进入如下形式的块时:
synchronized(obj)
{
critical section
}
它会获得obj的锁,于是之前使用同步方法达成的并发程序同样可以用同步块来实现。
同样值得注意的是这种互斥运行关系必须请求获取相同对象锁,在这个程序中就是lock,如果在每一次循环中新建一个lock,即使字符串相同,那也属于不同对象,锁就会失效。
同步方法和同步代码块之间的差别就在于同步块需要显式给出lock,但对于锁控制的范围更加精细。
以上的锁对象无法解决多线程中可能出现的所有问题,比如说死锁。
T1: synchronized(a){ synchronized(b){ … } }
T2: synchronized(b){ synchronized(a){ … } }
T1和T2就出现了死锁,因为T1要释放a必须获得b,T2要释放b必须获得a,总结一下死锁出现在两个方法互相要等待对方完成才能获得启动的锁。对于死锁问题,可以用使用粗粒度的锁来同步多个对象等解决方法,但同样会引发其它很多问题,目前java对死锁的处理仍然很麻烦,所以在程序设计得过程中要尽量避免死锁。