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

【day17】多线程基础

【day16】回顾

在进入多线程的世界之前,让我们快速回顾一下【day16】中的关键知识点:

  1. Math类:提供了基础的数学工具,包括abs, ceil, floor, round, max, min等方法。
  2. BigInteger类:用于处理超出long范围的超大整数,包括add, subtract, multiply, divide等方法。
  3. BigDecimal类:解决浮点数运算中的精度问题,提供add, subtract, multiply, divide, valueOf(double b)等方法。
  4. Date类:用于表示日期,包括getTime()setTime(long time)等方法。
  5. Calendar类:日历类,提供getInstance(), get(), set(), add(), getTime()等方法。
  6. SimpleDateFormat类:日期格式化类,提供formatparse方法。
  7. JDK8新日期类:包括LocalDateLocalDateTime,以及时间偏差计算的PeriodDuration类。
  8. DateTimeFormatter类:JDK8中新的日期格式化类。
  9. System类:提供系统级别的操作,如arrayCopy
  10. Arrays类:提供数组操作,如toString, binarySearch, sort, copyOf
  11. 包装类:基本数据类型对应的包装类,如Integer, Double, Boolean等。
  12. Integer类:提供valueOf(int i)intValue()方法,以及基本类型和字符串之间的转换。
  13. JavaBean:定义JavaBean时,基本类型的属性应改为包装类。

模块17重点

本模块将深入探讨多线程的相关概念和操作,包括:

  1. 掌握多线程的使用方法,主要是start()方法。
  2. 学习通过继承Thread类创建多线程。
  3. 学习通过实现Runnable接口实现多线程。
  4. 学习使用同步代码块解决线程不安全问题。
  5. 学习使用同步方法解决线程不安全问题。

第一章:多线程基本了解

1.多线程_线程和进程

进程是在内存中执行的应用程序,而线程是进程中最小的执行单元,负责当前进程中程序的运行。一个进程中至少有一个线程,多个线程的应用程序称为多线程程序。
在这里插入图片描述
在这里插入图片描述

2.并发和并行

并行是指在同一个时刻,多个指令在多个CPU上同时执行。并发是指在同一个时刻,多个指令在单个CPU上交替执行。
并行
并发
细节:

  1. 之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换
  2. 现在咱们的CPU都是多核多线程的了,比如2核4线程,那么CPU可以同时运行4个线程,此时不同切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在

3.CPU调度

CPU调度包括分时调度和抢占式调度,Java程序采用的是抢占式调度。
分时调度:值的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到了,哪个线程先执行,一般都是优先级高的先抢到CPU使用权的几率大,我们java程序就是抢占式调度

4.主线程介绍

主线程是CPU和内存之间专门为main方法服务的线程。
在这里插入图片描述

第二章:创建线程的方式(重点)

1.第一种方式_extends Thread

通过继承Thread类并重写run方法来创建线程,然后调用start方法开启线程。

public class Test01 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main线程..........执行了"+i);
        }
    }
}

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread...执行了"+i);
        }
    }
}

2.多线程在内存中的运行原理

在这里插入图片描述

注意:同一个线程对象不能连续调用多次start,如果想要再次调用start,则需要新建一个线程对象。

3.Thread类中的方法

Thread类中的方法包括start(), run(), getName(), setName(String name), currentThread(), sleep(long millis)等。

4.Thread中其他的方法

Thread类还提供了setPriority(int newPriority), getPriority(), setDaemon(boolean on), yield(), join()等方法。

4.1.线程优先级

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了......"+i);
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("金莲");

        MyThread1 t2 = new MyThread1();
        t2.setName("阿庆");

        /*
           获取两个线程的优先级
           MIN_PRIORITY = 1 最小优先级 1
           NORM_PRIORITY = 5 默认优先级 5
           MAX_PRIORITY = 10 最大优先级 10
         */
        //System.out.println(t1.getPriority());
        //System.out.println(t2.getPriority());

        //设置优先级
        t1.setPriority(1);
        t2.setPriority(10);

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

4.2.守护线程

public class Test01 {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("金莲");

        MyThread2 t2 = new MyThread2();
        t2.setName("阿庆");

        //将t2设置成守护线程
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了......"+i);
        }
    }
}
public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了..."+i);
        }
    }
}

在这里插入图片描述

4.3.礼让线程

场景说明: 如果两个线程一起执行,可能会执行一会儿线程A,再执行一会线程B,或者可能线程A执行完毕了,线程B在执行。那么我们能不能让两个线程尽可能的平衡一点 -> 尽量让两个线程交替执行

注意:只是尽可能的平衡,不是绝对的你来我往,有可能线程A线程执行,然后礼让了,但是回头A又抢到CPU使用权了

public class Test01 {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("金莲");

        MyThread1 t2 = new MyThread1();
        t2.setName("阿庆");


        t1.start();
        t2.start();
    }
}
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了......"+i);
            Thread.yield();
        }
    }
}

4.4.插入线程

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了......"+i);
        }
    }
}
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        MyThread1 t1 = new MyThread1();
        t1.setName("金莲");
        t1.start();

        /*
          表示把t1插入到当前线程之前,t1要插到main线程之前,所以当前线程就是main线程
         */
        t1.join();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了......"+i);
        }
    }
}

5.第二种方式_实现Runnable接口

通过实现Runnable接口并重写run方法来创建线程。利用Thread类的构造方法:Thread(Runnable target),创建Thread对象(线程对象),将自定义的类当参数传递到Thread构造中 -> 这一步是让我们自己定义的类成为一个真正的线程类对象

public class Test01 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        Thread t1 = new Thread(myRunnable);
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"...执行了"+i);
        }
    }
}

6.两种实现多线程的方式区别

