当前位置: 首页 > article >正文

2-Java进阶知识总结-6-多线程

文章目录

  • 多线程--基本概念
    • 并发和并行
    • 进程和线程
    • 多线程
  • 多线程--实现
    • 方式一,继承Thread类
      • 方法介绍
      • 实现步骤
      • 注意事项
    • 方式二,实现Runnable接口
      • Thread构造方法
      • 实现步骤
    • 方式三,实现Callable接口
      • 方法介绍
      • 实现步骤
    • 三种多线程实现方法对比
  • 多线程--线程方法
    • 设置和获取线程名称
    • 线程休眠
    • 线程优先级
    • 守护线程
    • 礼让线程和插入线程
    • 线程生命周期
  • 多线程--线程同步
    • 示例:卖票
    • 问题发现
    • 问题解决1:同步代码块
    • 问题解决2:同步方法
    • 问题解决3:线程锁Lock
  • 多线程应用--死锁【非概念,是一种BUG现象】
  • 多线程应用--生产者消费者模式示例
    • 等待唤醒机制
    • 阻塞队列实现等待唤醒机制
  • 多线程的6种状态
  • 线程池

多线程–基本概念

并发和并行

  • 并行:在同一时刻,有多个指令在多个CPU上同时执行

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行

进程和线程

进程:是正在运行的程序

  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
  • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
  • 并发性:任何进程都可以同其他进程一起并发执行

线程:是进程中的单个顺序控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径,则称为单线程程序

  • 多线程:一个进程如果有多条执行路径,则称为多线程程序

image-20231023165600023

  • 360安全卫士是一个进程;QQ是另外一个进程
  • 木马查杀、电脑清理、系统修复是不同的线程

多线程

指从软件或者硬件上实现多个线程并发执行的技术

  • 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能

多线程–实现

方式一,继承Thread类

方法介绍

方法名说明
void run()在线程开启后,此方法将被调用执行
void start()使此线程开始执行,Java虚拟机会调用run方法()

实现步骤

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

//        my1.run();
//        my2.run();

        //void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

注意事项

为什么要重写run()方法?

  • 因为run()是用来封装被线程执行的代码

run()方法和start()方法的区别?

  • run():封装线程执行的代码,直接调用,相当于普通方法的调用

  • start():启动线程;然后由JVM调用此线程的run()方法

方式二,实现Runnable接口

Thread构造方法

方法名说明
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target, String name)分配一个新的Thread对象

实现步骤

  • 定义一个类MyRunnable实现Runnable接口
  • 在MyRunnable类中重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  • 启动线程
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
		
        // Thread(Runnable target)
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"坦克");
        Thread t2 = new Thread(my,"飞机");

        //启动线程
        t1.start();
        t2.start();
    }
}

方式三,实现Callable接口

方法介绍

方法名说明
V call()计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()如有必要,等待计算完成,然后获取其结果

实现步骤

  • 定义一个类MyCallable实现Callable接口
  • 在MyCallable类中重写call()方法
  • 创建MyCallable类的对象
  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数
  • 启动线程
  • 再调用get方法,就可以获取线程结束之后的结果。
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();

        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);

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

        String s = ft.get();
        //开启线程
        t1.start();

        //String s = ft.get();
        System.out.println(s);
    }
}

三种多线程实现方法对比

优点缺点
实现Runnable、Callable接口扩展性强,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法
继承Thread类编程比较简单,可以直接使用Thread类中的方法可扩展性较差,不能再继承其他的类

多线程–线程方法

设置和获取线程名称

方法名说明
void setName(String name)将此线程的名称更改为等于参数name
String getName()返回此线程的名称
Thread currentThread()返回对当前正在执行的线程对象的引用
public class MyThread extends Thread {
    public MyThread() {}
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

        //void setName(String name):将此线程的名称更改为等于参数 name
        my1.setName("高铁");
        my2.setName("飞机");

        //Thread(String name)
        MyThread my1 = new MyThread("高铁");
        MyThread my2 = new MyThread("飞机");

