random 线程安全_多线程系列-(一)线程属性、生产者与消费者模型、死锁

一.线程的基本属性有哪些?

169e7ad1ea216d043776c7415377d9d3.png

二.讲讲实现多线程方式有哪些?

在java代码中实现多线程的方式常用的有两种

1.类A继承Thread类,重写Thread类的run方法,通过start()方法来启动

2.类B实现Runnable接口及其run方法。new一个Thread类,将B对象实例作为参数传进去,执行Thread类的start()方法

第二种方法之所以可以实现多线程,是因为在Thread类源码里边,在执行start方法的时候,会先判断有无实现runnable接口的,如果有则直接执行runnable的run方法。

30e659bbffdd59adbf8abf89d0306777.png

三.线程有哪些状态?

2ef1b0a23778b9955330f12cbca9539b.png

3.1.阻塞和等待的区别:

阻塞是被动的,但是等待是主动的。

四.如何终止一个线程?

4.1.interrupt中止线程

用法1:线程二在while/for等循环的时候,线程一发出中断信号,此时需要循环条件再进行判断

public class StopThreadWithSleep implements Runnable{
    private static int a=0;
    @Override
    public void run() {
        while(a<100000000&&!Thread.currentThread().isInterrupted()){
            if(a%100==0){
                System.out.println(a+"是100的倍数");
            }
            a++;
        }
    }
    public static void main(String[] args) {
        Thread thread1=new Thread(new StopThreadWithSleep());
        thread1.start();
        try{
            Thread.sleep(5);
            System.out.println("主线程休眠结束---------------------------------------------");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        thread1.interrupt();
    }
}

用法2:线程二在sleep/wait/join等阻塞的过程中,线程一发出中断信号,此时线程二将抛出异常(抛出异常后中断标志会重新复位,即在调用interrupt方法时,中断标志是true,在抛出异常后,中断标志为false)

public class StopThreadWithOutSleep implements Runnable{
    private static int a=0;

    @Override
    public void run() {
        try {
            Thread.sleep(5000);
        }catch (Exception e){
            e.printStackTrace();
        }
        //在捕获异常后,中断标志已经为false,此时需要重新将标志变为true
        Thread.currentThread().interrupt();
        while(a<100000000&&!Thread.currentThread().isInterrupted()){
            if(a%1000==0){
                System.out.println(a+"是1000的倍数");
            }
            a++;
        }
    }
    public static void main(String[] args) {
        Thread thread1=new Thread(new StopThreadWithOutSleep());
        thread1.start();
        try{
            Thread.sleep(2000);
            System.out.println("主线程休眠结束---------------------------------------------");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        thread1.interrupt();
    }
}

4.2.其他方法(设置Boolean类型的中止变量)中止线程

用此方法可以实现部分条件下的线程中止,比如类似于interrupt用法一的情况。但是不适用于interrupt用法二的情况,比如当sleep的时候,不能进行中断,当sleep结束的时候才能进行中断,不建议使用

4.3.中止线程的实际应用

1.当方法包含sleep等会使线程阻塞的方法时,应该将异常throw,方便处理。

public class StopThreadWithOutSleep implements Runnable{
    private static int a=0;

