Java多线程之线程同步

线程安全问题

在单线程中不会出现线程安全问题,而多线程编程中,如果多个线程同时操作同一个资源,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,由于每个线程执行的过程是不可控的,比如两个线程同时检查某个文件是否存在,如果存在则文件数+1,不存在则创建新文件,最后产生的结果很可能创建两个新文件,原因就是第一个线程检测到文件不存在时,在创建新文件前,第二个线程也检测到文件不存在,所以线程二也要新建一个文件,最后的结果就是新建了两个文件,与初衷不符,这个就是线程安全问题。

解决线程同步问题的两个方法

在java中有两种机制可以防止线程安全的发生,Java语言提供了一个synchronized关键字来解决这问题,同时在Java SE5.0引入了Lock锁对象的相关类,接下来分别介绍这两种方法。

通过synchronied关键字的方式

在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

synchronized原理

JVM基于进入和退出Monitor对象来实现代码块同步和方法同步,两者实现细节不同,但本质上都是对一个对象的监视器(monitor)的获取。任意一个对象都拥有自己的监视器,当同步代码块或同步方法时,执行方法的线程必须先获得该对象的监视器才能进入同步块或同步方法,没有获取到监视器的线程将会被阻塞,并进入同步队列,状态变为BLOCKED。当成功获取监视器的线程释放了锁后,会唤醒阻塞在同步队列的线程,使其重新尝试对监视器的获取。

代码块同步:在编译后通过将monitorenter指令插入到同步代码块的开始处,将monitorexit指令插入到方法结束处和异常处,任何一个对象都有一个monitor与之关联,线程执行monitorenter指令时,会尝试获取对象对应的monitor的所有权,即尝试获得对象的锁。

方法同步:synchronized方法在method_info结构有ACC_synchronized标记,线程执行时会识别该标记,获取对应的锁,实现方法同步。

两种使用synchronized关键字的实现方式

  1. synchronized修饰方法
public synchronized void method{  
  //需要同步的代码  
} 
  1. synchronized修饰代码块
Object obj = new Object();  
synchronized(obj){  
  //需要同步的代码  
}

通过锁(Lock)对象的方式

Java SE5.0之后并发包中新增了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
ReentrantLock(重入锁)是支持重新进入的锁,它表示该锁能够支持一个线程对资源的重复加锁,也就是说在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞,同时还支持获取锁的公平性和非公平性。这里的公平是在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平锁,反之,是不公平的。

使用ReentrantLock的实现方式

ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁    
ReentrantLock lock = new ReentrantLock(true); //公平锁    

lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果    
try {    
    //操作    
} finally {    
    lock.unlock();  //释放锁  
}    

synchronized和ReentrantLock的区别

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
  3. Lock可以让等待锁的线程响应中断,而synchronized不可以,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
  4. 通过Lock可以知道是否成功获取到锁,而synchronized却无法做到。
  5. Lock可以提高多个线程进行读操作的效率。

猜你喜欢

转载自blog.csdn.net/xdzhouxin/article/details/80329747