Lock 锁

简单使用 Lock 锁

Java 5 中引入了新的锁机制——java.util.concurrent.locks 中的显式的互斥锁:Lock 接口,它提供了比synchronized 更加广泛的锁定操作。Lock 接口有 3 个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。

采用 lock 加锁和释放锁的一般形式如下:

Lock lock = new ReentrantLock();//默认使用非公平锁,如果要使用公平锁,需要传入参数true  
........  
lock.lock();  
try {  
     //更新对象的状态  
    //捕获异常,必要时恢复到原来的不变约束  
   //如果有return语句,放在这里  
 finally {  
       lock.unlock();        //锁必须在finally块中释放  

 

可中断锁

ReetrantLock 有两种锁:忽略中断锁和响应中断锁。忽略中断锁与 synchronized 实现的互斥锁一样,不能响应中断,而响应中断锁可以响应中断。

获得响应中断锁的一般形式如下:

ReentrantLock lock = new ReentrantLock();  
...........  
lock.lockInterruptibly();//获取响应中断锁  
try {  
      //更新对象的状态  
      //捕获异常,必要时恢复到原来的不变约束  
      //如果有return语句,放在这里  
}finally{  
    lock.unlock();        //锁必须在finally块中释放  
} 

当用 synchronized 中断对互斥锁的等待时,并不起作用,该线程依然会一直等待,如下面的实例:

public class Buffer {

    private Object lock;

    public Buffer() {
        lock = this;
    }

    public void write() {
        synchronized (lock) {
            long startTime = System.currentTimeMillis();
            System.out.println("开始往这个buff写入数据…");
            for (; ; ) {// 模拟要处理很长时间
                if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {
                    break;
                }
            }
            System.out.println("终于写完了");
        }

    }

    public void read() {
        synchronized (lock) {
            System.out.println("从这个buff读数据");
        }
    }

    public static void main(String[] args) {
        Buffer buff = new Buffer();

        final Writer writer = new Writer(buff);
        final Reader reader = new Reader(buff);

        writer.start();
        reader.start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                long start = System.currentTimeMillis();
                for (; ; ) {                    //等5秒钟去中断读
                    if (System.currentTimeMillis() - start > 5000) {
                        System.out.println("不等了,尝试中断");
                        reader.interrupt();  //尝试中断读线程
                        break;
                    }
                }

            }
        }).start();
        // 我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁,
        // 就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它,
        // 它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断,
        // 让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。
    }
}

class Writer extends Thread {

    private Buffer buff;

    public Writer(Buffer buff) {
        this.buff = buff;
    }

    @Override
    public void run() {
        buff.write();
    }
}

class Reader extends Thread {

    private Buffer buff;

    public Reader(Buffer buff) {
        this.buff = buff;
    }

    @Override
    public void run() {
        buff.read();//这里估计会一直阻塞
        System.out.println("读结束");
    }
}

将上面代码中 synchronized 的互斥锁改为 ReentrantLock 的响应中断锁,即改为如下代码:

public class BufferInterruptibly {

    private ReentrantLock lock = new ReentrantLock();

    public void write() {
        lock.lock();
        try {
            long startTime = System.currentTimeMillis();
            System.out.println("开始往这个buff写入数据…");
            for (; ; ) {// 模拟要处理很长时间
                if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {
                    break;
                }
            }
            System.out.println("终于写完了");
        } finally {
            lock.unlock();
        }
    }

    public void read() throws InterruptedException {
        lock.lockInterruptibly();// 注意这里,可以响应中断
        try {
            System.out.println("从这个buff读数据");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String args[]) {
        BufferInterruptibly buff = new BufferInterruptibly();

        final Writer2 writer = new Writer2(buff);
        final Reader2 reader = new Reader2(buff);

        writer.start();
        reader.start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                long start = System.currentTimeMillis();
                for (; ; ) {
                    if (System.currentTimeMillis() - start > 5000) {
                        System.out.println("不等了,尝试中断");
                        reader.interrupt();  //此处中断读操作
                        break;
                    }
                }
            }
        }).start();

    }
}

