大飞老师带你再看Java线程(七)

本篇不会对Reentrantlock类做深入探讨, 仅仅演示Reentrantlock 类如何实现同步操作, 后续的章节再深入.

Lock介绍

jdk5的 java concurrent包中新增了Lock接口,用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,不同的是,lock需要自己手动获取锁与释放锁,失去了synchronized那样隐式获取释放锁的便捷性,但是却拥有了更加灵活的可操作性.本篇的主角就是Lock接口重要实现类Reentrantlock.

重要API
    //如果锁是空闲状态,则获取锁,如果锁已经被其他线程获取了,则等待
    void lock();
     //尝试获取锁, 如果锁空闲状态,则获取锁, 返回true, 反之,返回false, 注意获取锁失败,线程并不会阻塞,会继续执行.
    boolean tryLock();
    //跟tryLock一样,只是加上等待时间
    boolean tryLock(long time, TimeUnit unit)
    //获取可中断的锁,如果线程等待很久,还是无法获取到锁,可以中断等待
    void lockInterruptibly();
    //执行此方法时, 当前线程将释放锁. 注意锁只能由持有者释放,否则可以导致异常
    void unlock();
操作模板
//创建锁对象:一般都是成员变量
ReentrantLock lock = new ReentrantLock();
-----------------------------------------------------------------
try {
      lock.lock();  //获取锁,如果没有则等待      
      .......//相关的同步操作
}finally {
     //必须在finally里面释放锁, 同步操作中如果抛异常,不会自动释放锁
     lock.unlock();      
}
代码演示

上一篇售票的代码改造:

public class Ticket implements Runnable {
    //创建锁对象
    private ReentrantLock lock = new ReentrantLock();
    private int count = 10;
    public void run() {
        while (count > 0) {
            try {
                lock.lock(); //获取锁
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出" + count + "号票");
                    count--;
                }
            }finally {
                //释放锁
                lock.unlock();
            }

            //放大效果..
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
Reentrantlock 与 synchronized 区别

1>Reentrantlock发生异常时,不会自动的是否锁, 所以unlock操作必须放置在finally中;synchronized 发送异常后自动释放锁
2>Reentrantlock 可以调用tryLock 方法感知是否获取到锁;synchronized 做不到
3>当线程迟迟无法获取到锁时, Reentrantlock 可以通过lockInterruptibly来中断等待, synchronized 无法中断, 会一直等待下去.

synchronized 中断实验:

public class Resource {
    public synchronized  void method1() {
        System.out.println(Thread.currentThread().getName() + "线程进来...method1");
        try {
            Thread.sleep(10000);  //模拟长时间占有锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程出去...method1");
    }
    public synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + "线程进来...method2");
        System.out.println(Thread.currentThread().getName() + "线程出去...method2");
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {
        final Resource resource = new Resource();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                resource.method1();
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                resource.method2();
            }
        }, "t2");
        t1.start();
        Thread.sleep(1000);
        t2.start();
        Thread.sleep(1000);
        t2.interrupt();
        System.out.println("........");
    }
}

结果:
t1线程进来…method1
……..
t1线程出去…method1
t2线程进来…method2
t2线程出去…method2

代码思路:
Resource 类定义method1 method2方法, 都使用synchronized 修饰,App类定义2个线程, 共同持有Resource对象, t1先访问resource中的method1方法, 在里面等待10000毫秒, 长时间占有锁, 1000毫秒之后, t2线程访问resource中的method2方法,因无法获取锁则等待, 1000毫秒后, t2发起中断通知interrupt. 此时t2方法无法反应, 一直等, 直到 t1执行完method1方法后, 获取到锁后再执行method2. 这也就证明了synchronized无法中断等待.

Reentrantlock 实验:

public class Resource {

    private ReentrantLock lock = new ReentrantLock();
    public  void method1(){
        try {
            lock.lockInterruptibly();  //获取可中断锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() +"线程进来...method1");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() +"线程出去...method1");
        lock.unlock();
    }
    public  void method2(){
        try {
            lock.lockInterruptibly();//获取可中断锁
            System.out.println(Thread.currentThread().getName() +"线程进来...method2");
            System.out.println(Thread.currentThread().getName() +"线程出去...method2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
    }
}
测试类App是一样的

结果:
t1线程进来…method1
……..
java.lang.InterruptedException
t1线程出去…method1

上面结果看到, Reentrantlock 使用lockInterruptibly 获取锁时, 如果线程发起interrupt中断通知时, t2会自动放弃等待锁, 抛出一个InterruptedException异常.

4>Reentrantlock 是从代码层面实现同步的,是轻量级别的锁, 而synchronized实现依赖JVM底层实现, 是重量级别锁, 等同情况下,Reentrantlock 性能更优. 当时jdk7之后synchronized做了一定的优化,现在孰优孰劣难说.

5>ReentrantLock 类增加了一些高级的功能, 比如公平锁,非公平锁,分支通知,嗅探锁定等功能, 这里我们就先不探讨.


猜你喜欢

转载自blog.csdn.net/wolfcode_cn/article/details/80900971