Java基础之【多线程】

目录

基本概念

进程

线程

多线程

并发

并行

多线程的实现方式

继承Thread类

代码示例

执行结果

实现Runnable接口

代码示例

执行结果

利用FutureTask和Callable接口

代码示例

执行结果

线程的优先级

查看源码

测试案例

线程的生命周期

新建

就绪

运行

阻塞等待

死亡

线程安全问题

卖票案例

需求

代码实现

测试结果发现存在数据不一致问题

解决线程安全问题

synchronized

Lock

原子类

线程的状态

概述

查看源码


基本概念

进程

每一个正在运行着的程序都是一个进程

线程

一个进程中可以包含多个线程,可以将线程理解为应用程序中相互独立执行的功能,是操作系统能够运算调度的最小单位

多线程

应用程序中相互独立执行的功能比较多,就形成了多线程,可以让程序同时做多件事情,合理的线程数可以提高效率

并发

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

并行

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

多线程的实现方式

在Java中,有如下几种常见的实现多线程的方式

继承Thread类

代码示例

java 复制代码
package thread;

/**
 * 1、继承Thread类并重写run方法
 * 2、创建线程对象
 * 3、启动线程
 */
public class ExtendsThread {
    public static void main(String[] args) {
        // 创建线程对象
        ChildThread t1 = new ChildThread();
        ChildThread t2 = new ChildThread();
        // 设置线程名字
        t1.setName("线程1");
        t2.setName("线程2");
        // 启动线程
        t1.start();
        t2.start();
    }
}

class ChildThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // getName():获取当前线程名字
            System.out.println(getName() + "-" + i);
        }
    }
}

执行结果

如下图,能看到线程的交替执行

实现Runnable接口

代码示例

java 复制代码
package thread;

public class ImplementsRunnable {
    public static void main(String[] args) {
        // 创建任务对象
        Task task = new Task();
        // 创建线程对象,执行任务
        Thread t1 = new Thread(task, "线程1");
        Thread t2 = new Thread(task, "线程2");
        // 开启线程
        t1.start();
        t2.start();
    }
}

/**
 * 自定义类实现Runnable接口并将要执行的任务放入run方法中
 */
class Task implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // Thread.currentThread().getName():获取当前线程名字
            System.out.println(Thread.currentThread().getName() + "-" + i);
        }
    }
}

执行结果

同样能看到线程交替执行的现象

利用FutureTask和Callable接口

Callable接口有返回值,通过该实现方式可以获取到执行结果

代码示例

java 复制代码
package thread;

import java.util.concurrent.FutureTask;

public class CallableFuture {
    public static void main(String[] args) throws Exception {
        // 任务1
        FutureTask<String> task1 = new FutureTask<>(() -> {
            // 执行任务1需要耗时5s
            Thread.sleep(5000);
            return "任务1执行结束...";
        });
        // 任务2
        FutureTask<String> task2 = new FutureTask<>(() -> {
            // 执行任务2需要耗时3s
            Thread.sleep(3000);
            return "任务2执行结束...";
        });
        // 创建线程对象
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        long beginTime = System.currentTimeMillis();
        // 开启线程执行任务
        t1.start();
        t2.start();
        // 两个任务都执行完
        System.out.println(task2.get());
        System.out.println(task1.get());
        long endTime = System.currentTimeMillis();
        // 查看两个任务执行完后耗时情况
        System.out.println("任务总耗时:" + ((endTime - beginTime) / 1000) + "s");
    }
}

执行结果

如下图所示,执行任务1需要5s,执行任务2需要3s,执行完任务1和任务2总耗时为5s

线程的优先级

在计算机中,线程的调度分为两种,抢占式调度(多个线程在抢夺CPU的执行权)和非抢占式调度(所有的线程轮流执行),Java中采取的是抢占式调度,体现出一种随机性,线程的优先级就和这个随机性有关,线程的优先级越高,抢占到CPU的执行权的几率越大

查看源码

在Java中,线程的优先级分为1~10,如果不设置优先级的话,线程默认优先级为5

