首页 > 编程语言 >Java基础——多线程

Java基础——多线程

时间:2024-11-16 15:50:25浏览次数:3  
标签:Java Thread 对象 创建 基础 线程 new 多线程 public

1. 线程

  1. 是一个程序内部的一条执行流程
  2. 程序中如果只有一条执行流程,那这个程序就是单线程的程序

2. 多线程

  1. 指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)

2.1. 如何创建多条线程

  1. Java通过java.lang.Thread类的对象来代表线程
2.1.1. 方式一:继承Thread类
// 1. 继承Thread类
public class MyThread extends Thread{
    // 2. 重写run方法
    @Override
    public void run() {
        System.out.println("子线程");
    }
}
public static void main(String[] args) {
    // 3. 创建子线程对象
    Thread t = new MyThread();

    // 4. 启动子线程
    t.start();

    System.out.println("主线程");
}
  1. 优点:编码简单
  2. 缺点:已经继承Thread,无法继承其他类,不利于功能的扩展
  3. 启动线程必须调用start方法,而不能调用run方法,否则会将线程对象当作一个普通的对象。
  4. 不要把主线程任务放在启动子线程之前。
2.1.2. 方式二:实现Runnable接口
// 1. 定义任务类, 实现Runnable接口
public class MyRunnable implements Runnable {
    // 2. 重写run方法
    @Override
    public void run() {
        System.out.println("Runnable子线程");
    }
}
public static void main(String[] args) {
    // 3. 创建一个任务对象
    Runnable runnable = new MyRunnable();

    // 4. 把任务对象交给一个线程对象, 启动线程
    new Thread(runnable).start();

    System.out.println("主线程");
}
  1. 任务类只是实现接口,可以继续继承其他类,实现其他接口,扩展性强
2.1.3. 方式三:利用Callable接口、FutureTask类实现
  1. 以上两种方式的问题:如果线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。
  2. 使用Callable接口,可以返回线程执行完毕后的结果

  1. 未来任务对象的作用,是一个任务对象,实现了Runnable接口;可以在线程执行完毕之后,用未来任务对象调用get方法获取执行完的返回结果
// 1. 实现Callable接口
public class MyCallable implements Callable<String> {
    // 2. 重写call方法
    @Override
    public String call() throws Exception {
        // 描述线程任务, 返回线程执行后的结果
        int sum = 0;
        for(int i = 1; i <= 100; i++){
            sum += i;
        }
        return String.valueOf(sum);
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 3. 创建一个Callable对象
    Callable<String> callable = new MyCallable();

    // 4. 将Callable对象封装成FutureTask任务对象
    FutureTask<String> futureTask = new FutureTask<>(callable);

    // 5. 使用任务对象创建一个线程对象, 并启动
    new Thread(futureTask).start();

    // 6. 获取线程执行完毕返回的结果
    String s = futureTask.get();
    System.out.println(s);
}

2.2. Thread的常用方法

3. 线程安全

  1. 多个线程,同时访问同一共享资源,且存在修改该资源的时候,可能会出现业务安全问题

4. 线程同步

  1. 是用于解决线程安全问题的方案
  2. 让多个线程实现先后依次访问共享资源,这样就解决了安全问题

4.1. 线程同步的方案

  1. 加锁,每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程再加锁进来。

4.2. 加锁的方式

4.2.1. 方式一:同步代码块
  1. 作用:把访问共享资源的核心代码块给上锁,以此保证线程安全
  2. 写法

  1. 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
  2. 注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象)
  3. Ctrl + ALT + T选第九个,快速生成同步代码块
  4. 建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象;静态方法建议使用字节码(类名.class)对象作为锁对象
4.2.2. 方式二:同步方法
  1. 作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

  1. 原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
4.2.3. 方式三:Lock锁
  1. 可以通过它创建锁对象,进行加锁和解锁
  2. Lock是接口,不能直接实例化,可以采用他的实现类ReentrantLock来创建锁对象。

5. 线程通信

  1. 当多个线程共同操作共享资源时,线程间通过某种方式互相告知自己的状态,一相互协调,并避免无效的资源争夺
  2. 生产者消费者问题

上述方法应该使用当前同步锁对象进行调用

public class Test5 {
    public static void main(String[] args) {
        // 有三个生产者线程负责生产包子, 每次只能有一个包子放在桌子上
        // 两个消费者线程负责吃包子, 每次只有一个线程能将包子吃掉
        // 1. 创建一个桌子对象
        Desk desk = new Desk();

        // 2. 创建三个厨师线程
        new Thread(() ->{
            while (true) {
                desk.put(); // 抢桌子
            }
        }, "厨师1").start();

        new Thread(() ->{
            while (true) {
                desk.put(); // 抢桌子
            }
        }, "厨师2").start();

        new Thread(() ->{
            while (true) {
                desk.put(); // 抢桌子
            }
        }, "厨师3").start();

        // 3. 创建两个吃货线程
        new Thread(() -> {
            while (true){
                desk.get();
            }
        },"吃货1").start();

        new Thread(() -> {
            while (true){
                desk.get();
            }
        },"吃货2").start();
    }
}
public class Desk {
    private List<String> list = new ArrayList<>();

