Java多线程编程4--Lock的使用--重入锁(ReentrantLock)、Condition

    在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加的灵活。

1、使用ReentrantLock实现同步

    既然ReentrantLock类在功能上相比synchronized更多,那么就以一个初步的程序示例

public class MyService {
    private Lock lock = new ReentrantLock();

    public void testMethod() {
        lock.lock();  //获取锁,线程就持有了“对象监视器”
        for (int i=0; i<3; i++) {
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + (" " + (i+1)));
        }
        lock.unlock();  //释放锁
    }
}
public class MyThread extends Thread {
    private MyService myService;
    public MyThread(MyService myService) {
        this.myService = myService;
    }
    public void run() {
        myService.testMethod();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        MyThread a1 = new MyThread(service);
        MyThread a2 = new MyThread(service);

        a1.start();
        a2.start();
    }
}
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
    从运行的结果来看,当前线程打印完毕之后将锁进行释放,其他线程才可以继续打印。线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。

2、使用Condition实现等待/通知:错误用法与解决

    关键字synchronized与waits和notifyn/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。
    在使用notify()/notifyAll()方法进行通知时,被通知的线程却是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在Condition类中是默认提供的。
    而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
自定义线程
public class MyThread1 extends Thread {
    private MyService service;
    public MyThread1(MyService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.await();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        MyThread1 a1 = new MyThread1(service);
        a1.start();
    }
}
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await
     报错的异常信息是监视器出错,解决的办法是必须在condition.await()方法调用之前调用lock.lock()代码获得同步监视器。

正确使用Condition实现等待/通知

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await 时间为: " + System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            System.out.println("signal 时间为:" + System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}
public class MyThread1 extends Thread {
    private MyService service;
    public MyThread1(MyService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.await();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        MyThread1 a1 = new MyThread1(service);
        a1.start();

        Thread.sleep(1000);
        service.signal();
    }
}
await 时间为: 1462595312580
signal 时间为:1462595313580

    Object类中的wait()方法相当于Condition类中的await()方法。
    Object类中的wait(Iong timeout)方法相当于Condition类中的await(long time, TimeUnit unit)方法。
    Object类中的notify()方法相当于Condition类中的signal()方法。
    Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。

----------------------------------------------------------------------------------------------------

3、使用Condition实现通知线程

3.1、通知全部线程
     如果线程共用一个Condition,则signalAll()会唤醒所有的线程
public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();  //一个公共的

    public void awaitA() {
        try {
            lock.lock();
            System.out.println(" begin awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());

            condition.await();
            System.out.println("  end awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println(" begin awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());

            condition.await();
            System.out.println("  end awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll() {
        try {
            lock.lock();
            System.out.println("signal 时间为:" + System.currentTimeMillis());
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
public class MyThread1 extends Thread {
    private MyService service;
    public MyThread1(MyService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.awaitA();
    }
}
public class MyThread2 extends Thread{
    private MyService service;
    public MyThread2(MyService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.awaitB();
    }
}
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        MyThread1 a = new MyThread1(service);
        a.setName("AA");
        a.start();

        MyThread2 b = new MyThread2(service);
        b.setName("BB");
        b.start();

        Thread.sleep(2000);
        service.signalAll();
    }
}
 begin awaitA 时间为: 1462628140485  ThreadName=AA
 begin awaitA 时间为: 1462628140487  ThreadName=BB
signal 时间为:1462628142485
  end awaitA 时间为: 1462628142485  ThreadName=AA
  end awaitA 时间为: 1462628142485  ThreadName=BB
程序运行后,线程A和线程B都被唤醒了。
    如果想单独唤醒部分线程该怎么处理呢?这时就有必要使用多个Condition对象了,也就是Condition对象可以唤醒部分指定线程,有助于提升程序运行的效率。可以先对线程进行分组,然后再唤醒指定组中的线程。
3.2、唤醒单个线程

 

public class MyService {
    private Lock lock = new ReentrantLock();

    //使用多个Condition可以单独唤醒部分线程!!!
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println(" begin awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());

            conditionA.await();
            System.out.println("  end awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println(" begin awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());

            conditionB.await();
            System.out.println("  end awaitA 时间为: " + System.currentTimeMillis()
                    + "  ThreadName=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("signal 时间为:" + System.currentTimeMillis());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println("signal 时间为:" + System.currentTimeMillis());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
两个自定义的线程同上
public class Run {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        MyThread1 a = new MyThread1(service);
        a.setName("AA");
        a.start();

        MyThread2 b = new MyThread2(service);
        b.setName("BB");
        b.start();

        Thread.sleep(2000);
        service.signalAll_A(); //这里只唤醒线程A
    }
}
 begin awaitA 时间为: 1462629180458  ThreadName=BB
 begin awaitA 时间为: 1462629180469  ThreadName=AA
signal 时间为:1462629182457
  end awaitA 时间为: 1462629182457  ThreadName=AA
程序运行后,只有线程A被唤醒了,线程B没有唤醒

 通过此实验可以得知,使用ReentrantLock对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式。

你可能感兴趣的:(java,线程,Lock,ReentrantLock,重入锁)