    @Override
    public void run() {

        while(true){
            try{
                printNum();
            }catch (InterruptedException e){
                System.out.println("已经出现异常");
                break;
            }
        }
    }
    //在类似情况下,当printNum在阻塞时被中断是不影响while(true)继续进行的,假如printNum是一个公共调用类
    //则调用者可能会出现接收不到日志的情况,因此最好将异常抛出,这样会强制让调用者进行处理
    private static void printNum() throws InterruptedException{
        while(a<100000000){
            if(a%1000==0){
                System.out.println(a+"是1000的倍数");
            }
            a++;
            Thread.sleep(5000);
        }

    }
    public static void main(String[] args) {
        Thread thread1=new Thread(new StopThreadWithOutSleep());
        thread1.start();
        try{
            Thread.sleep(2000);
            System.out.println("主线程休眠结束---------------------------------------------");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        thread1.interrupt();
    }
}

五.关于线程常用方法。

消费者生产者模式

1.必须要有一个类似于队列的东西当缓存
2.生产者线程安全且满足在仓库满的时候进行等待,在未满的时候,进行生产且通知消费者继续消费
3.消费者线程安全且在仓库空的时候进行等待,在未空的时候,进消费且通知生产者继续生产

实现1:通过BlockingQueue(阻塞队列来实现) 其put和take方法都是线程安全,且put方法会在仓库满的时候进行等待,在未满的时候,进行生产且通知消费者继续消费。take方法会在仓库空的时候进行等待,在未空的时候,进消费且通知生产者继续生产

/**
 * @author xiaqi
 * @date 2019/12/11
 *
 * 1.定义共享队列LinkedBlockingQueue
 * 2.定义原子类(用来确定货物编号)
 * 3.定义消费者实现类
 *      while(true){
 *          linkedBlockingQueue.take()
 *      }
 * 4.定时生产者实现类
 *      while(true){
 *          linkedBlockingQueue.put()
 *      }
 *
 * 说明:linkedBlockingQueue在put的时候会通过Lock加锁,
 *       linkedBlockingQueue在take的时候会通过take加锁,
 *       在内部通过Lock通过Condition await和signal来防止多线程下出现问题
 *
 */
public class BlockingQueueModel implements Model {
    private final BlockingQueue<Task> queue;
    private final AtomicInteger increTaskNo = new AtomicInteger(0);
    public BlockingQueueModel(int cap) {
        // LinkedBlockingQueue 的队列不 init,入队时检查容量;ArrayBlockingQueue 在创建时 init
        this.queue = new LinkedBlockingQueue<>(cap);
    }

    @Override
    public Runnable newRunnableConsumer() {
        return new ConsumerImpl();
    }
    @Override
    public Runnable newRunnableProducer() {
        return new ProducerImpl();
    }

    private class ConsumerImpl implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    // 固定时间范围的消费,模拟相对稳定的服务器处理过程
                    Thread.sleep(500 + (long) (Math.random() * 500));
                    System.out.println("consume: "+Thread.currentThread().getName()+"-------" + task.no);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
    private class ProducerImpl  implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep((long) (Math.random() * 1000));
                    Task task = new Task(increTaskNo.getAndIncrement());
                    System.out.println("produce: " +Thread.currentThread().getName()+"++++++++" + task.no);
                    queue.put(task);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
    public static void main(String[] args) {
        Model model = new BlockingQueueModel(3);
        for (int i = 0; i < 2; i++) {
            new Thread(model.newRunnableConsumer()).start();
        }
        for (int i = 0; i < 5; i++) {
            new Thread(model.newRunnableProducer()).start();
        }
    }
}

实现二:利用wait、notify来实现。注意:消费者与生产者采用同一个锁,且每次采用notifyAll方法进行唤醒。

/**
 * @author xiaqi
 * @date 2019/12/11
 * 错误地方,为了加快处理使用了两个锁:PRODUCE_LOCK、CONSUME_LOCK会导致一个问题,
 * 当消费者获取一次商品之后,不能唤醒生产者,只能唤醒CONSUME_LOCK.notifyAll()其他消费者,正常来说应该唤醒生产者继续生产的。
 * 代码之所以不出错是因为有while,一直在生产,所以显得正常,但是实际上并没有做到通知。因为对象notify的限制,所以一般不可能
 * 改正后采用同一个锁,可以保证正确,但是会造成效率不高,正确的解决方法是采用两个Lock变量PRODUCE_LOCK与CONSUME_LOCK。
 * 同时增加两个变量对应的PRODUCE_Condition,CONSUME_Condition,当消费者消费之后,可以唤醒生产者线程去生产。
 * 参考:https://monkeysayhi.github.io/2017/10/08/Java%E5%AE%9E%E7%8E%B0%E7%94%9F%E4%BA%A7%E8%80%85-%E6%B6%88%E8%B4%B9%E8%80%85%E6%A8%A1%E5%9E%8B/
 */
public class WrongWaitNotifyModel implements Model{
    /**
     * 定义队列
     */
    private final Queue<Task> linkedList=new LinkedList<Task>();
    /**
     * 定义队列长度
     */
    private int max=0;
    /**
     * 定义生产者锁
     */
    private final Object PRODUCE_LOCK=new Object();
    /**
     * 定义消费者锁
     */
//改正后采用同一个锁,可以保证正确,但是会造成效率不高,解决方法是采用两个Lock变量与两个变量对应的Condition
//    private final Object CONSUME_LOCK=new Object();
    /**
     * 定义原子类
     */
    private final AtomicInteger atomicInteger=new AtomicInteger(0);


