Java多线程 - 黑马教程

Java 多线程

一、多线程概述

  • 线程(Thread)是一个程序内部的一条执行流程
  • 程序如果有一条执行流程,就是单线程程序
  • 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由 CPU 负责调度执行)。

二、 多线程创建方式

Java 是通过 java.lang.Thread 类的对象代表多线程。主要方式有三种 :

  1. 继承 Thread 类,重写 run 方法

  2. 实现 Runnable 接口

  3. 实现 Callable 接口

1、继承 Thread 类创建线程

  1. 创建子类 MyThread 继承 Thread 线程类
  2. 重写 Thread 类的 run 方法
  3. 创建的 MyThread 线程类的对象代表一个线程
  4. 调用 start() 方法启动线程(自动执行 run 方法)

示例代码:

/**
 * 1、创建子类 MyThread 继承 Thread 线程类
 */
public class MyThread extends Thread{

//    2、重写 Thread 类的 run 方法
    @Override
    public void run() {
        super.run();

//        描述线程的执行任务
        for (int i = 1; i < 9; i++) {
            System.out.println("子线程 MyThread 输出:" + i);
        }
    }
}


public class ThreadTest1 {

    //    main 方法是由一条默认的主线程负责执行
    public static void main(String[] args) {
//        3、创建的 MyThread 线程类的对象代表一个线程
        Thread t = new MyThread();

//        4、启动线程(自动执行 run 方法)
        t.start(); // main线程 t线程

        for (int i = 1; i < 9; i++) {
            System.out.println("主线程 main 输出:" + i);
        }
    }
}

测试结果:
在这里插入图片描述

继承 Thread 类创建线程 优缺点:

  • 优点:编码简单
  • 缺点:继承了 Thread 类无法继承其他类,不利于功能扩展

多线程注意事项:

  1. 启动线程必须使用 start 方法,不是 run 方法

如果调用 run 方法,那么 Thread t = new MyThread() 就会把 t 仅仅当做一个 Java 对象而不是一个线程,此时就只有一个 main 线程,程序会先执行 run 方法然后执行下面的代码;

start 方法是向 CPU 注册,告诉 CPU Thread t = new MyThread() 是一个单独的执行流程。

  1. 不要把主线程任务放在启动子线程之前,否则永远先执行主线程再执行子线程

2、实现 Runnable 接口

  1. 定义任务类,实现 Runnable 接口
  2. 重写 run 方法
  3. 创建任务对象
  4. 把任务对象交给线程对象处理

代码示例:

/**
 * 1、定义任务类,实现 Runnable 接口
 */
public class MyRunnable implements Runnable {

    //    2、重写 run 方法
    @Override
    public void run() {

        for (int i = 1; i < 9; i++) {
            System.out.println("子线程输出=== " + i);
        }

    }
}


public static void main(String[] args) {


//        3、创建任务对象
        Runnable target = new MyRunnable();

//         4、把任务对象交给线程对象处理
//        public Thread(Runnable target)
        new Thread(target).start();

        for (int i = 1; i < 9; i++) {
            System.out.println("主线程 main 输出:" + i);
        }
    }

测试结果:

在这里插入图片描述

实现 Runnable 接口的匿名内部类写法

public class ThreadTest2_2 {
    public static void main(String[] args) {

//        直接创建 Runnable 接口的匿名内部类(任务对象)
        Runnable target = new Runnable() {
            @Override
            public void run() {

                for (int i = 1; i < 9; i++) {
                    System.out.println("子线程输出=== " + i);
                }

            }
        };

        new Thread(target).start();

//        简化形式1
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i < 9; i++) {
                    System.out.println("子线程2输出=== " + i);
                }
            }
        }).start();

        //        简化形式2
        new Thread(() -> {

            for (int i = 1; i < 9; i++) {
                System.out.println("子线程3输出=== " + i);
            }

        }).start();


        for (int i = 1; i < 9; i++) {
            System.out.println("主线程输出:" + i);
        }

    }
}

3、实现 Callable 接口

假如线程执行完毕后需要一些数据的返回,前面两种方式重写的 run 方法均不能返回结果。

此时可以通过 Callable 接口和 FutureTask 类来实现。

