同步锁(Lock)
Java1.5开始,Java提供了一种功能更强大的线程同步机制—-通过显示定义同步锁对象来实现同步,在这种机制下,同步锁使用Lock对象充当。
Lock有很多种类,某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock和ReadWriteLock是java 5新提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。
我们使用ReentrantLock来修改取钱的代码,添加同步的限制:
private final ReentrantLock look=new ReentrantLock();
public void draw(String name,double drawAmount){
//加锁
look.lock();
try {
if(balance>=drawAmount){
System.out.println(name+"取钱成功! 取出="+drawAmount);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改金额
balance=balance-drawAmount;
System.out.println("\t 余额为:"+balance);
}else{
System.out.println(name+"取钱失败,账号余额不足!");
}
}finally {
//释放锁
look.unlock();
}
}
上面程序中的第一行代码定义了一个ReentrantLock对象,程序中实现draw()方法时,进入方法执行后立即请求对ReentrantLock对象进行加锁,当执行完draw()方法的取钱逻辑之后,程序使用finally块来确保锁的释放。
注意: 同步方法或同步代码块使用与竞争资源相关的,隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取到多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被释放时相同的范围内释放所有锁。
同步锁(Lock)使用的注意事项:
- Lock提供了同步方法和同步代码块所没有的其他功能,包括用于非块结构的tryLock()方法,以及试图获取可中断锁的lockInterruptibly方法,还有获取超时失效锁tryLock(long,TimeUnit); 方法。
- ReentrantLock锁具有可重入性,也是说,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显示调用unlock()释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
释放线程锁的锁定
注:这里说的线程锁的锁定,只指的是同步代码块和同步方法,Lock需要显示释放锁定所以不在讨论范围之内。
任何线程在进入同步代码块,同步方法之前,必须先获得对同步代码块的锁定,那么何时会释放同步监视器的锁定呢?
程序无法显示的释放同步监视器的锁定。线程会在如下几种情况下释放对同步监视器的锁定:
当前线程的同步方法,同步代码块执行结束,当前线程即释放同步监视器。
当前线程在同步代码块,同步方法中遇到break,return终止了该代码块或该方法的继续执行,当前线程会释放同步监视器。
当前线程在同步代码块,同步方法中出现了未处理的Error或Excepation,导致了该代码块,该方法异常结束时,当前线程会释放同步锁。
当前线程执行同步代码或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。
在如下情况下,线程不会释放同步锁:
线程执行同步代码块或同步方法时,程序调用了Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。
程序执行同步代码块时,其他线程执行了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。
死锁
最后讨论一下这个牛逼的概念以及它是怎么产生的。死锁是个啥?当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有检测,也没有采取措施处理死锁这种情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,整个程序即不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
下面用代码来实现一下死锁,哈哈!
package com.example.thread;
/**
- auther: Simon zhang
- Emaill:18292967668@163.com
*/
public class DeadLock implements Runnable {
class A{
public synchronized void foo(B b){
System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了A实例的foo方法");//①
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B实例的last方法");//③
b.last();
}
public synchronized void last(){
System.out.println("进入了A实例的last方法");
}
}
class B{
public synchronized void bar(A a){
System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了B实例的bar方法");//②
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A实例的last方法");//④
a.last();
}
public synchronized void last(){
System.out.println("进入了B实例的last方法");
}
}
A a=new A();
B b=new B();
public void init(){
Thread.currentThread().setName("主线程");
a.foo(b);
System.out.println("进入了主线程之后");
}
@Override
public void run() {
Thread.currentThread().setName("子线程");
b.bar(a);
System.out.println("进入了子线程之后");
}
public static void main(String args[]) throws Exception {
DeadLock dl=new DeadLock();
new Thread(dl).start();
dl.init();
}
}
运行上面的程序:
1当前线程名:主线程进入了A实例的foo方法
2当前线程名:子线程进入了B实例的bar方法
3当前线程名:主线程企图调用B实例的last方法
4当前线程名:子线程企图调用A实例的last方法