多线程

Thread

线程概述

  • 进程:是操作系统中一个程序及其数据在处理机上顺序执行时所发生的活动

  • 线程:也成轻量进程,是进程中某个单一顺序的控制流(进程中的某一新单元)

  • 多进程:在操作系统中同时运行多个任务(程序)

  • 多线程:在同一应用程序中有多个顺序流同时执行

  • 线程的生命周期:一个线程从创建到执行完的整个过程

线程与进程的区别

​ 进程对应的是一个服务,线程是进程中的执行单元

​ 一个进程可以包含多个线程,一个线程一定属于某个进程

​ 多线程特点:线程是异步的,线程是并发的,多个线程同时工作,线程与线程之间是互不干扰的

​ 例子:火车站(进程)和售票窗口(线程)火车站 的关系

关于线程的创建方式

三种方法

  • 继承Thread类

  • 实现Runnable接口 / 通过匿名内部类来创建线程对象

  • 使用Callable结合Task实现多线程

继承Thread类

方法一:继承Thread类,重写run()方法

package com.os467.thread;

public class ThreadDemo01 {

    public static void main(String[] args) {

        //创建线程对象
        ThreadImpl01 threadImpl01 = new ThreadImpl01();
        ThreadImpl01 threadImpl02 = new ThreadImpl01();

        //开启线程
        threadImpl01.start();
        threadImpl02.start();

    }

}


class ThreadImpl01 extends Thread{

    /**
     * 让程序跑起来的方法
     */
    @Override
    public void run() {

        for (int i = 0; i < 10; i++) {

            System.out.println("第"+(i + 1)+"个数为"+i);

        }


    }
}

设置线程的名称

//设置线程的名称
threadImpl01.setName("t1");
threadImpl02.setName("t2");

获取当前正在运行的线程

class ThreadImpl01 extends Thread{

     //获取当前正在运行的线程
    Thread thread = Thread.currentThread(); 
    
    System.out.println(thread.getName()+"正在执行");

}

实现Runnable接口

方法二:实现一个Runnable接口

此方法的实现类只是一个可运行类,不能被称为线程类

package com.os467.thread;

public class ThreadDemo02 {

    public static void main(String[] args) {

        //创建线程对象
        RunnableImpl02 runnableImpl02 = new RunnableImpl02();

        //创建线程类
        Thread thread1 = new Thread(runnableImpl02);

        Thread thread2 = new Thread(runnableImpl02);


        //设置线程名称
        thread1.setName("t1");

        //设置线程名称
        thread2.setName("t2");

        //开启线程
        thread1.start();

        //开启线程
        thread2.start();


    }

}

/**
 * 可运行类
 */
class RunnableImpl02 implements Runnable{


    @Override
    public void run() {

        for (int i = 0; i < 10; i++) {

            //获取当前正在运行的线程
            Thread thread = Thread.currentThread();

            System.out.println(thread.getName()+"正在执行:"+(i + 1)+"个数为"+i);

        }

    }

}

通过匿名内部类来创建线程对象

package com.os467.thread;

public class ThreadDemo03 {

    public static void main(String[] args) {

        //创建线程对象
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i < 10; i++) {

                    System.out.println(Thread.currentThread().getName()+" : "+i);

                }

            }
        });

        //创建线程对象
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i < 10; i++) {

                    System.out.println(Thread.currentThread().getName()+" : "+i);

                }

            }
        });

        thread1.setName("t1");
        thread2.setName("t2");

        //开启线程
        thread1.start();
        thread2.start();

    }

}

线程的生命周期

  • 新建状态

    ​ 创建出线程对象之后,线程就处于新建状态

  • 就绪状态

​ 当一个线程调用了start()方法之后,该线程处于就绪状态,处于就绪状态的线程拥有争夺CPU资源的使用权力

  • 运行状态

​ 当一个线程争夺到了CPU使用权之后,就回去执行线程,运行run方法,这个时候的线程处于运行状态

  • 阻塞状态

​ 当线程在运行的过程中出现了控制台打印、睡眠sleep等需要等待的操作,这个时候线程会处于阻塞状态,处于阻塞状态的线程,会释放CPU使用权

  • 死亡状态

​ 当一个线程执行完run方法中所有的代码就会跳出run方法,这个时候线程处于死亡状态 isAlive()方法可以判断线程是否存活

package com.os467.thread;

public class ThreadDemo04 {

