【重难点】【操作系统 02】线程有哪几种状态、线程调度方法、保持线程同步的方法、死锁、手写线程安全的生产者与消费者模型

【重难点】【操作系统 02】线程有哪几种状态、线程调度方法、保持线程同步的方法、死锁、手写线程安全的生产者与消费者模型

一、线程有哪几种状态

我们以 Java 中的线程为例
在这里插入图片描述

  • 新建状态:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它会保持这个状态直到程序 start() 这个线程
  • 就绪状态:当线程对象调用了 start() 方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度
  • 运行状态:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态
  • 阻塞状态:如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。进入阻塞状态的方有三种:
    • 等待状态:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
    • 同步阻塞:线程获取 synchronized 同步锁失败(因为同步锁被其它线程占用)
    • 其它阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态
  • 死亡状态:一个运行状态的线程完成任务或者 其它终止条件发生时,该线程就切换到终止状态

二、线程调度方法

与进程调度一样

三、保持线程同步的方法

对于多线程访问共享资源出现数据混乱的问题,需要进行线程同步。所谓的共享资源就是多个线程共同访问的变量,这些变量通常为全局数据区变量或者堆区变量,这些变量对应的共享资源也被称之为临界资源

互斥锁

互斥锁是线程同步最常用的一种方式,通过互斥锁可以锁定一个代码块,对于被锁定的这个代码块,所有的线程只能串行处理,不能并行处理

读写锁

读写锁是互斥锁的升级版,所有线程的读操作都是并行的,只有写操作是串行的。程序中的读操作越多,读写锁性能就越高,相反,若程序中全是写操作,那么读写锁会退化成互斥锁

条件变量

条件变量的主要作用不是处理线程同步,而是进行线程的阻塞。常常和互斥锁一起用在生产者和消费者模型中。举个例子,当任务队列为空时,消费者无法消费,使用条件变量把消费者线程阻塞住,等待生产者生产出任务后,再唤醒一个或多个被条件变量阻塞的消费者线程;反过来也可以控制生产者生产的上限,当任务队列达到一个上限值时用条件变量阻塞住生产者线程,直到消费者把任务消费后再唤醒被条件变量阻塞的生产者线程

信号量

信号量可以实现线程同步,也可以实现进程同步,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。信号量主要是为了阻塞线程,不能完全保证线程安全,如果要保证线程安全,需要和互斥锁一起使用。信号量也可以用来实现生产者消费者模型,在用条件变量实现生产者消费者模型时需要自己做条件判断,而使用信号量就不需要。举个例子,初始化生产者的信号量为 5,消费者的信号量为 0,因为消费者信号量为 0,所以会被阻塞。生产者进行一次生产后会将自己的信号量减 1,将消费者信号量加 1,这时消费者解除阻塞,进行消费后再将自己的信号量减 1,将生产者信号量加 1

自旋锁

自旋锁与互斥锁类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多成本

屏障

屏障让每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行

四、死锁

1.概念

多个线程竞争有限数量的资源,自己持有某种其它线程需要的资源不释放,而且又在等待其它线程持有的资源释放,一直在保持这种状态,就称为死锁。简单的说,就是两个或多个进程处于无期限的阻塞、相互等待的状态

在这里插入图片描述

2.产生的条件

  • 互斥:一个资源每次只能被一个线程使用
  • 不可抢占:线程已获得的资源,在未使用完之前,不能强行剥夺
  • 占有并等待:一个进程因请求资源而阻塞时,对已获得的资源不释放
  • 环形等待:若干进程之间形成一种首尾相接的循环等待资源关系

这四个条件时死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁

3.如何解决死锁

理解了死锁产生的条件,就可以最大可能地避免、预防和解除死锁。具体做法就是在在系统设计、进程调度等方面注意不要让死锁产生的四个必要条件成立,确定合理的资源分配算法,避免进程永久占用系统资源

4.手写一个死锁案例

package com.sisyphus.lock;

public class DeadLock {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();
        Thread thread1 = new Thread(new FirstThread(object1, object2));
        Thread thread2 = new Thread(new SecondThread(object1, object2));
        thread1.start();
        thread2.start();
    }
}

class FirstThread implements Runnable {
    Object object1;
    Object object2;