    public WrongWaitNotifyModel(int length){
        max=length;
    }


    @Override
    public Runnable newRunnableConsumer() {
        return new ConsumeImpl();
    }

    @Override
    public Runnable newRunnableProducer() {
        return new ProduceImpl();
    }

    private class ConsumeImpl implements Runnable{

        @Override
        public void run() {
            try{
                while(true){
                    synchronized (PRODUCE_LOCK){
                        Thread.sleep(500);
                        while(linkedList.size()==0) {
                            System.out.println(Thread.currentThread().getName()+"已售罄");
                            PRODUCE_LOCK.wait();
                        }
                        Task task = linkedList.poll();
                        System.out.println(Thread.currentThread().getName()+" -- "+task.no);
                        PRODUCE_LOCK.notifyAll();
                    }
                }
            }catch (InterruptedException ie){
                ie.printStackTrace();
            }
        }
    }

    private class ProduceImpl implements Runnable{
        @Override
        public void run() {
            try{
                while(true){
                    synchronized (PRODUCE_LOCK){
                        Thread.sleep(200);
                        while(linkedList.size()>max){
                            System.out.println(Thread.currentThread().getName()+"当前仓库已满---");
                            PRODUCE_LOCK.wait();
                        }
                        Task task=new Task(atomicInteger.getAndIncrement());
                        linkedList.offer(task);
                        System.out.println(Thread.currentThread().getName()+"   +++   :"+task.no);
                        PRODUCE_LOCK.notifyAll();
                    }
                }
            }catch (InterruptedException ie){
                ie.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        Model model = new WrongWaitNotifyModel(3);
        for (int i = 0; i < 2; i++) {
            new Thread(model.newRunnableProducer()).start();
        }
        for (int i = 0; i < 2; i++) {
            new Thread(model.newRunnableConsumer()).start();
        }
    }
}

实现3:在实现2的基础上,利用reentrantLock与condition来实现。wait、notify用的是一个锁,但此时可以使用两个reentrantLock来将消费者锁和生产者锁分开、提高效率。同时采用两个condition来分别进行wait和notify操作。

----------------------------------------更新-------------------------------------------

1.ReentrantLock的实现是

pro.lock-- pro.unlock-- cus.lock-- cus.signal--cus.unlock实现

当使用sync的时候,我们可以再释放生产者锁之后,再获取消费者锁,唤醒消费者,达到跟reentrantLock类似的情况

2.在判断仓库是否为空、是否已满的时候,要用while循环,而不是if循环,原因是可能存在虚假唤醒。可参考:java多线程中虚假唤醒的原因都有什么? - 齐柏林的回答 - 知乎 https://www.zhihu.com/question/50892224/answer/280667072

死锁

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁的代码:

while(true){
 Synchorized(objectA){
    Synchorized(objectB){
        //操作
 
    }
 }
}
while(true){
 Synchorized(objectB){
    Synchorized(objectA){
        //操作
 
    }
 }
}

死锁的必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(3) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值