java多线程

本文详细介绍了Java多线程的相关概念,包括线程的生命周期、实现方式、同步和安全、死锁避免,以及线程池的使用和常见问题解答。讨论了线程的四种状态,线程的创建方式,如继承Thread类、实现Runnable接口以及使用Callable和Future。还讲解了synchronized和ThreadLocal的用法,以及线程的sleep、wait、yield和join方法的区别。此外,文章还提到了线程池的类型,如newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor和newScheduledThreadPool,以及线程池的生命周期管理。最后,解答了多线程中的一些常见问题,如run()和start()的区别,以及shutdown()和shutdownNow()的不同。
摘要由CSDN通过智能技术生成

Java多线程

线程的基本概念

进程:

每个进程都有独立的代码和数据空间(进程上下文),进程间的切
换会有较大的开销,一个进程包含 1–n 个线程。

b) 线程:

同一类线程共享代码和数据空间,每个线程有独立的运行栈和程
序计数器(PC),线程切换开销小。

) 并行与并发

i. 并行:多个 cpu 实例或者多台机器同时执行一段处理逻辑,是真正
的同时。
ii. 并发:通过 cpu 调度算法,让用户看上去同时执行,实际上从 cpu
操作层面不是真正的同时。并发往往在场景中有公用的资源,那么
针对这个公用的资源往往产生瓶颈,我们会用 TPS 或者 QPS 来反应
这个系统的处理能力。

线程的生命周期

新建状态:

i. 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程
对象就处于新建状态。它保持这个状态直到程序 start()这个线程。

b) 就绪状态:

i. 当线程对象调用了 start()方法之后,该线程就进入就绪状态,该状态
的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。

c) 运行状态:

i. 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便
处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状
态、就绪状态和死亡状态。

d) 阻塞状态:

i. 如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去
所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间
已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  1. 等待阻塞:运行状态中的线程执行 wait()方法,使线程进入到等
    待阻塞状态。
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别
    的线程占用,则 JVM 会把该线程放入锁池中。
  3. 其他阻塞:通过调用线程的 sleep()或 join()发出了 I/O 请求时,
    线程就会进入到阻塞状态。当 sleep()状态超时,join()等待线程
    终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
    e) 死亡状态:
    i. 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就
    关为跟你一起学多线程
    切换到终止状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmwG8nOz-1658798549782)(C:\Users\2537376648\AppData\Roaming\Typora\typora-user-images\image-20220416110128231.png)]

线程的实现方式

通过继承 Thread 类
package com.dailyblue.java.pack0108;

// 线程的第一种实现方式
// 1⃣️ 继承Thread类
public class DemoA extends Thread {
   

    // 2⃣️ 重新run方法  当run放被执行时 线程进入运行状态
    public void run() {
   
        for (int i = 0; i < 100; i++) {
   
            // 获取到当前运行的线程名
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + ":" + i);
        }
        // run方法全部执行完毕后线程进入死亡状态 释放当前线程对象
    }

    /*
        1.为什么书写的是run方法,但是调用的却是start?
        2.为什么调用的是start方法,但是执行的是run方法?
        3.为什么a1的run未执行完毕,a2的run方法就去执行了?
     */
    public static void main(String[] args) {
   
        // 当产生一个线程类型(Thread类或者它的子类)的对象时 进入新建状态
        DemoA a1 = new DemoA(); // 产生了一个对象
        DemoA a2 = new DemoA();
        // 当线程对象调用了start方法时 进入就绪状态
        // 调用了a1的start方法
        a1.start();// 我准备好,只欠东风 只等待CPU执行
        a2.start();

    }
}
/*
    线程的生命周期
    新建、就绪、运行、阻塞和死亡。
 */
b) 通过实现 Runnable 接口
package com.dailyblue.java.pack0108;

// 线程的第二种实现方式
// 1⃣️ 实现一个Runnable接口
public class DemoB implements Runnable {
   

    // 2⃣️ 重写run方法
    public void run() {
   
        for (int i = 1; i <= 20; i++) {
   
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
   
        // 这个时候不是新建状态  并不是一个线程对象
        DemoB b1 = new DemoB();
        DemoB b2 = new DemoB();
        // 产生2个线程去包装这两个任务b1,b2
        // 新建状态
        Thread t1 = new Thread(b1);
        Thread t2 = new Thread(b2);
        t1.start();
        t2.start();
    }
}
c) 通过 Callable 和 Future 创建线程