通过源码可以看出来,@FunctionalInterface 表示他是一个函数式接口,同时定义了泛型与 call 方法泛型一致。如果在实现 Callable 的时候定义了泛型则重写 call 方法的返回类型返回类型固定,否则返回 Object
在这里插入图片描述
代码示例:

import java.util.concurrent.Callable;

/**
 * 1、定义任务类,实现 Callable 接口
 */
public class MyCallable implements Callable<String> {

    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {

//        描述线程任务,返回线程执行完毕后返回的结果
//        需求:求 1-n 的和

        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }

        return "线程求出了1-" + n + "的和是:" + sum;
    }
}


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadTest3 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 3、创建一个 Callable 对象
        Callable<String> call = new MyCallable(100);

        // 4、把 Callable 的对象封装成一个 FutureTask 对象(任务对象)
//      未来任务对象作用?
//        ①是一个任务对象,实现了 Runnable 对象
//        ②可以在线程执行完毕后,用未来任务对象调用 get 方法获取线程执行完毕后的结果
        FutureTask<String> f1 = new FutureTask<>(call);

//        5、把任务对象交给 Thread 对象
        new Thread(f1).start();

//        6、获取线程执行完毕后的结果
//        注意:如果执行到这,假如上面的代码没有执行完毕
//        这里的代码会暂停,等待上面线程执行完毕后才会继续往下走
        String s = f1.get();
        System.out.println(s);

    }
}

三、Thread 常用的方法

在这里插入图片描述
示例代码1:

public class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        super.run();

        Thread t = Thread.currentThread();
        for (int i = 1; i <= 3; i++) {
            System.out.println(t.getName() + "输出:" + i);
        }
    }
}


public class ThreadTest1 {

    public static void main(String[] args) {

        Thread t1 = new MyThread("1号线程");
        t1.start();

        Thread t2 = new MyThread("2号线程");
        t2.start();

        Thread t = Thread.currentThread();
        for (int i = 1; i <= 5; i++) {
            System.out.println(t.getName() + "输出:" + i);
        }
    }
}

示例代码2:sleep 方法

public class ThreadTest4 {

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

        for (int i = 1; i <= 5; i++) {
            System.out.println(i);

//            当 i 等于3时,休眠5秒
            if (i == 3) {
//                会让当前执行的线程暂停5秒,再继续执行
//                作用就是让系统运行的慢一些
                Thread.sleep(5000);
            }
        }

    }
}

示例代码3:join 方法

public class ThreadTest5 {

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

//        join 方法作用:让当前调用这个方法的线程先执行
        Thread t1 = new MyThread("1号线程");
        t1.start();
        t1.join();

        Thread t2 = new MyThread("2号线程");
        t2.start();
        t2.join();

        Thread t3 = new MyThread("3号线程");
        t3.start();
        t3.join();
    }
}

测试结果:

在这里插入图片描述

四、线程安全

什么是线程安全问题?

线程安全:多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

通俗来说,比如火车站有100张车票,同时开放三个售票窗口,如果三个窗口同时卖完车票后系统内还剩97张车票,就是线程安全的,否则就是线程不安全。

线程安全问题出现的原因

  • 存在多个线程同时执行
  • 同时访问一个共享资源
  • 存在修改共享资源

程序模拟线程安全

取钱案例

需求:

  • 小明和小红是一对夫妻,他们有一个共同账户,余额是10万元,模拟2人同时去取钱10万元

分析:

  1. 需要提供一个账户类,接着创建一个账户对象代表2个人的共享账户
  2. 定义一个线程类(用于创建两个线程,分别代表小红和小明)
  3. 创建2个线程,传入同一个账户对象给2个线程处理
  4. 启动2个线程,同时去同一个账户对象中取钱10万

账户类:

public class Account {

    private double money; // 金额

    //    小明 小红同时过来
    public void drawMoney(double drawMoney) {

//        先弄清楚谁来取钱
        String name = Thread.currentThread().getName();

//        判断余额是否足够
        if (this.money >= drawMoney) {
            System.out.println(name + "来取钱" + drawMoney + "成功!");
            this.money -= drawMoney;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        } else {
            System.out.println(name + "来取钱,余额不足");
        }
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public Account() {
    }

    public Account(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "money=" + money +
                '}';
    }
}

线程类:

public class DrawMoneyThread extends Thread {