    public FirstThread(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    @Override
    public void run() {
        synchronized (object1) {
            System.out.println("线程 1 获得 锁 1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object2) {
                System.out.println("线程 1 获得 锁 2");
            }
        }
    }
}

class SecondThread implements Runnable {
    Object object1;
    Object object2;

    public SecondThread(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    @Override
    public void run() {
        synchronized (object2) {
            System.out.println("线程 2 获得 锁 2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object1) {
                System.out.println("线程 2 获得 锁 1");
            }
        }
    }
}

五、手写线程安全的生产者与消费者模型

1.Synchronized 实现

这是最简单也是最基础的实现方式,缓冲区 count 满和空时都调用 wait() 等待,当生产者生产或消费者消费后唤醒所有线程

package com.sisyphus.product;

public class SynchronizedDemo {
    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        //3 个生产者
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Producer()).start();

        //2 个消费者
        new Thread(demo.new Consumer()).start();
        new Thread(demo.new Consumer()).start();
    }

    private static Integer count = 0;
    private static final Integer FULL = 9;
    private static final String LOCK = "lock";

    class Producer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK) {
                    while (count.equals(FULL)) {
                        try {
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println(Thread.currentThread().getName() + " 生产者生产了 1 个产品,目前共有 " + count + " 个产品");
                    LOCK.notifyAll();
                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK) {
                    while (count == 0) {
                        try {
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + " 消费者消费了 1 个产品,目前共有 " + count + " 个产品");
                    LOCK.notifyAll();
                }
            }
        }
    }
}

2.ReentrantLock 实现

java.util.concurrent.lock 中的 Lock 通过对 lock() 和 unlock() 实现了对锁的显示控制,而 Synchronized 只能实现隐式控制。Reentrant 实现了 Lock 接口,是一个可重入的互斥锁。互斥锁是指同一时间只能被一个线程持有,可重入是指 ReentrantLock 可以被单个线程多次获取,每获取一次之后就要释放一次,否则其它线程无法获取该 ReentrantLock

与 Synchronized 的等待唤醒机制相比,Condition 具有更多的灵活性以及精确性,这是因为使用 Synchronized 的实现方式想要唤醒线程只能选择 notify() 随机唤醒一个线程,或者使用 notifyAll() 唤醒所有线程。而 Condition 则可以通过多个 Condition 实例对象建立更加精细的线程控制

package com.sisyphus.product;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    public static void main(String[] args) {
        ReentrantLockDemo demo = new ReentrantLockDemo();
        //3 个生产者
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Producer()).start();

        //2 个消费者
        new Thread(demo.new Consumer()).start();
        new Thread(demo.new Consumer()).start();
    }

    private static Integer count = 0;
    private static final Integer FULL = 9;
    //创建一个锁对象
    private final Lock lock = new ReentrantLock();
    //创建两个条件变量,一个为缓冲区非满,一个为缓冲区非空
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    class Producer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取锁
                lock.lock();
                try {
                    while (count.equals(FULL)) {
                        try {
                            notFull.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println(Thread.currentThread().getName() + " 生产者生产了 1 个产品,目前共有 " + count + " 个产品");
                    //唤醒消费者
                    notEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();
                try {
                    while (count == 0) {
                        try {
                            notEmpty.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + " 消费者消费了 1 个产品,目前共有 " + count + " 个产品");
                    //唤醒生产者
                    notFull.signal();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

3.BlockingQueue 实现

使用 BlockingQueue 的 take() 和 put() 实现生产者消费者,这里的生产者和生产者之间、消费者和消费者之间不存在同步,所以会出现连续生产和连续消费的现象

package com.sisyphus.product;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueueDemo demo = new BlockingQueueDemo();
        //3 个生产者
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Producer()).start();
        new Thread(demo.new Producer()).start();

        //2 个消费者
        new Thread(demo.new Consumer()).start();
        new Thread(demo.new Consumer()).start();
    }

    private static Integer count = 0;
    //创建一个阻塞队列
    final BlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(9);

    class Producer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    blockingQueue.put(1);
                    count++;
                    System.out.println(Thread.currentThread().getName() + " 生产者生产了 1 个产品,目前共有 " + count + " 个产品");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    blockingQueue.take();
                    count--;
                    System.out.println(Thread.currentThread().getName() + " 消费者消费了 1 个产品,目前共有 " + count + " 个产品");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

313YPHU3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值