java-12-Thread续

来源:互联网 发布:js小球碰撞原理 编辑:程序博客网 时间:2024/06/10 06:46

线程之间的同步互斥关系
1. 同步关系:

它是指为了完成某种任务而建立两个或多个线程,这些线程要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系。例如卖烧鸡,生产者必须把烧鸡生产出来,然后消费者才可以去买。这就产生了次序问题。

2 互斥关系:

当一个线程进入临界区使用临界资源时,另一个线程就必须等待,当占用临界资源的线程退出临界区后,另一个线程才允许去访问此临界资源。例如一个车厢内的厕所,就是临界资源,当一个人正在使用时,其他人是不能进去的,必须等待前一个人从厕所中出来,然后他才可以进去。临界资源就是某一时刻,只允许一个线程进行访问的资源。

3 在java中有等待唤醒机制去解决这些同步互斥的问题。
在Object类中提供了对象监视器方法

wait:让线程处于冻结状态,被wait的线程会被存储到线程池中。notify:唤醒线程池中的一个线程(任意)。notifyAll:唤醒线程池中所有的线程。这些方法必须定义在同步中,因为这些方法是用于操作线程状态的方法,

4 一个生产者与一个消费者的例子

class Resource //临界缓冲区区,同一时刻内只能有一个线程访问。{    private String commodity;//商品的名字    private int num;//商品的编号    private boolean flag = false;//代表资源是否存在,没有的话就false,有的话就true    public synchronized void setCommodity(String commodity)//加同步锁防止线程间对共享数据进行操作引发的安全问题    {        if(this.flag)//首先判断缓冲出中是否存在商品,        {            try {                this.wait();//如果有商品,生产者线程冻结            } catch (InterruptedException e) {                e.printStackTrace();            }        }        this.commodity = commodity+num;//生产者制作商品,放入缓冲区        System.out.println(Thread.currentThread().getName()+"生产者制作"+this.commodity);        num++;        this.flag = true;//修改缓冲区是否有商品的标志。        this.notify();//唤醒消费者线程,让消费者购买    }    public synchronized void out()    {        if(!this.flag)//判断缓冲区中是否有商品        {            try {                this.wait();//没有商品的话就冻结线程,等待生产者生产            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName()+"消费者购买....."+this.commodity);        this.flag = false;//购买完商品后,修改缓冲区是否有商品的标志        this.notify();//唤醒生产者线程,让生产者生产    }}class product implements Runnable{    private Resource r;//接收缓冲区资源对象,因为缓冲区内提供了生产和销售的方法只需要拿来调用即可    product(Resource r)    {        this.r = r;    }    public void run()    {        while(true)        {            r.setCommodity("烧鸡");        }    }}class consumer implements Runnable{    private Resource r;//接收缓冲区资源对象,用来访问缓冲区的一些资源。    consumer(Resource r)    {        this.r = r;    }    public void run()    {        while(true)        {            r.out();        }    }}public class ProducterConsumer {    public static void main(String[] args)    {        //创建资源对象        Resource r = new Resource();        //创建任务对象        //生产者和消费者对同一个临界资源进行访问        product p = new product(r);        consumer c = new consumer(r);        //创建线程任务        Thread t1 = new Thread(p);        Thread t2 = new Thread(c);        t1.start();        t2.start();    }

运行结果
这里写图片描述
4.2如果两个消费者和两个生产者对一个临界资源区进行访问的话,就会出现以下错误

