线程同步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:0和number2:0写入number1:1和number2:2number1=1;number2=2number1=1;number2=2number1=1;number2=2number1=1;number2=2number1=1;number2=2写入number1:2和number2:4number1=2;number2=4number1=2;number2=4number1=2;number2=4number1=2;number2=4number1=2;number2=4写入number1:3和number2:6number1=3;number2=6number1=3;number2=6number1=3;number2=6写入number1:4和number2: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()方法时,它将自动释放这个条件绑定的锁,其他某个线程才可以获取这个锁并且执行相同的操作,或者执行这个锁保护的另一个临界区代码。
- C#lock线程同步
- 关于线程同步(Lock)
- 同步,线程通信,lock
- 同步线程1(lock)
- 线程同步Lock
- 线程同步---同步锁(Lock)
- 线程同步lock and monitor
- 【java】同步,线程通信,lock
- Lock实现线程的同步
- c#线程同步(2)----lock
- 线程同步—synchronized & Lock
- 线程同步:synchronized和Lock
- java线程同步 Lock同步锁
- 线程LOCK机制和synchronized线程同步
- 线程(七)--线程同步-同步锁(Lock)
- 线程同步作业(一):Lock,monitor
- c#线程同步——lock
- 线程同步之Lock对象的使用
- 14.[个人]C++线程入门到进阶(14)----双线程读写队列数据
- Android图片压缩(质量压缩和尺寸压缩)&Bitmap转成字符串上传
- List接口实现类(3):Vector
- 简单理解霍夫变换
- 杭电oj1213——How Many Tables(并查集)
- 线程同步Lock
- List接口实现类(4):Vector
- Java多线程中join方法的理解
- 15.[个人]C++线程入门到进阶(15)----线程函数:WaitForSingleObject
- MarkdownPad 2 Pro 注册码
- 【C语言】getchar单个字符依次输入(可对单个字符进行处理)
- Java线程安全的计数器
- HDU5540-Secrete Master Plan
- List总结