进程与线程详解

2年前 (2022) 程序员胖胖胖虎阿
216 0 0

目录

一、进程 

二、线程

三、进程和线程的区别

四、多线程的创建和常见方法

1、多线程的创建

2、多线程的常用方法

五、线程的安全问题

5.1、线程不安全的原因是什么?

5.2如何解决线程不安全问题

六、锁--synchronized 关键字

七、volatile 关键字的作用和用法

synchronized和volatile的区别

八、对象的等待集

8.1、wait方法

8.2、notify方法

九、多线程案例

9.1、单例模式

9.2、阻塞式队列

9.3、定时器

9.4、线程池


一、进程 

进程是操作系统中一种非常重要的软件资源,当我们把一个可执行程序exe运行起来的时候,系统就会随之创建一个进程,如果这个程序结束系统会随之销毁对应的进程。

当运行exe文件时,exe文件中的很多内容都加载到内存中,通过分配资源来执行这个程序包含的指令的过程叫做进程

进程的管理是通过先描述在组织的方法:

在Linux中每创建一个进程就会创建一个PCB这样的类的实例

创建一个进程,本质上就是在内核中先创建一个PCB对象,然后将这个对象加入到链表中。

结束一个进程的时候,本质上就是在内核中找到这个PCB对象,将他从链表中删除,并且释放该对象。

我们通过任务管理器中看到的所以的进程信息就是在内核中遍历该链表,依次读取PCB中的信息。

进程与线程详解

进程与线程详解

 PCB中包含的信息:

1、进程id----进程的身份标识。

2、一组内存指针----指向该进程持有的一些重要数据在内存中的位置。 

以及状态、优先级、进程的记账信息、上下文,这些模块交做调度器来实现进程模块的调度,

为了让这么多进程可以在有限的CPU上并发执行。

状态:判断是在运行还是在休眠等······

优先级:判断这个进程是优先在CPU上执行还是放到后面······

进程的记账信息:记录进程在CPU上执行的时间,通过时间来限制一段进程的执行······

上下文:保存进程在CPU上执行的进度,方便下次继续执行······

二、线程

所谓的线程其实是一种轻量级的进程

1、一个进程中包含多个线程

2、相比于进程成本更低,大部分资源是和原来的线程共享的,主要共享内存资源和打开的文件,上下文等是不能共享的。

3、每个线程都有一段自己的执行逻辑,每个线程都是一个独立的执行流。

注意:当创建出一个进程时,会随之创建一个主线程。

线程中的管理模式和进程中的一样都是先描述在组织。

一个线程和一个PCB对应,一个进程可能和多个PCB对应,在PCB中会有线程组id来对应一组线程。

进程中最多可以有多少个线程取决于:

1、CPU的个数。

2、和线程执行的任务类型也相关(CPU密集性和IO密集性)。

三、进程和线程的区别

根本区别:进程是操作系统分配资源的最小单位,线程是任务调动和执行的最小单位。

在开销方面:每个进程都有独立的代码和数据空间,程序切换会有较大的开销。线程之间共享代码和数据,每个线程都有自己独立的栈和调度器,线程之间的切换的开销较小。

所处环境:一个操作系统中可以运行多个进程,一个进程中有多个线程同时执行。

内存分配方面:系统在运行时会为每个进程分配内存,系统不会单独为每个线程分配内存。

包含关系:创建进程时系统会自动创建一个主线程由主线程完成,进程中有多线程时,由多线程共同执行完成。

四、多线程的创建和常见方法

1、多线程的创建

1、通过创建类继承Thread来调用

    //1、通过类直接创建
    static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("qewrwerwerw");
        }
    }

    public static void main(String[] args) {
        Thread t=new MyThread();
        t.start();
    }

2、通过Thread匿名内部类进行调用

    public static void main(String[] args) {
        Thread t=new Thread(){
            @Override
            public void run() {
                System.out.println("wefwefwef");
            }
        };
        t.start();
    }