        my1.start();
        my2.start();

        //static Thread currentThread() 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());
    }
}

线程休眠

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        /*System.out.println("睡觉前");
        Thread.sleep(3000);
        System.out.println("睡醒了");*/

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

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

线程优先级

线程调度方式

  • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
    • 随机性

Java使用的是抢占式调度模型

优先级相关方法

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return "线程执行完毕了";
    }
}
public class Demo {
    public static void main(String[] args) {
        //优先级: 1 - 10 默认值:5
        MyCallable mc = new MyCallable();

        FutureTask<String> ft = new FutureTask<>(mc);

        Thread t1 = new Thread(ft);
        t1.setName("飞机");
        t1.setPriority(10);
        //System.out.println(t1.getPriority());//5
        t1.start();

        MyCallable mc2 = new MyCallable();

        FutureTask<String> ft2 = new FutureTask<>(mc2);

        Thread t2 = new Thread(ft2);
        t2.setName("坦克");
        t2.setPriority(1);
        //System.out.println(t2.getPriority());//5
        t2.start();
    }
}

守护线程

当非守护线程结束之后,守护线程也会相继结束

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

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

礼让线程和插入线程

方法名说明
public static void yield()出让线程,当前线程让出cpu
public static void join()插入线程,把一个线程插入到当前线程前面,该线程执行完毕,才会执行当前线程后面的代码
package com.itheima.a07threadmethod4;

public class MyThread extends Thread{

    @Override
    public void run() {//"飞机"  "坦克"
        for (int i = 1; i <= 100; i++) {

            System.out.println(getName() + "@" + i);
            //表示出让当前CPU的执行权
            Thread.yield();
        }
    }
}
package com.itheima.a08threadmethod5;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {

        MyThread t = new MyThread();
        t.setName("土豆");
        t.start();

        //表示把t这个线程,插入到当前线程之前。
        //t:土豆
        //当前线程: main线程
        t.join();

        //执行在main线程当中的
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}

线程生命周期

image-20231023184503271

多线程–线程同步

示例:卖票

案例需求

  • 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

实现步骤

  • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

  • 在SellTicket类中重写run()方法实现卖票,代码步骤如下

  • 判断票数大于0,就卖票,并告知是哪个窗口卖的

  • 卖了票之后,总票数要减1

  • 票卖没了,线程停止

  • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

  • 创建SellTicket类的对象

  • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

  • 启动线程

public class SellTicket implements Runnable {
    private int tickets = 100;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            if(ticket <= 0){
                    //卖完了
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        //创建SellTicket类的对象
        SellTicket st = new SellTicket();

        //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

问题发现

卖票出现了问题

  • 相同的票出现了多次

  • 出现了负数的票

问题产生原因

  • 线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

安全问题出现的条件

  • 是多线程环境

  • 有共享数据

  • 有多条语句操作共享数据

如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境

怎么实现呢?

  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

  • Java提供了同步代码块的方式来解决

问题解决1:同步代码块

synchronized(任意对象) { 
	多条语句操作共享数据的代码 
}

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题

  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
                //t1进来后,就会把这段代码给锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

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

问题解决2:同步方法

非静态同步方法

修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}
  • 锁对象:this

静态同步方法

修饰符 static synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}
  • 锁对象:类名.class
public class MyRunnable implements Runnable {
    private static int ticketCount = 100;

    @Override
    public void run() {
        while(true){
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean result = synchronizedMthod();
                if(result){
                    break;
                }
            }

            if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized (MyRunnable.class){
                    if(ticketCount == 0){
                       break;
                    }else{
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                    }
                }
            }

        }
    }

    private static synchronized boolean synchronizedMthod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
            return false;
        }
    }
}

问题解决3:线程锁Lock

方法名说明
ReentrantLock()创建一个ReentrantLock的实例
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
方法名说明
void lock()获得锁
void unlock()释放锁
public class Ticket implements Runnable {
      //票的数量
      private int ticket = 100;
      private Object obj = new Object();
      private ReentrantLock lock = new ReentrantLock();  // 定义为静态变量

