本篇我们将讨论以下知识点:
1.线程同步问题的产生
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午2:55:42
- * @decrition 模拟卖票线程
- */
- public class Ticket implements Runnable
- {
- //当前拥有的票数
- private int num = 100;
- public void run()
- {
- while(true)
- {
- if(num>0)
- {
- try{Thread.sleep(10);}catch (InterruptedException e){}
- //输出卖票信息
- System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
- }
- }
- }
- }
上面是卖票线程类,下来再来看看执行类:
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午2:54:18
- * @decrition 模拟卖票系统,该案例只考虑单方面卖票,其他情况暂时不考虑
- */
- public class TicketDemo {
- public static void main(String[] args)
- {
- Ticket t = new Ticket();//创建一个线程任务对象。
- //创建4个线程同时卖票
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- Thread t3 = new Thread(t);
- Thread t4 = new Thread(t);
- //启动线程
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
运行程序结果如下(仅截取部分数据):
SE5.0之后并发包中新增了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。接下来我们就来介绍Lock接口的主要API方便我们学习
方法 | 相关描述内容 |
void lock() | 获取锁,调用该方法当前线程会获取锁,当获取锁后。从该方法返回 |
void lockInterruptibly() throws InterruptedException |
可中断获取锁和lock()方法不同的是该方法会响应中断,即在获取锁 中可以中断当前线程。例如某个线程在等待一个锁的控制权的这段时 间需要中断。 |
boolean tryLock() | 尝试非阻塞获取锁,调用该方法后立即返回,如果能够获取锁则返回 true,否则返回false。 |
boolean tryLock(long time,TimeUnit unit) throws InterruptedException |
超时获取锁,当前线程在以下3种情况返回: 1.当前线程在超时时间内获取了锁 2.当前线程在超时时间被中断 3.当前线程超时时间结束,返回false |
void unlock() | 释放锁 |
Condition newCondition() | 条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有 获取了锁,才能调用该组件的await()方法,而调用后,当前线程将缩放 锁。 |
- ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
- ReentrantLock lock = new ReentrantLock(true); //公平锁
- lock.lock(); //如果被其它资源锁定,会在此等待锁释放,达到暂停的效果
- try {
- //操作
- } finally {
- lock.unlock(); //释放锁
- }
2.防止重复执行代码:
- ReentrantLock lock = new ReentrantLock();
- if (lock.tryLock()) { //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
- try {
- //操作
- } finally {
- lock.unlock();
- }
- }
3.尝试等待执行的代码:
- ReentrantLock lock = new ReentrantLock(true); //公平锁
- try {
- if (lock.tryLock(5, TimeUnit.SECONDS)) {
- //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
- try {
- //操作
- } finally {
- lock.unlock();
- }
- }
- } catch (InterruptedException e) {
- e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
- }
这里有点需要特别注意的,把解锁操作放在finally代码块内这个十分重要。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。好了,ReentrantLock我们就简单介绍到这里,接下来我们通过ReentrantLock来解决前面卖票线程的线程同步(安全)问题,代码如下:
- package com.zejian.test;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author zejian
- * @time 2016年3月12日 下午2:55:42
- * @decrition 模拟卖票线程
- */
- public class Ticket implements Runnable
- {
- //创建锁对象
- private Lock ticketLock = new ReentrantLock();
- //当前拥有的票数
- private int num = 100;
- public void run()
- {
- while(true)
- {
- ticketLock.lock();//获取锁
- if(num>0)
- {
- try{
- Thread.sleep(10);
- //输出卖票信息
- System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
- }catch (InterruptedException e){
- Thread.currentThread().interrupt();//出现异常就中断
- }finally{
- ticketLock.unlock();//释放锁
- }
- }
- }
- }
- }
- public synchronized void method{
- //method body
- }
等价于
- private Lock ticketLock = new ReentrantLock();
- public void method{
- ticketLock.lock();
- try{
- //.......
- }finally{
- ticketLock.unlock();
- }
- }
从这里可以看出使用synchronized关键字来编写代码要简洁得多了。当然,要理解这一代码,我们必须知道每个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入synchronized方法的线程,由条件来管那些调用wait的线程(wait()/notifyAll/notify())。同时我们必须明白一旦有一个线程通过synchronied方法获取到内部锁,该类的所有synchronied方法或者代码块都无法被其他线程访问直到当前线程释放了内部锁。刚才上面说的是同步方法,synchronized还有一种同步代码块的实现方式:
- Object obj = new Object();
- synchronized(obj){
- //需要同步的代码
- }
其中obj是对象锁,可以是任意对象。那么我们就通过其中的一个方法来解决售票系统的线程同步问题:
- class Ticket implements Runnable
- {
- private int num = 100;
- Object obj = new Object();
- public void run()
- {
- while(true)
- {
- synchronized(obj)
- {
- if(num>0)
- {
- try{Thread.sleep(10);}catch (InterruptedException e){}
- System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
- }
- }
- }
- }
- }
- //创建条件对象
- Condition conditionObj=ticketLock.newCondition();
方法 | 函数方法对应的描述 |
void await() | 将该线程放到条件等待池中(对应wait()方法) |
void signalAll() | 解除该条件等待池中所有线程的阻塞状态(对应notifyAll()方法) |
void signal() | 从该条件的等待池中随机地选择一个线程,解除其阻塞状态(对应notify()方法) |
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:44:25
- * @decrition 烤鸭资源
- */
- public class KaoYaResource {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- /**
- * 生产烤鸭
- */
- public synchronized void product(String name){
- if(flag){
- //此时有烤鸭,等待
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace()
- ;
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- notifyAll();//通知消费线程可以消费了
- }
- /**
- * 消费烤鸭
- */
- public synchronized void consume(){
- if(!flag){//如果没有烤鸭就等待
- try{this.wait();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- notifyAll();//通知生产者生产烤鸭
- }
- }
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:29:12
- * @decrition 单生产者单消费者模式
- */
- public class Single_Producer_Consumer {
- public static void main(String[] args)
- {
- KaoYaResource r = new KaoYaResource();
- Producer pro = new Producer(r);
- Consumer con = new Consumer(r);
- //生产者线程
- Thread t0 = new Thread(pro);
- //消费者线程
- Thread t2 = new Thread(con);
- //启动线程
- t0.start();
- t2.start();
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:22
- * @decrition 生产者线程
- */
- class Producer implements Runnable
- {
- private KaoYaResource r;
- Producer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.product("北京烤鸭");
- }
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:05
- * @decrition 消费者线程
- */
- class Consumer implements Runnable
- {
- private KaoYaResource r;
- Consumer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.consume();
- }
- }
- }
很显然的情况就是生产一只烤鸭然后就消费一只烤鸭。运行情况完全正常,嗯,这就是单生产者单消费者模式。上面使用的是synchronized关键字的方式实现的,那么接下来我们使用对象锁的方式实现:KaoYaResourceByLock.java
- package com.zejian.test;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author zejian
- * @time 2016年3月13日 上午9:55:35
- * @decrition 通过对象锁的方式来实现等待/通知机制
- */
- public class KaoyaResourceByLock {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- //创建一个锁对象
- private Lock resourceLock=new ReentrantLock();
- //创建条件对象
- private Condition condition= resourceLock.newCondition();
- /**
- * 生产烤鸭
- */
- public void product(String name){
- resourceLock.lock();//先获取锁
- try{
- if(flag){
- try {
- condition.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- condition.signalAll();//通知消费线程可以消费了
- }finally{
- resourceLock.unlock();
- }
- }
- /**
- * 消费烤鸭
- */
- public void consume(){
- resourceLock.lock();
- try{
- if(!flag){//如果没有烤鸭就等待
- try{condition.await();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- condition.signalAll();//通知生产者生产烤鸭
- }finally{
- resourceLock.unlock();
- }
- }
- }
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月13日 上午10:35:05
- * @decrition 多生产者多消费者模式
- */
- public class Mutil_Producer_Consumer {
- public static void main(String[] args)
- {
- KaoYaResource r = new KaoYaResource();
- Mutil_Producer pro = new Mutil_Producer(r);
- Mutil_Consumer con = new Mutil_Consumer(r);
- //生产者线程
- Thread t0 = new Thread(pro);
- Thread t1 = new Thread(pro);
- //消费者线程
- Thread t2 = new Thread(con);
- Thread t3 = new Thread(con);
- //启动线程
- t0.start();
- t1.start();
- t2.start();
- t3.start();
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:22
- * @decrition 生产者线程
- */
- class Mutil_Producer implements Runnable
- {
- private KaoYaResource r;
- Mutil_Producer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.product("北京烤鸭");
- }
- }
- }
- /**
- * @author zejian
- * @time 2016年3月12日 下午11:02:05
- * @decrition 消费者线程
- */
- class Mutil_Consumer implements Runnable
- {
- private KaoYaResource r;
- Mutil_Consumer(KaoYaResource r)
- {
- this.r = r;
- }
- public void run()
- {
- while(true)
- {
- r.consume();
- }
- }
- }
就多了两条线程,我们运行代码看看,结果如下:
不对呀,我们才生产一只烤鸭,怎么就被消费了3次啊,有的烤鸭生产了也没有被消费啊?难道共享数据源没有进行线程同步?我们再看看之前的KaoYaResource.java
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:44:25
- * @decrition 烤鸭资源
- */
- public class KaoYaResource {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- /**
- * 生产烤鸭
- */
- public synchronized void product(String name){
- if(flag){
- //此时有烤鸭,等待
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- notifyAll();//通知消费线程可以消费了
- }
- /**
- * 消费烤鸭
- */
- public synchronized void consume(){
- if(!flag){//如果没有烤鸭就等待
- try{this.wait();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- notifyAll();//通知生产者生产烤鸭
- }
- }
解决后的资源代码如下只将if改为了while:
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月12日 下午10:44:25
- * @decrition 烤鸭资源
- */
- public class KaoYaResource {
- private String name;
- private int count = 1;//烤鸭的初始数量
- private boolean flag = false;//判断是否有需要线程等待的标志
- /**
- * 生产烤鸭
- */
- public synchronized void product(String name){
- while(flag){
- //此时有烤鸭,等待
- try {
- this.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- this.name=name+count;//设置烤鸭的名称
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
- flag=true;//有烤鸭后改变标志
- notifyAll();//通知消费线程可以消费了
- }
- /**
- * 消费烤鸭
- */
- public synchronized void consume(){
- while(!flag){//如果没有烤鸭就等待
- try{this.wait();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1
- flag = false;
- notifyAll();//通知生产者生产烤鸭
- }
- }
运行代码,结果如下:
到此,多消费者多生产者模式也完成,不过上面用的是synchronied关键字实现的,而锁对象的解决方法也一样将之前单消费者单生产者的资源类中的if判断改为while判断即可代码就不贴了哈。不过下面我们将介绍一种更有效的锁对象解决方法,我们准备使用两组条件对象(Condition也称为监视器)来实现等待/通知机制,也就是说通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。有了前面的分析这里我们直接上代码:
- package com.zejian.test;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- /**
- * @author zejian
- * @time 2016年3月13日 下午12:03:27
- * @decrition 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
- */
- public class ResourceBy2Condition {
- private String name;
- private int count = 1;
- private boolean flag = false;
- //创建一个锁对象。
- Lock lock = new ReentrantLock();
- //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
- Condition producer_con = lock.newCondition();
- Condition consumer_con = lock.newCondition();
- /**
- * 生产
- * @param name
- */
- public void product(String name)
- {
- lock.lock();
- try
- {
- while(flag){
- try{producer_con.await();}catch(InterruptedException e){}
- }
- this.name = name + count;
- count++;
- System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name);
- flag = true;
- // notifyAll();
- // con.signalAll();
- consumer_con.signal();//直接唤醒消费线程
- }
- finally
- {
- lock.unlock();
- }
- }
- /**
- * 消费
- */
- public void consume()
- {
- lock.lock();
- try
- {
- while(!flag){
- try{consumer_con.await();}catch(InterruptedException e){}
- }
- System.out.println(Thread.currentThread().getName()+"...消费者.5.0......."+this.name);//消费烤鸭1
- flag = false;
- // notifyAll();
- // con.signalAll();
- producer_con.signal();//直接唤醒生产线程
- }
- finally
- {
- lock.unlock();
- }
- }
- }
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月13日 下午2:45:52
- * @decrition 死锁示例
- */
- public class DeadLockDemo {
- private static String A="A";
- private static String B="B";
- public static void main(String[] args) {
- DeadLockDemo deadLock=new DeadLockDemo();
- while(true){
- deadLock.deadLock();
- }
- }
- private void deadLock(){
- Thread t1=new Thread(new Runnable(){
- @SuppressWarnings("static-access")
- @Override
- public void run() {
- synchronized (A) {
- try {
- Thread.currentThread().sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- synchronized(B){
- System.out.println("1");
- }
- }
- });
- Thread t2 =new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (B) {
- synchronized (A) {
- System.out.println("2");
- }
- }
- }
- });
- //启动线程
- t1.start();
- t2.start();
- }
- }
millis)和join(long millis,int
nanos)两个具备超时特性的方法。这两个超时的方法表示,如果线程在给定的超时时间里没有终止,那么将会从该超时方法中返回。下面给出一个例子,创建10个线程,编号0~9,每个线程调用钱一个线程的join()方法,也就是线程0结束了,线程1才能从join()方法中返回,而0需要等待main线程结束。
- package com.zejian.test;
- /**
- * @author zejian
- * @time 2016年3月13日 下午4:10:03
- * @decrition join案例
- */
- public class JoinDemo {
- public static void main(String[] args) {
- Thread previous = Thread.currentThread();
- for(int i=0;i<10;i++){
- //每个线程拥有前一个线程的引用。需要等待前一个线程终止,才能从等待中返回
- Thread thread=new Thread(new Domino(previous),String.valueOf(i));
- thread.start();
- previous=thread;
- }
- System.out.println(Thread.currentThread().getName()+" 线程结束");
- }
- }
- class Domino implements Runnable{
- private Thread thread;
- public Domino(Thread thread){
- this.thread=thread;
- }
- @Override
- public void run() {
- try {
- thread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+" 线程结束");
- }
- }
好了,到此本篇结束。