3、通过创建类实现Runnable接口实现

    static class MyThread implements Runnable{
        @Override
        public void run() {
            System.out.println("awetqeragearga");
        }
    }

    public static void main(String[] args) {
        Thread t=new Thread(new MyThread());
        t.start();
    }

4、通过Runnable实现匿名内部类调用实现

    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("w3rqetqwetqw");
            }
        };
        Thread r=new Thread(runnable);
        r.start();
    }

5、通过lambad表达式调用

    public static void main(String[] args) {
        Thread t=new Thread(()->System.out.println("asdasfasfasfas"));
        t.start();
    }

2、多线程的常用方法

2.1、多线程的构造方法

方法 解释
Thread() 创建线程对象
Thread(Runnable target) 使用Runnable类型创建对象
Thread(String name) 创建线程对象并命名
Thread(Runnable target,String name) 使用Runnable类型创建对象并命名

例:

    public static void main(String[] args) {
        Thread t=new Thread("我是你爸爸"){
            @Override
            public void run() {
                System.out.println("你好");
                while (true){
                    
                }
            }
        };
        t.start();
        while(true){
            
        }
    }

进程与线程详解

 2.2、线程的常见属性

属性 获取方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

2.3、中断程序

1)让线程run执行(比较温和)

    private static boolean cread=false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                while(!cread){
                    System.out.println("转账中");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();

        Thread.sleep(2000);
        System.out.println("有内鬼终止交易");
        cread=true;
    }

2)调用线程的interrupt方法来(比较激烈)

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("转账中");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //break;
                    }
                }
                System.out.println("转账被终止");
            }
        };

        t.start();
        Thread.sleep(2000);
        System.out.println("有内鬼终止交易");
        t.interrupt();
    }

Thread.interrupted()判断当前线程中断标志被设置--清除中断

Thread.currentThread().isInterrupted()判断指定线程的中断标志--不清除中断标志

2.4、线程的等待和休眠

线程之间是并发执行的

线程的等待:

我们可以通过join方法来阻塞线程,join为了控制线程结束的先后顺序。

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run() {
                for(int i=0;i<3;i++){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("我在执行t1");
                }
            }
        };

        Thread t2=new Thread(){
            @Override
            public void run() {
                for(int i=0;i<3;i++){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("我在执行t2");
                }
            }
        };

        t2.start();
        t1.start();
        t2.join();
        t1.join();
    }

当两个线程执行完成之后,主线程才会继续向下执行,我们可以通过这种方法来获取时间。

线程的休眠 :

在内核态中线程分为就绪队列和阻塞队列,当正常运行时,线程在就绪队伍排队执行,当出现sleep、join、wait、锁时线程会放入阻塞队伍,当通过恢复条件时会在进入就绪队伍

进程与线程详解

 2.5、线程状态

NEW 创建对象,没有PCB,布置了任务但是还没开始执行
RUNNABLE 就绪状态当前线程在CPU上执行或者准备好随时上CPU,通过就绪队列来维护
BLOCKED 阻塞状态,锁
WAITING 阻塞状态,wait
TIMED_WAITING 阻塞状态,sleep
TERMINATED 内核中的线程结束了,但是代码中的Thread对象还在

isAlive线程存活,除了NEW和TERMINATED之外,其他状态都表示线程存活。

进程与线程详解

五、线程的安全问题

线程不安全:多线程在并发执行某个代码时,产生了逻辑上的错误,就是线程不安全。

线程安全:多线程在并发执行过程中,没有产生逻辑上的错误,就是线程安全。

5.1、线程不安全的原因是什么?

1)线程是抢占式执行的

线程之间的调度完全由内核负责,线程之间的执行过程用户也不能干预。

2)自增操作不是原子性的

我们把自增操作分为三部分

load   把内存中的数据读取到CPU

incr   在CPU中把数据加1

save 把计算完成的结果放入内存中

进程与线程详解

 当线程一和线程二并行执行的时候增加了两次但是结果返回1,就出现了线程不安全的情况。