      @Override
      public void run() {
          while (true) {
              //synchronized (obj){//多个线程必须使用同一把锁.
              try {
                  lock.lock();
                  if (ticket <= 0) {
                      //卖完了
                      break;
                  } else {
                      Thread.sleep(100);
                      ticket--;
                      System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                  }
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } finally {
                  lock.unlock();
              }
              // }
          }
      }
  }


public class Demo {
  public static void main(String[] args) {
      Ticket ticket = new Ticket();

      Thread t1 = new Thread(ticket);
      Thread t2 = new Thread(ticket);
      Thread t3 = new Thread(ticket);

      t1.setName("窗口一");
      t2.setName("窗口二");
      t3.setName("窗口三");

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

多线程应用–死锁【非概念,是一种BUG现象】

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

什么情况下会产生死锁?

  • 资源有限

  • 同步嵌套

package com.itheima.a12deadlock;


public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程A");
        t2.setName("线程B");

        t1.start();
        t2.start();

    }
}


package com.itheima.a12deadlock;

public class MyThread extends Thread {

    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        //1.循环
        while (true) {
            if ("线程A".equals(getName())) {
                synchronized (objA) {
                    System.out.println("线程A拿到了A锁,准备拿B锁");//A
                    synchronized (objB) {
                        System.out.println("线程A拿到了B锁,顺利执行完一轮");
                    }
                }
            } else if ("线程B".equals(getName())) {
                if ("线程B".equals(getName())) {
                    synchronized (objB) {
                        System.out.println("线程B拿到了B锁,准备拿A锁");//B
                        synchronized (objA) {
                            System.out.println("线程B拿到了A锁,顺利执行完一轮");
                        }
                    }
                }
            }
        }
    }
}

多线程应用–生产者消费者模式示例

等待唤醒机制

阻塞队列实现等待唤醒机制

多线程的6种状态

没有运行状态

  • 运行状态只是方便理解

image-20231023195203688

线程状态具体含义
新建状态 NEW一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
就绪状态 RUNNABLE当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
阻塞状态 BLOCKED当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
等待状态 WAITING一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
计时状态 TIMED_WAITING一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
结束状态 TERMINATED一个完全运行完成的线程的状态。也称之为终止状态、结束状态

线程池

暂空


http://www.kler.cn/a/108122.html

相关文章:

  • power bi中的related函数解析
  • 如何查看电脑关机时间
  • 【vue2.0入门】vue单文件组件
  • Vue常用加密方式
  • 基于matlab的CNN食物识别分类系统,matlab深度学习分类,训练+数据集+界面
  • 【webrtc】 RTP 中的 MID(Media Stream Identifier)
  • shell脚本的编写(输入、输出、变量、数组等的使用规范及实例)
  • 【鸿蒙软件开发】ArkTS基础组件之TextClock(时间显示文本)、TextPicker(滑动选择文本)
  • 手写RPC框架
  • 网络安全(黑客)—小白自学
  • JavaScript的字符串介绍
  • Python 编写 Flink 应用程序经验记录(Flink1.17.1)
  • LuatOS-SOC接口文档(air780E)--max30102 - 心率模块
  • Geeker-Admin中ProTable表格分页之自定义接口页码、尺寸参数
  • MySQL -- 表的约束
  • Python通过ldap3操作LDAP账号
  • Java零基础入门-位运算符
  • How to install mongodb-7.0 as systemd service with podman
  • JVM面试知识点整理
  • PG数据库基本使用
  • 从Mysql架构看一条查询sql的执行过程
  • use renv with this project create a git repository
  • 分类预测 | MATLAB实现SSA-CNN-BiGRU麻雀算法优化卷积双向门控循环单元数据分类预测
  • 【软件安装环境配置】vscode 安装界面没有出现安装路径的选择 的解决,以及vscode的删除的问题
  • 【算法|动态规划No.32 | 完全背包问题】完全背包模板题
  • DAY05 循环嵌套+函数的笔记整理