java 复制代码
package java.lang;
// ...

public
class Thread implements Runnable {
    // ...

    // 最小优先级
    public final static int MIN_PRIORITY = 1;

    // 默认优先级
    public final static int NORM_PRIORITY = 5;

    // 最大优先级
    public final static int MAX_PRIORITY = 10;


    // 设置优先级
    public final void setPriority(int newPriority) {
        // ...
    }

    // 获取优先级
    public final int getPriority() {
        return priority;
    }

    // ...
}

测试案例

java 复制代码
package thread;

public class ThreadPriority {
    public static void main(String[] args) {
        // 当前线程是main线程,默认优先级为5
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());
        System.out.println(currentThread.getPriority());

        // 创建一个线程,未设置其优先级,默认优先级为5
        Thread t1 = new Thread(() -> {
        });
        System.out.println(t1.getPriority());

        // 创建一个线程,设置其优先级
        Thread t2 = new Thread(() -> {
        });
        t2.setPriority(1);
        System.out.println(t2.getPriority());
    }
}

线程的生命周期

新建

创建了线程对象

就绪

  • 新创建的线程对象执行了start方法,有执行资格但是没有执行权,不停地抢CPU
  • 被其他线程抢走CPU的执行权
  • sleep方法时间到或者其他阻塞方式结束

运行

抢到了CPU的执行权,有执行资格运行run方法中的代码

阻塞等待

执行了线程的sleep方法或者其他阻塞式方法

死亡

run方法执行完毕,线程死亡变成垃圾

线程安全问题

多个线程同时操作共享数据时,发生数据不一致的现象叫做线程安全问题

卖票案例

需求

使用3个售票窗口同时卖票,一共有500张票

代码实现

java 复制代码
package thread;

public class SellTicketTask implements Runnable {
    // 当前票的编号,从1开始
    private int currTicketNo = 1;

    // 最大票的编号为500
    private static final int MAX_TICKET_NO = 500;

    @Override
    public void run() {
        while (true) {
            // 票卖完了
            if (currTicketNo > MAX_TICKET_NO) {
                break;
            }
            // 票没卖完,执行卖票逻辑
            // 这里假设卖票过程需要耗时20ms
            handleSellTicketTime(20);
            String handleResult = String.format("%s-卖出了第%s张票", getName(), currTicketNo++);
            System.out.println(handleResult);
        }
    }

    /**
     * 获取卖票窗口名字
     */
    public String getName() {
        return Thread.currentThread().getName();
    }