    private Account acc;

    public DrawMoneyThread(String name, Account acc) {
        super(name);
        this.acc = acc;

    }

    @Override
    public void run() {
        super.run();
//        取钱(小明/小红)
        acc.drawMoney(100000);

    }
}

执行测试:

public class ThreadTest6 {

    public static void main(String[] args) {

//        创建一个账户,代表两个人的共享账户
        Account acc = new Account(100000);

//        创建两个线程,分别代表小明和小红,再去同一个账户对象中取10万
        new DrawMoneyThread("小明", acc).start(); // 小明
        new DrawMoneyThread("小红", acc).start(); // 小红

    }
}

测试结果:

在这里插入图片描述

五、线程同步

线程同步是解决线程安全问题的方案。

线程同步思想

  • 让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

线程同步的常见方案

  • 加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。
    在这里插入图片描述

线程同步方式1:同步代码块

  • 作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

代码形式:

synchronized(同步锁) {
       // 访问共享资源的核心代码
    }
  • 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可进来执行。

同步锁的注意事项:

  • 对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出 bug

代码示例:

根据模拟线程安全的代码示例,首先需要找到访问共享资源的核心代码,根据业务逻辑可知,核心代码如下:

在这里插入图片描述
因此需要将这段代码加上锁,选中代码然后 IDEA 快捷键 CTRL + ALT + T 选 synchronized,如下:
在这里插入图片描述
注意,括号内的 同步加锁 是一个静态变量,表明惟一的一个 Java 对象,说明小明和小红访问的是同一个锁对象

执行结果:

在这里插入图片描述

特别注意!!!

在这里插入图片描述
由上面代码可以看出,我们定义的锁对象是一个静态字符串,它在系统中永远只有一份,表示唯一锁对象。无论有多少个线程,都只有这一个锁对象锁住线程。

但是如果现在有两个账户(如下图片所示代码),四个线程(多了小黑和小白),当小明线程进来的时候,锁对象会锁住小红、小黑、小白三个线程,但事实是只需要锁住小红的线程就可以了。

在这里插入图片描述

所以最佳的方案是小红小明一个锁对象,小黑小白一个锁对象,这个时候,锁对象的定义最好用 this,如下:

在这里插入图片描述

特别地:多个线程调用静态方法

在这里插入图片描述

锁对象随便选择一个惟一的对象好不好呢?

  • 不好,会影响其他无关线程的执行。

锁对象的使用规范?

  • 使用共享资源作为锁对象,对于实例方法建议使用 this 作为锁对象
  • 静态方法使用 类名.Class 对象作为锁对象

思考:

  • 同步代码块是如何实现线程安全?
  • 同步代码块的同步锁对象有什么要求?

线程同步方式2:同步方法

同步方法:

  • 作用:把访问共享资源的核心方法给上锁,保证线程安全
  • 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行

代码示例:

 修饰符 synchronized 返回值类型 方法名(形参列表) {
// 操作共享资源的代码
}

仍然以上面的示例为例,在方法上加锁:

在这里插入图片描述

同步方法的底层原理:

  • 同步方法底层也是有隐式锁对象,只是锁的范围是整个代码块
  • 如果方法是实例方法,默认是 this 作为锁对象
  • 如果方法是静态方法,默认 类名.Class 作为锁对象

是同步方法块好还是同步方法好一些?

  • 范围上:同步代码块锁的范围更小,同步方法锁的范围更大
  • 可读性:同步方法更好

思考:

  • 同步方法是如何保证线程安全的?
  • 同步方法的同步锁对象的原理?

线程同步方式3:Lock 锁

Lock 锁

  • Lock 锁:通过它创建出锁对象进行加锁和解锁
  • Lock 是接口,不能直接实例化,可以采用他的实现类 ReentrantLock 构建 lock 锁对象

代码示例:

  1. 在账户类中创建一个锁对象
  2. 在共享资源处加锁
  3. 逻辑执行完毕后解锁
package com.xiancheng;

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

public class Account {

    private double money; // 金额

    // 在账户类创建一个 Lock 对象
    private final Lock lk = new ReentrantLock();