class Reader2 extends Thread {

    private BufferInterruptibly buff;

    public Reader2(BufferInterruptibly buff) {
        this.buff = buff;
    }

    @Override
    public void run() {
        try {
            buff.read();//可以收到中断的异常,从而有效退出
        } catch (InterruptedException e) {
            System.out.println("我不读了");
        }
        System.out.println("读结束");
    }
}

class Writer2 extends Thread {

    private BufferInterruptibly buff;

    public Writer2(BufferInterruptibly buff) {
        this.buff = buff;
    }

    @Override
    public void run() {
        buff.write();
    }

}

条件变量实现线程间协作

下面将生产者——消费者模型一文中的代码改为用条件变量实现,如下:

class Info { // 定义信息类
    private String name = "name";//定义name属性,为了与下面set的name属性区别开
    private String content = "content";// 定义content属性,为了与下面set的content属性区别开
    private boolean flag = true;   // 设置标志位,初始时先生产
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition(); //产生一个Condition对象

    public void set(String name, String content) {
        lock.lock();
        try {
            while (!flag) {
                condition.await();
            }
            this.setName(name);    // 设置名称
            Thread.sleep(300);
            this.setContent(content);  // 设置内容
            flag = false; // 改变标志位,表示可以取走
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void get() {
        lock.lock();
        try {
            while (flag) {
                condition.await();
            }
            Thread.sleep(300);
            System.out.println(this.getName() +
                    " --> " + this.getContent());
            flag = true;  // 改变标志位,表示可以生产
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getName() {
        return this.name;
    }

    public String getContent() {
        return this.content;
    }
}

class Producer implements Runnable { // 通过Runnable实现多线程
    private Info info = null;      // 保存Info引用

    public Producer(Info info) {
        this.info = info;
    }

    public void run() {
        boolean flag = true;   // 定义标记位
        for (int i = 0; i < 10; i++) {
            if (flag) {
                this.info.set("姓名--1", "内容--1");    // 设置名称
                flag = false;
            } else {
                this.info.set("姓名--2", "内容--2");    // 设置名称
                flag = true;
            }
        }
    }
}

class Consumer implements Runnable {
    private Info info = null;

    public Consumer(Info info) {
        this.info = info;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            this.info.get();
        }
    }
}

class ThreadCaseDemo {
    public static void main(String args[]) {
        Info info = new Info(); // 实例化Info对象
        Producer pro = new Producer(info); // 生产者
        Consumer con = new Consumer(info); // 消费者
        new Thread(pro).start();
        //启动了生产者线程后,再启动消费者线程
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(con).start();
    }
}

从以上并不能看出用条件变量的 await()、signal()、signalAll()方法比用 Object 对象的 wait()、notify()、notifyAll()方法实现线程间协作有多少优点,但它在处理更复杂的多线程问题时,会有明显的优势。所以,Lock 和 Condition 对象只有在更加困难的多线程问题中才是必须的。

读写锁

另外,synchronized 获取的互斥锁不仅互斥读写操作、写写操作,还互斥读读操作,而读读操作时不会带来数据竞争的,因此对对读读操作也互斥的话,会降低性能。Java 5 中提供了读写锁,它将读锁和写锁分离,使得读读操作不互斥,获取读锁和写锁的一般形式如下:

ReadWriteLock rwl = new ReentrantReadWriteLock();      
rwl.writeLock().lock()  //获取写锁  
rwl.readLock().lock()  //获取读锁  

用读锁来锁定读操作,用写锁来锁定写操作,这样写操作和写操作之间会互斥,读操作和写操作之间会互斥,但读操作和读操作就不会互斥。

《Java 并发编程实践》一书给出了使用 ReentrantLock 的最佳时机:

当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用 synchronized。

参考来源:jike.并发新特性—Lock 锁与条件变量

猜你喜欢

转载自www.cnblogs.com/ooo0/p/12456657.html