3)多个线程尝试修改一个变量

如果一个线程修改一个变量,线程安全

如果多个线程读取一个变量,线程安全

如果多个线程尝试修改不同变量,线程安全

4)内存可见性导致的线程安全问题

5)指令重排序

 java在编译代码时,会针对指令进行优化、调整指令的输出顺序,在原有逻辑不变的情况下,提高算法的运行速度。

5.2如何解决线程不安全问题

如果是抢占式执行解决不了

对于自增操作我们可以采用加锁的方式来让线程变得安全

多线程同时修改同一个变量,这个想要看具体要求

public class TreadDome {
    static class Test{
        public int count=0;

        public void add(){
            count++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Test t=new Test();
        Thread t1=new Thread(){
            @Override
            public void run() {
                for(int i=0;i<5000;i++){
                    t.add();
                }
            }
        };
        t1.start();
        Thread t2=new Thread(){
            @Override
            public void run() {
                for(int i=0;i<5000;i++){
                    t.add();
                }
            }
        };
        t2.start();

        t1.join();
        t2.join();
        System.out.println(t.count);
    }
}

 注意:前置++  后置++  前置--  后置--  +=  -=  *=  /=····等操作都不是原子性的。

           直接赋值操作=   如果是针对内置类来说,一般是原子的,如果是针对引用类型来说不一定。

六、锁--synchronized 关键字

锁的特点:锁是具有互斥性,同线程只能有一个获取到锁。其他线程如果想要获取锁就会发生阻塞,直到获取锁的线程结束其他线程才能继续竞争锁。

在上述代码中我们加入:

        synchronized public void add(){
            count++;
        }

我们就会发现代码运行正常了,线程安全了。

这个时候的synchronized就是在针对t这个对象来加锁,进入add方法内部,就把加锁状态设为true,退出方法时把状态设置为false。

进程与线程详解

