Java多线程(二)

来源:互联网 发布:我们这里还有鱼 知乎 编辑:程序博客网 时间:2024/06/06 03:22
1.多线程的状态转换
    
    当线程处于就绪、运行、阻塞这三种的状态之一时,调用isAlive()方法将返回true;
    sleep()方法、yield()方法调用不会释放所占用的对象锁,wait()方法调用会释放锁,被唤醒后需要重新获取锁才能转到就绪状态;
    sleep()方法、join()方法和wait()方法阻塞时,若当前线程被中断,则抛出InterruptedException,并清除中断状态;
2.线程同步
    synchronized:该关键字可以修饰方法、同步控制块;调用方法或进入同步控制块都必须先加锁,也就是必须先获得监视器。该关键字修饰的类同步方法,监视器为类本身;对于修饰的实例方法,监视器为对象本身,即this;对于同步控制块,监视器为关键字括号中的对象;一个任务可以多次获得同一对象的锁,JVM负责跟踪对象被加锁的次数,计数为零时锁才完全释放。正在synchronized等待锁上的任务不能被中断;synchronized不能尝试获取锁且最终获取失败,或者尝试获取一段时间,然后放弃它,要实现这些,需要显示使用Lock对象
    Lock对象:Lock对象必须被显示的创建、锁定和释放;使用最多的是ReentrantLock锁,它允许你尝试获取锁但最终并未获取锁,对应方法为trylock(long timeout, TimeUnit unit),该方法尝试在一段时间内获取锁,若在这段时间内被中断,则抛出中断异常并清理中断状态,该类中的另外一个方法lockInterruptibly()也可以抛出中断异常;unlock()放到try-finally语句中,以确保unlock不会过早发生。典型的代码如下:
class X {   private final ReentrantLock lock = new ReentrantLock(); //定义锁对象   // ...   public void m() {      lock.lock();  // block until condition holds     try {       // ... method body     } finally {  //保证锁的释放       lock.unlock()     }   } }
      使用wait/notify,notifyAll
    使用Condition对象,await/signal/signalAll;一个锁可以创建多个对应的condition对象。典型代码如下:
class X {   private final ReentrantLock lock = new ReentrantLock(); //定义锁对象   private Condition condition = lock.newCondition(); //获取锁上对应的Condition对象   // ...   public void m() {      lock.lock();  // block until condition holds     try {       // ... method body       condition.await(); //等待     } finally {  //保证锁的释放       lock.unlock()     }   }  public void n() {      lock.lock();  // block until condition holds     try {       // ... method body       condition.signal(); //发出完成信号,signalAll()     } finally {  //保证锁的释放       lock.unlock()     }   } } }
   await()方法:造成当前线程在接到信号或被中断之前一直处于等待状态;与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,在被唤醒前将处于休眠状态;同Object.wait类似
    signalAll():唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。同Object.notifyAll类似。signal方法只唤醒其中一个线程。
3.死锁
    死锁的条件有以下四点:
  • 互斥条件;任务使用的资源中至少有一个是不能共享的;
  • 持有并等待;至少有一个任务它必须持有一个资源并正在等待获取一个当前被其他任务持有的资源;
  • 非抢占式占有;只要某一任务仍然占有并使用资源,其他任务就不能从其抢占资源,除非它主动释放;
  • 循环等待;一个任务等待其他任务持有的资源,后者又在等待另一个任务持有的资源,这样一直下去,构成一个循环;
    下面是一个死锁的例子:
import java.util.concurrent.TimeUnit;class A{public synchronized void foo(B b){try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}/** * 在这里调用b的last方法,此刻已经拥有对象a的锁 * 对象b的last方法是个同步方法,需要获取对象b的锁,才能执行 */System.out.println("尝试进入类B的last方法...");b.last(); }public synchronized void last(){System.out.println("在类A的last方法中");}}class B{public synchronized void bar( A a){try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}/** * 在这里调用a的last方法,此刻已经拥有对象b的锁 * 对象a的last方法是个同步方法,需要获取对象a的锁,才能执行 */System.out.println("尝试进入类A的last方法...");a.last();}public synchronized void last(){System.out.println("在类B的方法中");}}public class DeadLock implements Runnable{A a = new A();B b = new B();public static void main(String[] args) {DeadLock dl = new DeadLock();new Thread(dl).start();//启动副线程,首先获取对象b的锁,然后请求a的锁;dl.init();//主线程,首先获取了对象a的锁,然后请求对象b的锁,构成循环等待,产生死锁}@Overridepublic void run() {Thread.currentThread().setName("副线程");System.out.println("进入副线程之后...");b.bar(a);//副线程中调用对象b的bar()方法}public void init(){Thread.currentThread().setName("主线程");System.out.println("进入主线程之后...");a.foo(b);//主线程中调用对象a的foo()方法}}/**Output: * 进入主线程之后... *进入副线程之后... *尝试进入类A的last方法... *尝试进入类B的last方法... */