    //    小明 小红同时过来
    public void drawMoney(double drawMoney) {

//        先弄清楚谁来取钱
        String name = Thread.currentThread().getName();

        lk.lock();  // 加锁
//        判断余额是否足够
        if (this.money >= drawMoney) {
            System.out.println(name + "来取钱" + drawMoney + "成功!");
            this.money -= drawMoney;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        } else {
            System.out.println(name + "来取钱,余额不足");
        }
        lk.unlock();  // 解锁
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public Account() {
    }

    public Account(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "money=" + money +
                '}';
    }
}

注意事项:

  1. 创建的锁对象要用 final 来修饰
  2. 解锁逻辑放在 finally 处

原因:加了锁之后一定要解锁,否则中间的逻辑代码出现异常之后永远不能解锁,这样下一个线程就不能进来。所以,逻辑处理的代码最好放在 try catch 里处,如下:

在这里插入图片描述

六、线程池

什么是线程池?

  • 线程池就是一个可以复用线程的技术

创建线程池

谁代表线程池?

  • 线程池接口:ExecutorService

如何得到线程池对象?

  • 使用 ExecutorService 的实现类 ThreadPoolExecutor 自创建一个线程池对象
  • 使用 Executors (线程池工具类) 调用方法返回不同特点的线程池对象
ThreadPoolExecutor 构造器
public ThreadPoolExecutor(int corePoolSize, 
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

参数意义(面试常问):

  • corePoolSize:指定线程池的核心线程数量。比如传入3,表示有3个核心线程可以长期复用的
  • maximumPoolSize:指定线程池的最大线程数量。(最大线程数是要大于核心线程数的)。比如:核心线程是3,最大线程是5,则表示有两个临时线程。
  • keepAliveTime:指定临时线程的存活时间。
  • unit:临时线程存活的时间单位(秒、分、时、天)。
  • workQueue:指定线程池的任务队列。
  • threadFactory:指定线程池的线程工厂。用来创建线程的。
  • handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处置)

线程池创建的注意事项(面试常问)

1、临时线程什么时候创建?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程

2、什么时候开始拒绝新任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来时才会开始拒绝任务

线程池处理 Runnable 任务

在这里插入图片描述

代码示例

/**
 * 创建 Runnable 任务类
 */
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        // 任务干啥?
        System.out.println(Thread.currentThread().getName() + " ===> 输出666");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



import java.util.concurrent.*;

public class ThreadPoolTest1 {

    public static void main(String[] args) {

        // 通过 ThreadPoolExecutor 创建一个线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Runnable target = new MyRunnable();
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行

        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程

//        pool.shutdown(); // 等着线程池任务全部执行完毕,再关闭线程池
//        pool.shutdownNow(); // 立即关闭线程池,不管任务是否执行完毕
    }
}

测试结果:

在这里插入图片描述

如上代码所示,创建了一个:核心线程是3,最大线程5,任务队列4 的线程池。打印结果可以看出有3个核心线程池并且被复用。

但是现在临时线程并没有被创建,只有当核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

修改代码,睡眠时间调大,同时增加任务队列,代码如下:

/**
 * 创建 Runnable 任务类
 */
public class MyRunnable implements Runnable {

