线程同步Lock

来源:互联网 发布:手机淘宝怎样更新版本 编辑:程序博客网 时间:2024/06/04 19:05

转载请标明出处:
http://blog.csdn.net/hai_qing_xu_kong/article/details/70768583
本文出自:【顾林海的博客】


前言

除了上一篇文章的synchronized,Java还提供了同步代码块的另一种机制,这种机制基于Lock接口及其实现类。相比与synchronized来说更强大也更灵活。

关于线程相关知识可以查看:

《有关线程的相关知识(1)》
《有关线程的相关知识(2)》

关于synchronized的相关知识可以查看:

《线程同步synchronized》

Lock的使用

使用Lock能支持更灵活的同步代码块结构,也就是控制的获取和释放不出现在同一个块结构中。Lock接口提供了很多的功能,可以通过tryLock()方法来试图获取锁,如果锁已经被其他线程获取,它将返回false并继续往下执行代码。Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程。

下面编写一个使用Lock接口和它的实现类ReentrantLock类来创建一个临界区:

public class Queue {    // 声明一个锁对象,并且用ReentrantLock类初始化    private final Lock queueLock = new ReentrantLock();    /**     * 打印信息 通过调用lock()方法获取对锁对象的控制     */    public void printJob() {        queueLock.lock();        System.out.println("正在打印信息..."+new Date());        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            queueLock.unlock();        }    }}
public class Event implements Runnable{    private Queue queue;    public Event(Queue queue){        this.queue=queue;    }    @Override    public void run() {        queue.printJob();    }}
public class Client {    public static void main(String[] args) {        Queue queue=new Queue();        Event event=new Event(queue);        Thread[] thread=new Thread[10];        for(int i=0,length=thread.length;i<length;i++){            thread[i]=new Thread(event);        }        for(int i=0,length=thread.length;i<length;i++){            thread[i].start();        }    }}

上面Queue类中,定义来printJob()方法,通过lock()方法获取对锁对象的控制,最后通过unlock()方法释放对锁对象的控制。在主类中创建了10个线程,并启动这10个线程,运行程序,会在控制台上每隔1秒打印信息。

运行程序:正在打印信息...Tue Apr 25 22:02:31 CST 2017正在打印信息...Tue Apr 25 22:02:32 CST 2017正在打印信息...Tue Apr 25 22:02:33 CST 2017正在打印信息...Tue Apr 25 22:02:34 CST 2017正在打印信息...Tue Apr 25 22:02:35 CST 2017正在打印信息...Tue Apr 25 22:02:36 CST 2017正在打印信息...Tue Apr 25 22:02:37 CST 2017正在打印信息...Tue Apr 25 22:02:38 CST 2017正在打印信息...Tue Apr 25 22:02:39 CST 2017正在打印信息...Tue Apr 25 22:02:40 CST 2017

通过运行结果可以看出,对临界区的访问通过锁,来实现同一时间只有一个执行线程访问这个临界区,这里通过lock()方法获取对锁的控制,当某个线程访问这个方法时,如果没有其他线程获取对这个锁的控制,lock()方法将得到并且允许它立刻执行临界区。在线程离开临界区的时候,我们必须使用unlock()方法来释放它持有的锁,以让其他线程访问临界区,如果在离开临界区没有调用unlock()方法释放它持有的锁,其他线程将永远等待,从而导致死锁。


除了使用lock()方法来获取锁之外,Lock接口还提供了另一个方法来获取锁,即tryLock()方法。与lock()方法不同之处在于线程使用tryLock()不能够获取锁,tryLock()会立即返回,它不会将线程置入休眠,返回true表示线程获取了锁,false表示没有获取锁。


使用读写锁实现同步数据访问

ReadWriteLock接口和它唯一实现类ReentrantReadWriteLock,一个是读操作锁,另一个是写操作锁。使用读操作锁时可以允许多个线程同时访问,使用写操作锁时只允许一个线程进行。在一个线程执行写操作时,其他线程不能够执行读操作。

接下来通过ReadWriteLock接口来编写对某个对象的访问。

public class Product {    private int number1;    private int number2;    private ReadWriteLock lock;    public Product() {        number1 = 10;        number2 = 20;        lock = new ReentrantReadWriteLock();    }    /**     * 通过读锁获取对这个属性的访问     *      * @return number1     */    public int getNumber1() {        lock.readLock().lock();        int number = number1;        lock.readLock().unlock();        return number;    }    /**     * 通过读锁获取对这个属性的访问     *      * @return number2     */    public int getNumber2() {        lock.readLock().lock();        int number = number2;        lock.readLock().unlock();        return number;    }    /**     * 使用写锁来控制对这两个属性的访问     *      * @param number1     * @param number2     */    public void setNumber(int number1, int number2) {        lock.writeLock().lock();        System.out.println("写入number1:"+number1+"和number2:"+number2);        this.number1 = number1;        this.number2 = number2;        lock.writeLock().unlock();    }}
/** * 读取类,读取Product的number1和number2 *  * @author gulinhai * */public class Reader implements Runnable {    private Product product;    public Reader(Product product) {        this.product = product;    }    @Override    public void run() {        for (int i = 0; i < 10; i++) {            try {                Thread.sleep(3);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("number1=" + product.getNumber1() + ";number2=" + product.getNumber2());        }    }}
/** * 写入类,修改number1和number2 * @author gulinhai * */public class Writer implements Runnable {    private Product product;    public Writer(Product product) {        this.product = product;    }    @Override    public void run() {        for (int i = 0; i < 5; i++) {            product.setNumber(i, i * 2);            try {                Thread.sleep(2);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
public class Client {    public static void main(String[] args) {        Product product = new Product();        Reader[] reader = new Reader[5];        Thread[] readerThread = new Thread[5];        for (int i = 0, length = readerThread.length; i < length; i++) {            reader[i] = new Reader(product);            readerThread[i] = new Thread(reader[i]);        }        Writer writer = new Writer(product);        Thread writerThread = new Thread(writer);        for (int i = 0, length = readerThread.length; i < length; i++) {            readerThread[i].start();        }        writerThread.start();    }}
运行结果:写入number1:0number2:0写入number1:1number2:2number1=1;number2=2number1=1;number2=2number1=1;number2=2number1=1;number2=2number1=1;number2=2写入number1:2number2:4number1=2;number2=4number1=2;number2=4number1=2;number2=4number1=2;number2=4number1=2;number2=4写入number1:3number2:6number1=3;number2=6number1=3;number2=6number1=3;number2=6写入number1:4number2:8number1=4;number2=8number1=3;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8number1=4;number2=8

通过输出结果可以看出合理的利用ReentrantReadWriteLock类的两种锁,可以有效的避免数据不一致的问题。

ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数,它允许你控制这个两个类的行为,默认值是false,称为非公平模式,在非公平模式下,当有很多线程在等待锁时,锁将选择他们中的一个来访问临界区,这个选择是没有任何约束的。当布尔值为true时,称为公平模式,在公平模式下,当有很多线程在等待锁时,锁将选择它们中的一个来访问临界区,而且选择的是等待时机最长的。


锁中使用多条件

一个锁可能关联一个或者多个条件,这些条件通过Condition接口声明,目的允许线程获取锁并且查看等待的某一个条件是否满足,如果不满足就挂起直到某个线程唤醒它们,Condition接口提供了挂起线程和唤起线程的机制。

编程程序,利用Condition来解决生产者-消费者问题。

/** * 创建数据存储类EventStorage,并保存一个最大值maxSize和数据集合 LinkedList<Date>来保存存入的日期。 *  * @author gulinhai * */public class EventStorage {    private int maxSize;    private LinkedList<Date> storage;    private ReentrantLock lock;    private Condition condition1;    private Condition condition2;    public EventStorage() {        lock = new ReentrantLock();        condition1 = lock.newCondition();        condition2 = lock.newCondition();        this.maxSize = 10;        this.storage = new LinkedList<>();    }    public int size() {        return storage.size();    }    /**     * <pre>     * 获取锁,检查这个缓冲区是否满了,如果缓冲区满了,     * 就调用条件condition1的await()方法     * 等待空位出现,当其他线程调用条件condition1的     * signal()或者signalAll()方法时,这个线程     * 将被唤醒,在有空位后,线程会将数据保存到缓冲区中,     * 并调用条件condition2的signalAll()方法。     * </pre>     */    public void set() {        lock.lock();        try {            while (storage.size() == maxSize) {                condition1.await();            }            storage.add(new Date());            condition2.signalAll();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    /**     * <pre>     * 获取锁,检查这个缓冲区是否为空,如果缓冲区为空,     * 就调用条件condition2的await()方法     * 等待数据出现,当其他线程调用条件condition2的     * signal()或者signalAll()方法时,这个线程     * 将被唤醒,在有数据后,线程会从缓冲区中取出数据,     * 并调用条件condition1的signalAll()方法。     * </pre>     */    public void get() {        lock.lock();        try {            while (storage.size() == 0) {                condition2.await();            }            storage.poll();            condition1.signalAll();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }}
/** * 生产者 * @author gulinhai * */public class Producer implements Runnable{    private EventStorage storage;    public Producer(EventStorage storage){        this.storage=storage;    }    @Override    public void run() {        for(int i=0;i<100;i++){            storage.set();        }    }}
/** * 消费者 * @author gulinhai * */public class Consumber implements Runnable{    private EventStorage storage;    public Consumber(EventStorage storage){        this.storage=storage;    }    @Override    public void run() {        for(int i=0;i<100;i++){            storage.get();        }    }}
public class Client {    public static void main(String[] args) {        EventStorage storage=new EventStorage();        Producer producer=new Producer(storage);        Thread producerThread=new Thread(producer);        Consumber consumber=new Consumber(storage);        Thread consumberThread=new Thread(consumber);        producerThread.start();        consumberThread.start();        try {            producerThread.join();            consumberThread.join();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("缓冲区数据个数:"+storage.size());    }}
输出结果:缓冲区数据个数:0

与锁绑定的所有条件对象都是通过Lock接口声明的newCondition()方法创建的,在使用条件的时候,必须获取这个条件绑定的锁,所以带条件的代码必须在调用Lock对象的lock()方法和unlock()方法之间,当线程调用条件的await()方法时,它将自动释放这个条件绑定的锁,其他某个线程才可以获取这个锁并且执行相同的操作,或者执行这个锁保护的另一个临界区代码。

3 0
原创粉丝点击