    public static void main(String[] args) {

        //创建线程对象,该线程处于新建状态
        ThreadImpl02 threadImpl01 = new ThreadImpl02();
        ThreadImpl02 threadImpl02 = new ThreadImpl02();

        threadImpl01.setName("t1");
        threadImpl02.setName("t2");

        //该线程处于就绪状态
        threadImpl01.start();
        threadImpl02.start();

    }

}


class ThreadImpl02 extends Thread{

    @Override
    public void run() {//当某一个线程争夺到cpu使用权后才会进入run方法

        for (int i = 0; i < 10; i++) {

            System.out.println(Thread.currentThread().getName() + "======>"+i);

            //睡眠的方法,可以让程序处于阻塞状态
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

    //当某一线程执行完了run方法,那该线程处于死亡状态
}

Run方法和start方法的区别

Run是执行方法,start是启动方法

start启动不一定执行run方法

线程调度模型

  • 抢占式调度模式

​ 哪个线程的优先级比较高,抢到的CPU时间片的概率就多一些,java采用的就是抢占式调度模型

  • 均分式调度模型

​ 平均分配CPU时间片,每个线程占用的CPU时间片时间长度一样,平均分配,一切平等,有一些编程语言,线程调度模型采用 的是这种方式

使用哪种方式创建线程比较好?

​ 实现runnable接口,因为我们以后是面向接口开发

​ 因为如果继承了Thread类,那么类的可扩展性就降低了

​ 以后我们在写程序,尽量使用Runnable接口方式来创建线程对象,面向接口开发,程序的可扩展性会提高

问题:以下代码一共开启了几个线程?

package com.os467.thread;

public class ThreadDemo05 {

    public static void main(String[] args) {

        //创建线程对象
        Thread thread1 = new Thread(new MyRunnable());
        Thread thread2 = new Thread(new MyRunnable());
        Thread thread3 = new Thread(new MyRunnable());

        thread1.setName("t1");
        thread3.setName("t3");

        thread1.start();
        thread2.run();
        thread3.start();

        for (int i = 0; i < 10; i++) {

            System.out.println(Thread.currentThread().getName() + " ==> "+i);

        }

    }


}


class MyRunnable implements Runnable{

    @Override
    public void run() {


        for (int i = 0; i < 10; i++) {

            System.out.println(Thread.currentThread().getName() + " ==> "+i);

        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }


}

答案:一共开启了4个线程:

​ 两个普通线程 调用了start()方法

​ 守护线程(后台线程):垃圾回收线程

​ 主线程(main线程):JVM调用主函数

线程在内存上的分布,以及执行流程

  • ​ 每一个线程都会去对应一个栈,创建的每个线程,JVM都会去开辟出一个分支栈
  • ​ main中 针对于某个线程调用了start方法,这个时候,JVM会为这个线程单独的开辟出一个分支栈
  • ​ 假如程序中开启了t1、t2、t3 三个线程,栈与栈之间的资源是不共享的
  • ​ 比如 t1对应的分支栈声明了一个集合对象List,这个集合的引用在别的栈是访问不到的

在主线程调用了start方法之后,会开辟分支栈,每个栈都会去执行不同的任务,栈与栈之间的资源是不共享的

堆的资源是共享的,因为JVM上只有一个堆,只要你去创建引用数据类型的实例,都是在堆中开辟空间

方法区中的资源也是共享的,因为内存中只能有一个方法区

获取、设置线程优先级方法

//获取某个线程的优先级,java默认情况下线程优先级是5
int priority1 = thread1.getPriority();
//设置线程优先级方法,1-10,1优先级最低,10最高
thread1.setPriority(1);

yield()让位方法

暂停当前正在执行的线程对象,并执行其他线程

yield()方法的执行会让当前线程从”运行状态”回到”就绪状态”

Thread.yield();

多线程其它方法

join()方法

join()当前线程进入阻塞,指定线程执行,直到指定线程结束当前线程才可以继续

线程a{

​ 线程b.join()

}

将线程b合并入线程a,线程a等待线程b结束后才继续运行

注意:线程合并必须要有两个不同的线程


suspend()线程自己把自己挂起

resume线程自己把自己唤醒

龟兔赛跑案例

兔子线程类

package com.os467.game;

//兔子线程
public class Rabbit extends Thread {

    private String name;

    public Rabbit(String name) {
        //给当前的线程实例设置名称
        super(name);
        this.name = name;
    }


    @Override
    public void run() {

        //模拟整个赛道跑步的过程
        for (int i = 0; i <= 900; i+=100) {

            if(i == 800){

                System.out.println(Thread.currentThread().getName()+"开始睡觉");

                //自己把自己挂起
                Thread.currentThread().suspend();

            }

            System.out.println(Thread.currentThread().getName() + "跑了"+(i+100)+"米");

            try{

                //模拟睡眠
                Thread.sleep(1000);


            }catch (Exception e){

                e.printStackTrace();

            }

        }


        System.out.println(Thread.currentThread().getName()+"跑完了全程,最终比赛失败");

    }

}

乌龟线程类

package com.os467.game;

//乌龟线程类
public class Tortoise extends Thread {

    private String name;

    //兔子的引用
    private Rabbit rabbit;

    public Tortoise(String name, Rabbit rabbit) {
        //给当前的线程实例设置名称
        super(name);
        this.name = name;
        this.rabbit = rabbit;
    }

    //唤醒兔子的方法
    public void resumeRabbit(){

        System.out.println(Thread.currentThread().getName()+" 唤醒了 "+rabbit.getName());

        //调用唤醒的方法
        this.rabbit.resume();

    }

    @Override
    public void run() {

        //模拟整个赛道跑步的过程
        for (int i = 0; i <= 950; i+=50) {

            System.out.println(Thread.currentThread().getName() + "跑了"+(i+50)+"米");

            try{

                //模拟睡眠
                Thread.sleep(1000);


            }catch (Exception e){

                e.printStackTrace();

            }

        }

        //乌龟唤醒兔子
        this.resumeRabbit();

        System.out.println(Thread.currentThread().getName()+"跑完了全程,取得了胜利");

    }
}

测试类

package com.os467.game;

public class Test {

    public static void main(String[] args) {

        //创建兔子线程
        Rabbit rabbit = new Rabbit("兔子");

        //创建乌龟线程
        Tortoise tortoise = new Tortoise("乌龟",rabbit);

        //开启线程
        rabbit.start();
        tortoise.start();

    }

}

//tl.stop()中断线程

//interrupt中断睡眠

//tl.isAlive()判断指定线程是否处于活动状态

守护线程

  • java语言中线程分为两大类

用户线程

守护线程(后台线程)

  • 守护线程的特点

一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束

守护线程一般指的是后台线程,守护线程一般会去守护一个用户线程,用户线程启动,守护线程随之启动,用户线程结束,守护线程也结束,例如垃圾回收线程

设置守护线程方法:普通线程对象.setDaemon(true)

package com.os467;

public class ThreadDemo01 {

    public static void main(String[] args) {

        //创建线程对象
        Thread t1 = new Thread(new createThread());

        //将一个普通线程设置为守护线程
        t1.setDaemon(true);

        //开启线程
        t1.start();

        for (int i = 0; i < 10; i++) {

            System.out.println(Thread.currentThread().getName()+" ===> "+i);

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }

    }

}

class createThread implements Runnable{


    @Override
    public void run() {

        //守护线程一般就是一个死循环
        while (true){

            System.out.println("守护线程正在执行");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定时器

  • 定时器的作用:间隔特定的时间,执行特定的程序

​ 在java的类库中已经写好了一个定时器:java.util.Timer

实现线程的第三种方式(Callable结合Task)

  • 使用Callable结合Task实现多线程编程

FutureTask futureTask = new FutureTask(new Callable())

public class ThreadDemo02 {

    public static void main(String[] args) {

        //这个类官方称之为未来任务类,不是线程类
        FutureTask futureTask = new FutureTask(new Callable() {

            //作用类似于run方法
            @Override
            public Object call() throws Exception {

                int num = 0;

                for (int i = 0; i < 10; i++) {

                    num += i;

                    System.out.println(Thread.currentThread().getName() + "===>" + i);

                    Thread.sleep(500);

                }

                return num;

            }
        });

        //创建线程对象
        Thread thread = new Thread(futureTask);

        //设置线程名称
        thread.setName("t1");

        //开启线程
        thread.start();

        //线程结束之后获取线程的返回值
        try {

            Object o = futureTask.get();

            System.out.println("最终累加的结果为:"+o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }



    }

}

缺点:由于需要获取返回值,此方法会造成原线程的阻塞

线程同步

  • 异步编程模型

​ 线程A和线程B,各自执行各自的,A不管B,B不管A,谁也不需要等谁,这种编程模型叫做:异步编程模型,其实就是:多线程并发,效率较高

  • 同步编程模型

​ 线程A和线程B,在线程A执行的时候,必须等待B线程执行结束,或者说在A线程执行的时候,必须等待B线程执行结束,两个线程之间发生了等待关系,这就是同步程模型,效率较低

锁池和等待池

  • 锁池

    ​ 假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B,C想要调用这个对象的某个synchronized方法(或者块)之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正被A所占有,此时B,C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

  • 等待池

​ 假设线程A调用了某个对象的wait方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入等待池中的线程不会去竞争该对象的锁

Object类中的wait和notify方法

  • wait() 让正在该对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。wait()方法的调用,会让正在改对象的当前线程进入等待状态

此对象其实就是多线程中的共享对象

使用此方法必须保证当前多线程处于线程同步状态

当共享对象是类的实例的时候,可以直接在类的方法中写waitnotify方法

notify()方法作用

​ 只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会

notifyAll()方法的作用

​ 会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会

需求:生产者和消费者关系

仓库

仓库里面存放的就是生产者生产出来的商品,需要交给消费者去消费

要保证生产者与消费者均衡的状态:

如果仓库中没有产品了,这个时候消费者线程就不能再进行消费了,需要交给生产者线程去生产,如果仓库已经满了,仓库已经有产品的情况下,生产者线程就不用再生产了,要交由消费者线程去消费

生产者与消费者wait、notify

​ 要想使用wait和notify方法,必须是在线程同步的前提下

Consumer

package com.os467.wait;

public class Consumer extends Thread {

    private House house;

    public Consumer(String name,House house){

        super(name);
        this.house = house;

    }

    @Override
    public void run() {

        //模拟一直生产
        while (true) {

            synchronized (this.house) {

                if (this.house.getObjects().size() == 0) {

                    //消费者不能再消费,要让这个消费者处于等待状态
                    try {

                        //让处于该对象上活跃的线程处于等待状态
                        this.house.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

                //仓库中有产品,消费者就要去消费
                Object obj = this.house.getObjects().remove(0);

                System.out.println(Thread.currentThread().getName() + "消费了一个产品,产品实例为" + obj);

                try {
                    //模拟睡眠
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //唤醒生产者去生产
                this.house.notifyAll();

            }
        }
    }
}

House

package com.os467.wait;

import java.util.List;

//仓库,用于存放产品的
public class House {

    private List<Object> objects;

    public House(List<Object> objects) {
        this.objects = objects;
    }

    public List<Object> getObjects() {
        return objects;
    }

    public void setObjects(List<Object> objects) {
        this.objects = objects;
    }

}

Producer

package com.os467.wait;

/**
 * 需求:
 *      生产者线程在什么情况下不需要生产产品?
 *      当仓库中的产品满足一定数量之后,就不用再生产了
 *
 *      只要当仓库中有产品,就要交由消费者去消费,消费者消费完了,仓库中没产品了,就继续交由生产者去生产
 */
public class Producer extends Thread {

    //创建仓库的引用
    private House house;

    public Producer(String name,House house){

        super(name);
        this.house = house;

    }

    @Override
    public void run() {

        //模拟一直生产
        while (true) {

            synchronized (this.house) {

                if (this.house.getObjects().size() > 0) {

                    //生产者不能再生产,要让这个生产者处于等待状态
                    try {

                        //让处于该对象上活跃的线程处于等待状态
                        this.house.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }

                //如果仓库中没有产品,生产者要继续生产
                this.house.getObjects().add(new Object());

                System.out.println(Thread.currentThread().getName() + "生产了一个产品,产品实例为" + this.house.getObjects().get(0));

                try {
                    //模拟睡眠
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //唤醒此对象的消费者线程去消费
                this.house.notifyAll();

            }
        }

    }

}

WaitDemo

package com.os467.wait;

import java.util.ArrayList;

public class WaitDemo {

    public static void main(String[] args) {

        //创建仓库对象
        House house = new House(new ArrayList<>());

        //创建生产者线程
        Producer producer = new Producer("生产者",house);

        //创建消费者线程
        Consumer consumer = new Consumer("消费者",house);

        //启动线程
        producer.start();
        consumer.start();


    }

}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:多线程

字数:4.3k

本文作者:Os467

发布时间:2022-07-12, 12:29:58

最后更新:2022-09-05, 00:06:56

原始链接:https://os467.github.io/2022/07/12/%E5%A4%9A%E7%BA%BF%E7%A8%8B/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