继承Thread类的方式有继承的局限性,而实现Runnable接口的方式没有继承的局限性。

7.第三种方式_匿名内部类创建多线程

匿名内部类创建多线程实际上是基于实现Runnable接口的基础上完成的。

public class Test02 {
    public static void main(String[] args) {
        /*
          Thread(Runnable r)
          Thread(Runnable target, String name) :name指的是给线程设置名字
         */

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"...执行了"+i);
                }
            }
        },"阿庆").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"...执行了"+i);
                }
            }
        },"金莲").start();
    }
}

在这里插入图片描述

第三章:线程安全

1.线程安全问题–>线程不安全的代码

当多个线程访问同一个资源时,可能会导致数据问题,这种情况称为线程不安全。

public class MyTicket implements Runnable{
    int ticket = 100;

    @Override
    public void run() {
        while(true){
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                ticket--;
            }
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();

        Thread t1 = new Thread(myTicket, "赵四");
        Thread t2 = new Thread(myTicket, "刘能");
        Thread t3 = new Thread(myTicket, "广坤");

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

原因: CPU在多个线程之间做高速切换导致的
在这里插入图片描述

2.解决线程安全问题的第一种方式(使用同步代码块)

同步代码块可以解决线程不安全的问题。

public class MyTicket implements Runnable{
    //定义100张票
    int ticket = 100;

    //任意new一个对象
    Object obj = new Object();

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (obj){
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                    ticket--;
                }
            }

        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();

        Thread t1 = new Thread(myTicket, "赵四");
        Thread t2 = new Thread(myTicket, "刘能");
        Thread t3 = new Thread(myTicket, "广坤");

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

3.解决线程安全问题的第二种方式:同步方法

3.1.普通同步方法_非静态
public class MyTicket implements Runnable{
    //定义100张票
    int ticket = 100;

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
           //method01();
            method02();
        }
    }

   /* public synchronized void method01(){
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }*/
   public void method02(){
       synchronized(this){
           System.out.println(this+"..........");
           if (ticket>0){
               System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
               ticket--;
           }
       }

   }

}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        System.out.println(myTicket);

        Thread t1 = new Thread(myTicket, "赵四");
        Thread t2 = new Thread(myTicket, "刘能");
        Thread t3 = new Thread(myTicket, "广坤");

        t1.start();
        t2.start();
        t3.start();
    }
}
3.2.静态同步方法
public class MyTicket implements Runnable{
    //定义100张票
    static int ticket = 100;

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
           //method01();
            method02();
        }
    }

    /*public static synchronized void method01(){
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }*/
   public static void method02(){
       synchronized(MyTicket.class){
           if (ticket>0){
               System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
               ticket--;
           }
       }

   }
}
public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();

        Thread t1 = new Thread(myTicket, "赵四");
        Thread t2 = new Thread(myTicket, "刘能");
        Thread t3 = new Thread(myTicket, "广坤");

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

第四章:死锁(了解)

1.死锁介绍(锁嵌套就有可能产生死锁)

死锁是指两个或多个线程在执行过程中因为竞争同步锁而产生的阻塞现象,如果没有外力作用,它们将无法继续执行。
在这里插入图片描述

2.死锁的分析

在这里插入图片描述

3.代码实现

public class LockA {
    public static LockA lockA = new LockA();
}

public class LockB {
    public static LockB lockB = new LockB();
}
public class DieLock implements Runnable{
    private boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (LockA.lockA){
                System.out.println("if...lockA");
                synchronized (LockB.lockB){
                    System.out.println("if...lockB");
                }
            }
        }else{
            synchronized (LockB.lockB){
                System.out.println("else...lockB");
                synchronized (LockA.lockA){
                    System.out.println("else...lockA");
                }
            }
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(false);

        new Thread(dieLock1).start();
        new Thread(dieLock2).start();
    }
}

只需要知道死锁出现的原因即可(锁嵌套),以后尽量避免锁嵌套。

第五章:线程状态

1.线程状态介绍

线程在生命周期中有六种状态,包括NEW(新建)、Runnable(可运行)、Blocked(锁阻塞)、Waiting(无限等待)、Timed Waiting(计时等待)和Terminated(被终止)。

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Terminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()

2.线程状态图

在这里插入图片描述


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

相关文章:

  • 【更新】Docker新手入门教程2:在Windows系统通过compose创建多个mysql镜像并配置应用
  • .NET Framework 逐渐过时,.NET 8和 .NET 9引领未来
  • Flink调优----资源配置调优与状态及Checkpoint调优
  • 将Minio设置为Django的默认Storage(django-storages)
  • Visual Studio Code历史版本下载
  • 2024-12-24 NO1. XR Interaction ToolKit 环境配置
  • 【Java基础面试题046】Java中的注解原理是什么?
  • flink sink doris
  • 外包干了两年,技术退步明显...
  • LeetCode每日三題(三
  • shell学习简介(一)
  • 【三维重建】去除瞬态物体Distractor汇总
  • 【行空板K10】评测资料准备
  • 华为OD机试 密码截获(C/JAVA)
  • NNDL 作业11 LSTM
  • FFmpeg在python里推流被处理过的视频流
  • MyBatis如何处理延迟加载?
  • 三维扫描在汽车/航空行业应用
  • Java web的发展历史
  • C#中的委托机制:深入理解与应用
  • 基于earthSDK三维地图组件开发
  • vue.js 指令的修饰符
  • 16.2、网络安全风险评估技术与攻击
  • 解决Gradle下载很慢,运行及打包很慢
  • 在开发嵌入式系统时,尤其是处理大数时,会遇到取值范围的问题。51单片机通常没有内建大整数支持,因此我们需要采用不同的方法来解决这一问题
  • 【ELK】ES单节点升级为集群并开启https【亲测可用】