 我们加上锁之后就会把自增操作变成原子性的,这样就实现了线程的安全。

synchronized的几种常用方法:

1、加到普通方法前,表示锁this

2、加到静态方法前,表示锁当前类的类对象

3、加到某个代码块之前,显示指定给某个对象加锁

    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();
        Thread t1=new Thread(){
            @Override
            public void run() {
                Scanner scanner=new Scanner(System.in);
                synchronized (object){
                    System.out.println("请输入一个数");
                    int num=scanner.nextInt();
                    System.out.println("num="+num);
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                while(true){
                    synchronized (object){
                        System.out.println("线程2");
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        t1.start();
        t2.start();

    }

进程与线程详解

 线程就会停止,发生阻塞。

synchronized (object01.getClass()){}

synchronized (object02.getClass()){}

getClass()方法调用的是这个类的类对象,所以也会发生互斥。

扩展:

如果有一个线程一直占用锁的情况发生就会出现锁死的情况,一旦锁死之后解不开,需要进行重启。

如果对锁死感兴趣可以去了解一下哲学家就餐问题。

stringBuffer Vector HashTable 内部加上了锁线程安全
StringBuilder ArrayList HashMap 在单线程中可以使用,效率更高

七、volatile 关键字的作用和用法

当我们尝试使用两个线程,通过其中一个线程去进行读取操作,一个线程进行写入操作时

public class TreadDome01 {
    static class Counter{
        public int flag=0;
    }
    public static void main(String[] args) {
        Counter counter=new Counter();
        Thread t1=new Thread(){
            @Override
            public void run() {
                while(counter.flag==0){

                }
            }
        };
        t1.start();

        Thread t2=new Thread(){
            @Override
            public void run() {
                Scanner scanner=new Scanner(System.in);
                System.out.println("请输入一个整数:");
                counter.flag=scanner.nextInt();
            }
        };
        t2.start();
    }
}

进程与线程详解

我们会发现代码运行的结果和我们想的不太一样。

这是因为在进行比较的时候while(counter.flag==0),我们先从内存中读取到flag的值到cpu中,在cpu中比较这个值和0的相等关系,由于读取cpu的速度远大于读取内存的速度,在同一个线程中flag的值没有改变,编译器就会优化这个代码,后续的判断都在cpu上进行,而不是从内存中读取flag这个值。

    static class Counter{
        public volatile int flag=0;
    }

当我们加入volatile时,读取时我们就会在内存上读取到cpu中进行比较,

volatile的作用是:保持内存的可见性。

synchronized和volatile的区别

区别
synchronized 一般是两个线程都进行写入操作
volatile 一般是两个线程,一个线程写入,一个线程读出

八、对象的等待集

为什么会有对象的等待集,这是因为抢占式执行会出现问题,当一个线程重复进行抢占时,会对其他的线程产生影响,这时候我们就需要用到等待集来帮助我们来手动干涉抢占式执行。

对象等待集中会有wait方法、notify方法;

wait方法:当操作条件不成熟就等待;

notify方法:当条件成熟时,通知指定的线程来工作;

8.1、wait方法

wait方法的工作流程:

1、释放锁

2、等待通知(过程可能会很久)

3、当收到通知后,尝试重新获取锁,继续往下执行

注意:wait方法需要在锁中执行

    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            System.out.println("等待前");
            object.wait();
            System.out.println("等待后");
        }
    }

8.2、notify方法

notify方法就是使停止的线程继续运行。

    public static void main(String[] args) {
        Object object=new Object();
        Thread t1=new Thread(){
            @Override
            public void run() {
                synchronized(object){
                    while (true) {
                        System.out.println("等待中");
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("等待结束");
                    }
                }
            }
        };
        t1.start();
        Thread t2=new Thread(){
            @Override
            public void run() {
                Scanner scanner=new Scanner(System.in);
                System.out.println("请输入一个数:");
                int ma=scanner.nextInt();
                synchronized (object){
                    System.out.println("notify开始");
                    object.notify();
                    System.out.println("notify结束");
                }
            }
        };
        t2.start();

    }

进程与线程详解

 注意:

1、在synchronized中嵌套synchronized

    public static void main(String[] args) {
        Object object=new Object();
        Thread t=new Thread(){
            @Override
            public void run() {
                synchronized (object){
                    Scanner scanner=new Scanner(System.in);
                    System.out.println("输入一个数:");
                    int a=scanner.nextInt();
                    System.out.println(a);
                    //在synchronized中嵌套synchronized
                    synchronized (object){
                        System.out.println("输入一个数:");
                        int b=scanner.nextInt();
                        System.out.println(b);
                    }
                }
            }
        };
        t.start();
    }

这样看起来像是一个死锁,但是代码不会锁死,sychronized在内部针对这样的情况进行了特殊处理。调用的是操作系统提供的mutex(互斥量)。

加锁操作是调用pthread_mutex_lock函数。

解锁操作调用的是pthread_mutex_unlock函数。

像这种情况,不会真正的执行lock函数,而是仅仅维护一个“引用计数”,第一次进行调用才会调用lock函数,当引用计数减到0时调用unlock函数。

2、竞态条件问题

进程与线程详解

 通过这个图我们引发了以下问题,当wait释放锁之后还没有等待通知时,第二个线程发送了通知,第一个线程没有接受到,这是就会出现竞态条件问题,但是java中释放锁和等待通知是具有原子性,两个同时进行,就避免了这种情况的发生。

九、多线程案例

9.1、单例模式

单例模式是一种常见的设计模式,应用于代码中有些概念不应该存在多个实例,在这种场景中我们需要用单例模式来解决问题

单例模式分为饿汉模式懒汉模式两种

饿汉模式:

    static class Singleton{
        private Singleton(){}
        private static Singleton instance=new Singleton();
        public static Singleton getInstance(){
            return instance;
        }
    }

    public static void main(String[] args) {
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        System.out.println(s1==s2); //true
    }

懒汉模式:

    static class Singleton{
        private Singleton(){}
        private static Singleton instance=null;
        public static Singleton getInstance(){
            if(instance==null){
                instance=new Singleton();
            }
            return instance;
        }
    }

注意:懒汉模式和饿汉模式的区别是饿汉模式只要被加载就会创建一个类,而懒汉模式在第一次调用getInstance方法的时候才会被调用。在这里注意懒汉模式的线程是不安全的,当多个线程同时修改一个变量的时候就会出现问题,饿汉模式的线程是安全的,因为饿汉模式多个线程是在读取一个变量。

进程与线程详解

 由于上图所示,懒汉模式不安全,我们需要对懒汉模式进行优化。

最终优化结果:

    static class Singleton{
        //改成私有构造方法
        private Singleton(){}

        public volatile static Singleton instance=null;

        public static Singleton getInstance(){
            if(instance==null){
                synchronized (Singleton.class){
                    if (instance==null){
                        instance=new Singleton();
                    }
                }
            }
            return instance;
        }
    }

锁粒度要最小(锁粒度就是锁中包含的代码量)。同时因为多线程操作时,第一个线程写入,其他的线程读出,因此我们需要在变量前加入volatile来修改,避免因为内存可见性而引起的线程不安全。

进程与线程详解

 为了保证线程安全最重要的三个点:

1、加锁 保证进程的安全

2、双重if保证效率

3、volatile避免内存可见性带来的问题

9.2、阻塞式队列

阻塞队列是一种并发编程的方式,是一种生产者消费者模型。

它是一个先进先出的队列:

入队列时发现队列满了,就会阻塞,等待有线程出队列有空位了,才能继续入队列。

出队列时发现队列为空,就会阻塞,等待有线程入队列之后,才能继续出队列。

入队列:

        public void put(int value) throws InterruptedException {
            synchronized (this) {
                if(size==array.length){
                    wait();
                }
                array[tail]=value;
                tail++;
                if(tail==array.length){
                    tail=0;
                }
                size++;

                notify();
            }
        }

出队列:

        public int get() throws InterruptedException {
            int temp=-1;
            synchronized (this) {
                if(size==0){
                    wait();
                }
                temp = array[head];
                head++;
                if(head==array.length){
                    head=0;
                }
                size--;

                notify();
            }
            return temp;
        }

我们通过入队列的wait用出队列中的notify来控制,通过出队列的wait用出队列中的notify来控制。

注意:

当三个线程时(两个线程入队列,一个线程出队列):

如果队列已经满了、两个入队列线程都堵塞

如果出队列被唤醒,其中一个入队列就会被唤醒,继续插入元素。

如果队列已经空了,出队列就会被阻塞,直到两个入队列任何一个插入成功

当我们使用notifyAll时,我们需要用while来搭配使用。

                while (size==array.length){
                    wait();
                }

代码解析:

我们通过数组来实现阻塞队列

public class ThreadDome02 {
    public static class BlockingQueue{
        private int[] array=new int[1000];
        private int head=0;
        private int tail=0;

        private int size=0;

        public void put(int value) throws InterruptedException {
            synchronized (this) {
                if(size==array.length){
                    wait();
                }
                array[tail]=value;
                tail++;
                if(tail==array.length){
                    tail=0;
                }
                size++;

                notify();
            }
        }

        public int get() throws InterruptedException {
            int temp=-1;
            synchronized (this) {
                if(size==0){
                    wait();
                }
                temp = array[head];
                head++;
                if(head==array.length){
                    head=0;
                }
                size--;

                notify();
            }
            return temp;
        }
    }

    public static void main(String[] args) {
        BlockingQueue queue=new BlockingQueue();
        Thread t1=new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    try {
                        queue.put(i);
                        System.out.println("生产"+i);
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                while (true) {
                    try {
                        int temp=queue.get();
                        System.out.println("消费"+temp);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }
}

9.3、定时器

定时器是是多线程编程的重要组件,当编写好定时器之后,会等到延迟之后的时间进行执行,就像闹钟定时一样。

定时器的构成:

1、使用一个Task类来描述一段逻辑,同时要对执行的时间来进行记录。

2、使用柱塞优先级队列来组织若干跟Task。

3、使用一个扫描进程来进行不断的扫描检测,若需要执行就执行这个任务。

Task类的创建:

    static class Task implements Comparable<Task>{
        //通过Runnable中的run方法,来描述具体执行的类容
        private Runnable command;
        //描述时间
        private long time;
        //私有的构造方法
        public Task(Runnable command, long time) {
            this.command = command;
            this.time =System.currentTimeMillis()+time;
        }
        //具体执行任务的逻辑
        public void run(){
            command.run();
        }
        
        @Override
        //表小的
        public int compareTo(Task o) {
            return (int) (this.time-o.time);
        }
    }

阻塞优先级队列:

private PriorityBlockingQueue<Task> queue=new PriorityBlockingQueue<>();

对于扫描类的创建和使用:

        public Timer(){
            Work work=new Work(queue);
            work.start();
        }
    static class Work extends Thread{
        private PriorityBlockingQueue<Task> queue=null;

        public Work(PriorityBlockingQueue<Task> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true){
                try {
                    Task task=queue.take();
                    long curTime=System.currentTimeMillis();
                    if(task.time>curTime){
                        queue.put(task);
                    }else{
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

创建一个方法来安排:

        public void schenule(Runnable command,long after){
            Task task=new Task(command,after);
            queue.put(task);
        }

注意:这样编写会出现忙等的情况,当输入的时间和现在的时间间隔过长时,就会一直扫描,就出现了忙等的情况。

对于这种情况我们使用wait和notify来解决。

                    if(task.time>curTime){
                        queue.put(task);
                    //加锁wait()就是死等到notify()方法在执行
                    //wait(time)中time时间到了就会去执行
                        synchronized (mailBox){
                            mailBox.wait(task.time-curTime);
                        }
                    }else{
                        task.run();
                    }
        public void schenule(Runnable command,long after){
            Task task=new Task(command,after);
            queue.put(task);
            synchronized (mailBox){
                mailBox.notify();
            }
        }

源代码:

public class ThreadDome04 {
    //用Task来描述这段逻辑
    static class Task implements Comparable<Task>{
        //通过Runnable中的run方法,来描述具体执行的类容
        private Runnable command;
        //描述时间
        private long time;
        //私有的构造方法
        public Task(Runnable command, long time) {
            this.command = command;
            this.time =System.currentTimeMillis()+time;
        }
        //具体执行任务的逻辑
        public void run(){
            command.run();
        }

        @Override
        //表小的
        public int compareTo(Task o) {
            return (int) (this.time-o.time);
        }
    }
    //不断扫描的类
    static class Work extends Thread{
        private PriorityBlockingQueue<Task> queue=null;
        private Object mailBox=null;
        public Work(PriorityBlockingQueue<Task> queue,Object mailBox) {
            this.queue = queue;
            this.mailBox=mailBox;
        }

        @Override
        public void run() {
            while (true){
                try {
                    Task task=queue.take();
                    long curTime=System.currentTimeMillis();
                    if(task.time>curTime){
                        queue.put(task);
                        synchronized (mailBox){
                            mailBox.wait(task.time-curTime);
                        }
                    }else{
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //调用的类
    static class Timer{
        private PriorityBlockingQueue<Task> queue=new PriorityBlockingQueue<>();
        private Object mailBox=new Object();
        public Timer(){
            Work work=new Work(queue,mailBox);
            work.start();
        }

        public void schenule(Runnable command,long after){
            Task task=new Task(command,after);
            queue.put(task);
            synchronized (mailBox){
                mailBox.notify();
            }
        }
    }

    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schenule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hehe");
            }
        },2000);
    }
}

9.4、线程池

线程池会包含一些线程,可以让我们直接使用,线程池可以避免频繁创建和销毁线程的开销。

线程池的组成部分:

1、需要有一个类来描述具体线程要做的工作。

2、用一个数据结果来组织任务(队列)。

3、通过一个类来描述工作进程。

4、需要有一个数据结构来组织若干个进程。

1、用一个类来描述工作进程

    //用一个类来描述工作进程
    static class Worker extends Thread{
        // 每个 Worker 线程都需要从任务队列中取任务.
        // 需要能够获取到任务队列的实例
        private BlockingQueue<Runnable> queue=null;

        public Worker(BlockingQueue<Runnable> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                //只要线程收到异常就会结束run;
                while (!Thread.currentThread().isInterrupted()){
                    Runnable command=queue.take();
                    command.run();
                }
            } catch (InterruptedException e) {
                //e.printStackTrace();
                System.out.println("线程被终止了");
            }
        }
    }

2、阻塞队列

        // 这个阻塞队列用于组织若干个任务
        private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

3、用List中放入线程

private List<Worker> workers = new ArrayList<>();

4、编写execute(将一个任务加入到线程池中)和shutdown(销毁线程中的所有线程)方法

 

        //定义一个10来约束workers中size的个数
        //添加
        private static final int maxWorkerCount=10;
        public void execute(Runnable command) throws InterruptedException {
            if(workers.size()<maxWorkerCount){
                Worker worker=new Worker(queue);
                worker.start();
                workers.add(worker);
            }
            queue.put(command);
        }

        //结束
        public void shutdown() throws InterruptedException {
            //首先终止掉所有线程
            for(Worker worker:workers){
                worker.interrupt();
            }
            
            //需要等待每个线程结束
            for(Worker worker:workers){
                worker.join();
            }
        }

这样基本上所有的基础的东西就写完了,下面是完整代码展示:

package Thread;


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ThreadDome01 {
    //用一个类来描述工作进程
    static class Worker extends Thread{
        private int id=0;
        // 每个 Worker 线程都需要从任务队列中取任务.
        // 需要能够获取到任务队列的实例
        private BlockingQueue<Runnable> queue=null;

        public Worker(BlockingQueue<Runnable> queue,int id) {
            this.queue = queue;
            this.id=id;
        }

        @Override
        public void run() {
            try {
                //只要线程收到异常就会结束run;
                while (!Thread.currentThread().isInterrupted()){
                    Runnable command=queue.take();
                    System.out.println(id + "running、、、");
                    command.run();
                }
            } catch (InterruptedException e) {
                //e.printStackTrace();
                System.out.println("线程被终止了");
            }
        }
    }

    static class MyThreadPoll{
        private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();

        private List<Worker> workers = new ArrayList<>();

        //定义一个10来约束workers中size的个数
        //添加
        private static final int maxWorkerCount=10;
        public void execute(Runnable command) throws InterruptedException {
            if(workers.size()<maxWorkerCount){
                Worker worker=new Worker(queue,workers.size());
                worker.start();
                workers.add(worker);
            }
            queue.put(command);
        }

        //结束
        public void shutdown() throws InterruptedException {
            //首先终止掉所有线程
            for(Worker worker:workers){
                worker.interrupt();
            }

            //需要等待每个线程结束
            for(Worker worker:workers){
                worker.join();
            }
        }

        static class Command implements Runnable{
            private int num;

            public Command(int num) {
                this.num = num;
            }

            @Override
            public void run() {
                System.out.println("正在执行任务"+num);
            }
        }

        public static void main(String[] args) throws InterruptedException {
            MyThreadPoll pol=new MyThreadPoll();
            for(int i=0;i<1000;i++){
                pol.execute(new Command(i));
            }
            Thread.sleep(2000);
            pol.shutdown();
            System.out.println("被销毁了");
        }
    }
}

进程与线程详解

通过结果我们可以看出随着代码的执行,线程开始创建,线程最终被限制在10个,随着线程的终止,线程一个一个的被销毁(在执行任务的过过程中使用哪个线程是不知道的)。 

版权声明:程序员胖胖胖虎阿 发表于 2022年11月23日 上午12:24。
转载请注明:进程与线程详解 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...