线程同步
为什么要实现同步?
java的多线程实现了并发控制,当多个线程同时处理同一个内存单元时,可能会引发错误,比如经典的问题—银行取钱问题,当多个用户同时进行取钱时可能会出现余额为负数的情况(如果没有实现同步)
实现同步的方法
1、同步代码块
由关键字synchronized修饰的代码块
语法格式:
synchronized(obj){
//此处的代码就是同步代码块
}
//上面语法格式中synchronized后括号里的obj就是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
//注意任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。同步是一种高开销的操作,应该尽可能的减少同步的使用,对重要的代码块实现同步即可。
2、同步方法
由关键字synchronized修饰的方法
public synchronized void draw(){
}
//对于synchronized修饰的实例方法,无需显式指定同步监视器,同步方法的同步监视器是this
//注意:synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰显式构造器,成员变量等。
3、同步锁Lock
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关Condition对象
在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁),使用该对象可以显式的加锁,释放锁,通常使用ReentrantLock的代码如下:
class X
{
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
//定义需要保证线程安全的方法
public void m(){
lock.lock();
try
{
//需要保证线程安全的代码
//...method body
}
finally{
lock.unlock();
}
}
}
//使用ReentrantLock 对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来保证在必要的时候释放锁。
//ReentrantLock锁具有可重用性,也就是说,一个线程可以对已被加锁的ReentrantLock锁再次加锁。ReentrantLock对象会维持一个计数器来跟踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
死锁
当两个线程相互等待对方释放同步监视器时会发生死锁,java虚拟机没有监测,也没有采取措施来处理死锁的情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何的提示,只是所有的线程处于阻塞状态,无法继续。
注意:由于Thread类的suspend()方法也很容易导致死锁,所以java不再推荐使用该方法来暂停线程的执行。