    @Override
    public void run() {
        // 任务干啥?
        System.out.println(Thread.currentThread().getName() + " ===> 输出666");
        try {
        // 睡眠时间调大
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



public class ThreadPoolTest1 {

    public static void main(String[] args) {

        // 通过 ThreadPoolExecutor 创建一个线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Runnable target = new MyRunnable();

        // 3个核心线程
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行

        // 4个任务队列
        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程

        // 增加一个任务,此时核心线程都在忙,任务队列也满了
        pool.execute(target); 

    }
}

测试结果:

如下图所示,新创建了一个线程 pool-1-thread-4
在这里插入图片描述

关于 new ThreadPoolExecutor.AbortPolicy():任务的拒绝策略

如果3个核心线程在忙,两个临时线程也在忙,4个任务队列占满了,这个时候就会发起任务拒绝。

代码示例:

public class ThreadPoolTest1 {

    public static void main(String[] args) {

        // 通过 ThreadPoolExecutor 创建一个线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        Runnable target = new MyRunnable();

        // 3个核心线程
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行
        pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行

        // 4个任务队列
        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程
        pool.execute(target); // 复用前面的核心线程

        // 增加一个任务,此时核心线程都在忙,任务队列也满了
        pool.execute(target);
        pool.execute(target);

        // 3个核心线程在忙,两个临时线程也在忙,4个任务队列占满了,这个时候就会发起任务拒绝
        pool.execute(target);

    }
}

测试结果:

会抛出异常
在这里插入图片描述

常用的任务拒绝策略:
在这里插入图片描述

线程池处理 Callable 接口

  • 通过 Future submit(Callable task) 执行 Callable 接口

代码示例:

import java.util.concurrent.Callable;

/**
 * 1、定义任务类,实现 Callable 接口
 */
public class MyCallable implements Callable<String> {

    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {

//        描述线程任务,返回线程执行完毕后返回的结果
//        需求:求 1-n 的和

        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }

        return Thread.currentThread().getName() + "线程求出了1-" + n + "的和是:" + sum;
    }
}



import java.util.concurrent.*;

public class ThreadPoolTest2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {


        // 通过 ThreadPoolExecutor 创建一个线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());


        Future<String> f1 = pool.submit(new MyCallable(5));
        Future<String> f2 = pool.submit(new MyCallable(10));
        Future<String> f3 = pool.submit(new MyCallable(15));
        Future<String> f4 = pool.submit(new MyCallable(20));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());

    }
}

测试结果:

在这里插入图片描述

七、使用 Executors 工具类得到线程池

Executors

  • 线程池工具类,提供了很多静态方法用于返回不同特点的线程池对象
    在这里插入图片描述
    注意:以上方法的底层,都是通过线程池的实现类 ThreadPoolExecutor 创建的线程池对象。

代码示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest3 {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(3);
        ExecutorService pool1 = Executors.newSingleThreadExecutor();
        ExecutorService pool2 = Executors.newCachedThreadPool();
        ExecutorService pool3 = Executors.newScheduledThreadPool(3);
        
    }
}

问题:核心线程数量到底要配置多少?

  • 计算密集型的任务:核心线程数量 = CPU 的核数 + 1
  • IO 密集型的任务:核心线程数量 = CPU 的核数 * 2

CPU 核数:任务管理器中的逻辑处理器

在这里插入图片描述

特别重要的注意事项!!!!

Executors 使用可能存在的陷阱

  • 大型并发系统中使用 Executors 如果不注意可能会存在系统风险。

在这里插入图片描述

八、并发和并行

进程

  • 正在运行的程序(软件)就是一个独立的进程。
  • 线程属于进程,一个进程中可以同时运行多个线程。
  • 进程中的多个线程是并发和并行执行的。

如下:任务管理器中, IntelliJ IDEA 就是一个进程,这个进程中又同时运行5个线程
在这里插入图片描述

并发的含义

  • 同一时间间隔内执行以及调度多个程序的能力。其实就是CPU的快速切换能力。

进程中的线程是 CPU 负责调度执行,但是 CPU 能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU 会轮询为系统的每个线程服务,由于 CPU 切换的速度很快,给我们的感觉就是这些线程同时执行,这就是并发。

如下图所示,有3个进程,Google Chrome、IntelliJ IDEA、Microsoft Edge,同时这三个进程又有很多线程,这个时候 CPU 就会轮番的处理每一个线程,处理的速度特别快,感觉这些软件的功能都在同时执行,这个就是并发。
在这里插入图片描述

并行的含义

  • 在同一个时刻上,同时有多个线程在被 CPU 调度。

比如这台电脑,内核是4,逻辑处理器是8,是一个双核 CPU,表示 CPU 能同时处理8个线程
在这里插入图片描述
多线程到底是如何执行?

并发个并行同时执行的!!!

九、线程的生命周期

什么是线程生命周期?

  • 线程从生到死的过程中,经历的各种状态及状态转换。

Java 线程状态

  • Java 总共定义了6种状态
  • 6种状态定义在 Thread 类内部枚举类中

代码如下:

 public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

线程的6种状态及互相转换

在这里插入图片描述

线程的6种状态总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YD_1989

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

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

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

打赏作者

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

抵扣说明:

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

余额充值