    public synchronized void put() {
        try {
            String name = Thread.currentThread().getName();
            if(list.size() == 0){
                list.add(name + "包的包子");
                System.out.println(name + "包了一个包子");
                Thread.sleep(2000);

                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }else {  // 有包子了
                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public synchronized void get() {

        try {
            if(list.size() == 1){ // 有包子
                String name = Thread.currentThread().getName();
                System.out.println(name + "吃了" + list.get(0));
                list.clear(); // 清空
                Thread.sleep(1000);

                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            } else {
                // 唤醒别人, 等待自己
                this.notifyAll();
                this.wait();
            }
        }
        catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

6. 线程池

  1. 线程池就是一个可以复用线程的技术
  2. 代表线程池的接口,ExecutorService

6.1. 创建线程池对象

6.1.1. 使用ExecutorService的实现类ThreadPoolExecutor创建线程池对象

// 通过ThreadPoolExecutor创建一个线程池对象
/*
public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue,
                      ThreadFactory threadFactory,
                      RejectedExecutionHandler handler)
 */
ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
        TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());

注意事项:

  1. 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
  2. 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
  3. 如果是计算密集型任务,核心线程数 = CPU核数 + 1
  4. 如果是IO密集型任务,核心线程数 = CPU核数 * 2
6.1.2. 使用Executors(线程池工具类)调用静态方法返回不同特点的线程池对象

  1. 这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象
  2. 大型并发系统环境中使用Executors如果不注意可能会出现系统风险

6.2. 线程池处理Runnable任务、Callable任务

7. 并发、并行、线程的生命周期

7.1. 进程

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

7.2. 并发

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

7.3. 并行

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

7.4. 线程的生命周期

线程从创建到销毁的过程中,经历的各种状态及状态的转换

标签:Java,Thread,对象,创建,基础,线程,new,多线程,public
From: https://blog.csdn.net/weixin_68853331/article/details/143806940

相关文章

  • CSP/信奥赛C++语法基础刷题训练(9):洛谷P1035:[NOIP2002 普及组] 级数求和
    CSP/信奥赛C++语法基础刷题训练(9):洛谷P1035:[NOIP2002普及组]级数求和题目描述已知:Sn=1......
  • CSP/信奥赛C++语法基础刷题训练(10):洛谷P1307:[NOIP2011 普及组] 数字反转
    CSP/信奥赛C++语法基础刷题训练(10):洛谷P1307:[NOIP2011普及组]数字反转题目描述给定一个整数NNN,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,......
  • Java-面向对象(下)
    下面让我们继续学习面向对象类的继承在现有类的基础上去构建一个新的类。现有类叫做基类(baseclass)、超类(superclass)新的类叫做派生类(derivedclass)、子类(孩子类)(childclass)如果一个类想要继承另外一个类,需要用到extends关键字。class基类{}class子类extends......
  • 【C++类和对象基础篇下】再谈</|\>类和对象【完结撒花】
    --------------------------------------------------------------------------------------------------------------------------------- 每日鸡汤:再长的路,一步步也能走完,再短的路,不迈开双脚永远无法到达。你终会发现,拒绝放弃的那些努力,是多么值得!----------------------......
  • #Java-面向对象进阶-1
    1.static静态属性static是Java中的一个修饰符,可用来修饰成员变量、成员方法a.静态变量被static修饰的成员变量称为静态变量静态变量被该类的所有成员共享调用方式:类名调用(推荐)对象名调用例:创建方法//在创建的类中:publicstaticStringname;调用:假设类为:Stud......
  • #Java-面向对象进阶-多态
    1.多态多态是面向对象三大特征之一,表示同类型的对象表现不同的形态表现形式:父类类型对象名称=子类对象;多态的前提:有继承关系有父类引用子类Fuf=newZi();有方法重写使用场景举例:当需要写一个注册的方法,但是这个方法要能实现不同对象的注册例如:老......
  • Java序列化与反序列化深度解析
    一、引言在Java开发中,序列化与反序列化是非常重要的概念和技术手段。它允许我们将对象转换为字节流以便于存储或传输,然后在需要的时候再将字节流还原为对象。这一机制在很多场景中都有着广泛的应用,例如数据持久化、分布式系统中的远程方法调用(RMI)、缓存等。本文将深入探讨......
  • 操作系统4-基础知识判断题2
    25.引入当前目录是为了减少启动磁盘的次数√(当前目录可以放入内存) 26.文件目录必须常驻内存X解答:不一定。27.在文件系统中,打开文件是指创建一个文件控制块X解答:在文件系统中,“打开文件”并不直接等同于创建一个文件控制块,而是指对一个已经存在或正在创建的文件进......
  • 7.Java 注解和元注解(三种注解、四种元注解)
    一、注解概述注解也被称为元数据,用于修饰包、类、方法等数据信息和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息在JavaSE中,注解使用的目的比较简单,例如标记过时的功能,忽略警告等JavaEE中注解会充当更加重要的角色二、三种注......
  • JavaScript判断用户设备类型:PC端与移动端的区分方法
    在JavaScript中,可以通过检查用户代理字符串(UserAgentString)来判断用户设备类型,即访问网站的是PC端还是移动端设备。用户代理字符串是浏览器在发送HTTP请求时附带的一段信息,它包含了浏览器类型、版本、操作系统以及设备类型等信息。以下是一个简单的示例代码,用于判断用户......