这篇博客主要总结死锁以及Lock锁的获取
多线程死锁
Java的线程死锁是一个十分经典的问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有任务都无法继续完成,就会出现死锁的情况。所以,在多线程编程中,”死锁“是必须要避免的,因为会造成程序的卡死。
首先先举一个死锁的例子:
class MyThread implements Runnable{ public String name; public Object lock1 = new Object(); public Object lock2 = new Object(); public void setName(String name) { this.name = name; } @Override public void run() { if("a".equals(name)){ synchronized (lock1){ try{ System.out.println("name = "+this.name); Thread.sleep(2000); }catch (InterruptedException i){ i.printStackTrace(); } synchronized (lock2){ System.out.println("按照lock1->lock2的顺序"); } } } if("b".equals(name)){ synchronized (lock2){ try{ System.out.println("name = "+this.name); Thread.sleep(2000); }catch (InterruptedException i){ i.printStackTrace(); } synchronized (lock1){ System.out.println("按照lock2->lock1的顺序"); } } } } } public class Main{ public static void main(String[] args){ try{ MyThread myThread = new MyThread(); myThread.setName("a"); Thread thread = new Thread(myThread); thread.start(); Thread.sleep(500); myThread.setName("b"); Thread thread1 = new Thread(myThread); thread1.start(); }catch (InterruptedException i){ i.printStackTrace(); } } }
先解释一下这段代码:一开始只有一个线程进入run方法,在这个时候name = a;执行输出语句,执行完之后进入两秒的sleep等待时间,在这个时候该线程持有lock1的锁。在这时,另一个线程也进入了run方法,但是这个时候name = b;继续执行输出语句,另一个线程持有的是lodk2的锁。我们这个时候会发现:线程1想要执行下面的语句,需要获得lock2的锁,但是这个锁被另一个线程所持有。同理,另一个线程也想获得lock1的锁。所以他们互相拿着对方需要的锁。造成了程序的卡死。这就是一个很简单的死锁的例子。
Lock锁:
在JDK1.5以后,引入了java.util.concurrent.locks包,新提供了一种实现同步访问的方式:Lock。
其实synchronized实现同步是有一定的缺陷的,所以我先总结一下synchronized的缺陷
首先回顾一下synchronized的知识:如果一个方法或者是代码块被synchronized修饰了,如果这个时候有线程获取的对应的锁执行该代码块或者方法时。其他的线程只能一直等待锁被释放。就是只能等代码块被执行完或者是那个线程执行出现了异常,JVM让它释放锁。
假设这个线程因为调用sleep方法或者IO操作被阻塞住了,因为锁还没有被释放,所以其他的线程只能一直等待,这其实是很影响程序执效率的一种情况。那么,必须要有一种机制可以避免这种让其他线程无限期等待下去的情况。JDK1.5之后引入的Lock锁就可以实现我们的这种需求。
另外,synchronized是没有办法知道线程是否成功获取到锁的,但是Lock锁可以。也就是说Lock锁的功能是比synchronized多的。除此之外,synchronized是Java的关键字,是内置特性。但是Lock是类,同步访问是通过这个类来实现的。而且,synchronized是不需要用户去手动释放锁的,当synchronized方法或者代码块执行完毕之后,锁会自动被释放。但是Lock锁必须用户自己去释放,如果没有释放则可能会出现死锁的现象。
Java.ntil.concurrent.locks包
Lock接口
首先看一下Lock的源码:
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
newCondition()方法和线程协作有关,现在先不做解释。
lock(),tryLock(),tryLock(long time,TimeUnit unit),lockInterruptibly()这四种方法是用来获取锁的。只有unLock方法是用来释放锁的。
由于使用Lock锁时必须手动释放锁,但是在发生了异常时,不会自动释放锁。所以使用Lock锁时应该放在try...catch块里进行,为了保证锁一定被释放,释放锁的操作应该放在finally块里执行。如果使用lock()方法上锁的话,举一个例子。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class MyThread implements Runnable{ private Lock aLock = new ReentrantLock(); @Override public void run() { aLock.lock();//上锁 try{ System.out.println("测试"); }catch (Exception e){ e.printStackTrace(); }finally { aLock.unlock();//解锁 } } }
tryLock():该方法有返回值,如果获取成功返回true否则返回false,就是说如果拿不到锁也不会继续等待。这一点比使用synchronized方便许多。
tryLock(long time,TimeUnit unt)方法和tryLock()方法类似,使用这个方法如果拿不到锁,还是会等待一段时间的,在这个时间内如果拿不到锁,就返回false,如果在这段时间内拿到了锁,就会返回true。
在使用tyrLock获取锁的时候一般是这么写的
@Override public void run() { if(aLock.tryLock()) {//尝试获取锁 try { System.out.println("测试"); } catch (Exception e) { e.printStackTrace(); } finally { aLock.unlock(); } }else { //拿不到锁的话就干别的事 } }
lockInterruptibly():在通过这个方法来获取锁时,如果线程正在等待获取锁,那么这个线程可以相应中断,就是我们随时可以中断该线程的等待状态。假设有两个线程同时通过该方法想获取某个锁时,假设其中一个线程拿到了锁,则我们可以随时对另一个线程调用interrupt()方法终止它的等待过程。
lockInterruptibly会抛出InterruptedException异常,所以我们在使用该方法时应该这么写
public void methpod()throws InterruptedException{ aLock.lockInterruptibly(); try{ //...... }finally { aLock.unlock(); } }
在这里需要注意的是:如果线程已经拿到了锁,就不能通过interrupt()方法中断。因为它只能中断阻塞过程中的线程。
使用此方法获取锁,和synchronized获取锁的不同在于:synchronized是不能把在等待状态下的线程中断的,但是这个方法可以。
先初步总结到这里,后续会有补充。