java-12-Thread续

线程之间的同步互斥关系
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);虚拟机中的前台线程全部结束时,退出虚拟机。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值