i. 创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为
线程执行体,并且有返回值(类似于 run()方法)。
ii. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,
该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。
iii. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
iv. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

package com.dailyblue.java.pack0108;

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

// 线程的第三种实现方式
// 1⃣️ 实现一个Callable接口
public class DemoC implements Callable {
   
    // 2⃣️重写call方法 这个方法能够得到返回结果 其他跟run一致
    public Object call() throws Exception {
   
        int sum = 0;
        for (int i = 1; i <= 20; i++) {
   
            System.out.println(Thread.currentThread().getName() + ":" + i);
            sum += i;
        }
        return "1-20之和:" + sum;
    }

    public static void main(String[] args) throws Exception{
   
        DemoC c1 = new DemoC();
        DemoC c2 = new DemoC();
        FutureTask ft1 = new FutureTask(c1);
        FutureTask ft2 = new FutureTask(c2);
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        t1.start();
        t2.start();
        // 获取到返回结果
        Object result1 = ft1.get();
        Object result2 = ft2.get();
        System.out.println(result1);
        System.out.println(result2);
    }
}
/*
    1.多线程常见概念
    2.线程的生命周期
    3.线程的三种实现方式
 */

线程的安全和同步

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
      线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
package com.dailyblue.java.pack0108;

// 演示了线程安全问题----不安全的情况---如果通过上锁的方式去实现了线程安全
public class DemoE {
   
    public static void main(String[] args) {
   
        Counter counter = new Counter();
        for (int i = 0; i < 4; i++) {
   
            DemoE1 e1 = new DemoE1(counter);
            Thread t = new Thread(e1);
            t.start();
        }
        // 暂停3s之后执行后续代码
        try {
   
            Thread.sleep(3000);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        int result = counter.getCount();
        System.out.println(result);
    }
}
// 实现了一个线程任务
class DemoE1 implements Runnable {
   
    // 声明了一个counter属性 然后通过构造器给属性赋值
    private Counter counter;
    public DemoE1(Counter counter){
   
        this.counter = counter;
    }
    public void run() {
   
        // 循环1000执行addCount() 实现对count的自增
        for (int i = 0; i < 1000; i++) {
   
            counter.addCount(); // 自增
        }
    }
}

// 是一个计数器类
class Counter {
   
    private int count = 0; // 计数器

    // 调用一次这个方法
    // 需要在某一个线程执行这个方法时 其他线程不能执行 可以通过🔒来实现 synchronized:上锁
    public synchronized void addCount() {
   
        count++; // 自增
    }

    public int getCount() {
   
        return count;
    }
}
/*
0-1-2-3-4-5-6-7-8
a1 3-->4
a2 3-->4
 */

synchronized的用法

package com.dailyblue.java.pack0108;

// synchronized关键字的使用场景 同步锁
public class DemoF {
   
    private int count = 10;

    // 对当前方法上锁的方式 保证线程安全  场景一
    public synchronized void methodA() {
   
        System.out.println(Thread.currentThread().getName()+"开始执行递减操作....");
        if (count > 0) {
   
            System.out.println(Thread.currentThread().getName() + "执行了相减操作,目前值:" + (count--));
            return;
        }
        System.out.println("count值必须大于等于0!");
    }
    private Object obj = new Object();
    public void methodB() {
   
        System.out.println(Thread.currentThread().getName()+"开始执行递减操作....");
        // 对一段代码块进行上锁操作,代码块中需要填写Object类的一个对象 场景二
        synchronized (obj){
   
            if (count > 0) {
   
                System.out.println(Thread.currentThread().getName() + "执行了相减操作,目前值:" + (count--));
                return;
            }
        }
        System.out.println("count值必须大于等于0!");
    }

    public static void main(String[] args) {
   
        DemoF f = new DemoF();
        for (int i = 0; i < 4; i++) {
   
            DemoF1 f1 = new DemoF1(f);
            Thread t1 = new Thread(f1);
            t1.start();
        }
    }
}

class DemoF1 implements Runnable {
   
    private DemoF f;

    public DemoF1(DemoF f) {
   
        this.f = f;
    }

    public void run() {
   
        for (int i = 0; i < 10; i++) {
   
            f.methodA();
        }
    }
}

ThreadLocal的用法

package com.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值