public class ProducterConsumer {    public static void main(String[] args)    {        //创建资源对象        Resource r = new Resource();        //创建任务对象        product p = new product(r);        product p2 = new product(r);        consumer c = new consumer(r);        consumer c2 = new consumer(r);        //创建线程任务        Thread t1 = new Thread(p);        Thread t2 = new Thread(p2);        Thread t3 = new Thread(c);        Thread t4 = new Thread(c2);        t1.start();        t2.start();        t3.start();        t4.start();    }}

这里写图片描述
出现这个问题的原因是,消费者线程2购买完以后,把缓冲区标志修改,
然后唤醒线程池中的其他线程(唤醒后,如果消费者线程2时间片没有结束,唤醒的线程在就绪队列,等待消费者线程2释放CPU),
如果此时唤醒的是消费者线程3,因为它之前被wait,被wait之前它已经判断过了标志,
因为用的是if判断所以就直接往下执行,就会出现上图的错误(生产一个东西,消费多次)。
解决这个问题只需把if判断换成while判断,让唤醒后的线程,重新判断标志。

//生产者的  while(this.flag)//首先判断缓冲出中是否存在商品,        {            try {                this.wait();//如果有商品,生产者线程冻结            } catch (InterruptedException e) {                e.printStackTrace();            }        }  //消费者的  while(!this.flag)//判断缓冲区中是否有商品        {            try {                this.wait();//没有商品的话就冻结线程,等待生产者生产            } catch (InterruptedException e) {                e.printStackTrace();            }        }

4.3但是这样做又会出现新的问题,如下图
这里写图片描述
出现了死锁,这种情况的出现是因为,唤醒是随机的,可以唤醒的是本方消费者的线程,又可以唤醒对方的线程,
如果唤醒的是对方的线程,就会和谐的运行下去,但是唤醒的是本方的线程就会出现锁,(假设此时只有一个生产者线程0在运行状态,其他线程都处于wait冻结状态)
生产者线程0走完自己的代码后,如果唤醒了生产者线程1,因为此时标志是true所以生产者线程1就会再次被wait冻结,此时4个线程都是冻结状态,程序就无法进行了,进入了死锁
解决方案:线程做唤醒动作时要唤醒对方线程。可以用notifyAll

this.notifyAll();虽然说这样可以解决死锁的问题但是造成了效率的降低,因为每次都要唤醒线程池全部的线程。

4.4 jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
(1)Lock接口:

替代了同步代码块和同步函数,将同步的隐式锁操作变成了显示锁操作。同时更为灵活,可以一个锁上绑定多个监视器。
lock():获取锁。unlock():释放锁,通常需要定义在finally代码块中。例子:  Lock l = ...;     l.lock();     try {         // access the resource protected by this lock     } finally {         l.unlock();

(2)Condition接口:

替代了Object中的wait,notify,notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。提供了以下方法await();signal();signalAll();
例子  Lock lock = new ReentrantLock();    Condition notFull  = lock.newCondition();     Condition notEmpty = lock.newCondition();

用这些方法可以解决效率的问题。
修改后的代码如下

class Resource //临界缓冲区区,同一时刻内只能有一个线程访问。{    private String commodity;//商品的名字    private int num;//商品的编号    private boolean flag = false;//代表资源是否存在,没有的话就false,有的话就true    Lock lock = new ReentrantLock();//定义一个锁对象。    Condition product_con = lock.newCondition();//在锁上绑定生产者监视器    Condition consumer_con = lock.newCondition();//在锁上绑定消费者监视器    public void setCommodity(String commodity)//加同步锁防止线程间对共享数据进行操作引发的安全问题    {        lock.lock();        try {            while (this.flag)//首先判断缓冲出中是否存在商品,            {                try {                    product_con.await();//如果有商品,生产者线程冻结                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            this.commodity = commodity + num;//生产者制作商品,放入缓冲区            System.out.println(Thread.currentThread().getName() + "生产者制作" + this.commodity);            num++;            this.flag = true;//修改缓冲区是否有商品的标志。            consumer_con.signal();//唤醒消费者线程,让消费者购买        }finally {            lock.unlock();        }    }    public  void out()    {        lock.lock();        try {            while(!this.flag)//判断缓冲区中是否有商品            {                try {                    consumer_con.await();//没有商品的话就冻结线程,等待生产者生产                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            System.out.println(Thread.currentThread().getName()+"消费者购买....."+this.commodity);            this.flag = false;//购买完商品后,修改缓冲区是否有商品的标志        }        finally {            product_con.signal();//唤醒生产者线程,让生产者生产        }    }}

5.wait和sleep的区别?

1.wait可以指定时间也可以不指定时间。sleep必须指定时间。2.在同步中时对于cpu的执行权和锁的处理不同。wait:释放执行权,释放锁。sleep:释放执行权,不释放锁。

6.停止线程

1.stop方法。2.run方法结束。怎么控制线程的任务结束呢?任务中都会有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记来完成。但是如果线程处于了冻结状态,无法读取标记。如何结束呢?可以使用interrupt方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。但是强制动作会发生InterruptException,记得要处理。设置守护线程(后台线程)setDaemon(true);虚拟机中的前台线程全部结束时,退出虚拟机。
原创粉丝点击