    public void handleSellTicketTime(long time) {
        if (time <= 0) return;
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
java 复制代码
package thread;

public class SellTicketTest {
    public static void main(String[] args) {
        // 卖票任务
        SellTicketTask sellTicketTask = new SellTicketTask();
        // 3个卖票窗口执行卖票
        new Thread(sellTicketTask, "窗口一").start();
        new Thread(sellTicketTask, "窗口二").start();
        new Thread(sellTicketTask, "窗口三").start();
    }
}

测试结果发现存在数据不一致问题

这里数据不一致问题体现在如下两点

  • 存在多个窗口同时卖出同一张票
  • 出现超卖情况

解决线程安全问题

synchronized

java 复制代码
package thread;

public class SellTicketTask implements Runnable {
    // 当前票的编号,从1开始
    private int currTicketNo = 1;

    // 最大票的编号为500
    private static final int MAX_TICKET_NO = 500;

    @Override
    public void run() {
        while (true) {
            // 票卖完了
            if (currTicketNo > MAX_TICKET_NO) {
                break;
            }
            synchronized (Object.class) {
                // double check
                if (currTicketNo > MAX_TICKET_NO) {
                    break;
                }
                // 票没卖完,执行卖票逻辑
                // 这里假设卖票过程需要耗时20ms
                handleSellTicketTime(20);
                String handleResult = String.format("%s-卖出了第%s张票", getName(), currTicketNo++);
                System.out.println(handleResult);
            }
        }
    }

    /**
     * 获取卖票窗口名字
     */
    public String getName() {
        return Thread.currentThread().getName();
    }

    public void handleSellTicketTime(long time) {
        if (time <= 0) return;
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

Lock

java 复制代码
package thread;

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

public class SellTicketTask implements Runnable {
    // 当前票的编号,从1开始
    private int currTicketNo = 1;

    // 最大票的编号为500
    private static final int MAX_TICKET_NO = 500;

    // Lock锁
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 上锁
            lock.lock();
            try {
                // 票卖完了
                if (currTicketNo > MAX_TICKET_NO) {
                    break;
                }
                // 票没卖完,执行卖票逻辑
                // 这里假设卖票过程需要耗时20ms
                handleSellTicketTime(20);
                String handleResult = String.format("%s-卖出了第%s张票", getName(), currTicketNo++);
                System.out.println(handleResult);
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 获取卖票窗口名字
     */
    public String getName() {
        return Thread.currentThread().getName();
    }

    public void handleSellTicketTime(long time) {
        if (time <= 0) return;
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

原子类

java 复制代码
package thread;

import java.util.concurrent.atomic.AtomicInteger;

public class SellTicketTask implements Runnable {
    // 当前票的编号,从1开始(这里使用原子类AtomicInteger)
    private final AtomicInteger currTicketNo = new AtomicInteger(1);

    // 最大票的编号为500
    private static final int MAX_TICKET_NO = 500;

    @Override
    public void run() {
        while (true) {
            int currNo = currTicketNo.get();
            // 票卖完了
            if (currNo > MAX_TICKET_NO) {
                break;
            }
            // 票没卖完,执行卖票逻辑
            // 这里假设卖票过程需要耗时20ms
            handleSellTicketTime(20);
            // 比较并交换
            if (currTicketNo.compareAndSet(currNo, (currNo + 1))) {
                String handleResult = String.format("%s-卖出了第%s张票", getName(), currNo);
                System.out.println(handleResult);
            }
        }
    }

    /**
     * 获取卖票窗口名字
     */
    public String getName() {
        return Thread.currentThread().getName();
    }

    public void handleSellTicketTime(long time) {
        if (time <= 0) return;
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

线程的状态

概述

在Java中,定义了线程的6种状态

  • 新建状态:创建线程对象
  • 就绪状态:start方法等待CPU调度
  • 阻塞状态:无法获得锁对象
  • 等待状态:wait方法
  • 计时等待状态:sleep方法
  • 结束状态:全部代码运行完毕

查看源码

java 复制代码
package java.lang;

// ...

public
class Thread implements Runnable {
    // ...

    public enum State {
        // 新建状态
        NEW,

        // 就绪状态
        RUNNABLE,

        // 阻塞状态
        BLOCKED,

        // 等待状态
        WAITING,

        // 计时等待状态
        TIMED_WAITING,

        // 结束状态
        TERMINATED;
    }

    // ...
}
相关推荐
Python+JAVA+大数据2 小时前
TCP_IP协议栈深度解析
java·网络·python·网络协议·tcp/ip·计算机网络·三次握手
东东5162 小时前
基于vue的电商购物网站vue +ssm
java·前端·javascript·vue.js·毕业设计·毕设
她说..2 小时前
策略模式+工厂模式实现审批流(面试问答版)
java·后端·spring·面试·springboot·策略模式·javaee
鹿角片ljp2 小时前
力扣9.回文数-转字符双指针和反转数字
java·数据结构·算法
skywalker_112 小时前
网络编程篇
java·网络协议·网络编程
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于Java的九价疫苗预约系统为例,包含答辩的问题和答案
java·开发语言
tb_first3 小时前
SSM速通4
java·jvm·spring·tomcat·maven·mybatis
百炼成神 LV@菜哥3 小时前
Kylin Linux V10 aarch64安装DBeaver
java·linux·服务器·kylin
有代理ip3 小时前
成功请求的密码:HTTP 2 开头响应码深度解析
java·大数据·python·算法·php