总结篇(23)---Java线程及其相关(1)线程介绍

本文围绕Java线程展开,介绍了线程调度,JVM采用抢占式调度;阐述了线程的六种状态及wait()、sleep()等方法的区别;说明了开启线程的三种方式,包括继承Thread类、实现Runnable接口等;还提及线程关闭的两种方式及线程阻塞的原因。

线程

  • 进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

一、线程调度


  • 各个线程轮流获得CPU的使用权,分别执行各自的任务。

    那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度,JVM采用的是抢占式调度,因此可能造成多线程执行结果的随机性。

备注:

        (1)一个CPU在任意时刻只能执行一条计算机指令,每个线程只有获得CPU的使用权才能执行指令
        (2)抢占式调度:指的是每条线程执行的时间、线程的切换都由系统控制。
        

二、线程的状态及转换


    

  • Java线程的六种状态
状态名称说明
NEW初始状态, 线程刚被构建,但是还没调用start()方法
RUNNABLE运行状态,Java系统中将操作系统中的就绪和运行两种状态笼统地称为“运行中”
BLOCKED阻塞状态,表示线程阻塞于锁
WAITING等待状态,表示线程进入等待状态,进入该状态表示当前线程做出一些特定动作(通知或者中断)
TIME_WAITING超时等待状态,该状态不同于等待状态,它可以在指定的时间后自行返回
TERMINATED中止状态,表示该线程已经执行完毕
  • 备注:Java线程只有六种状态,网上还有说五种状态,其实那个操作系统的五种状态。

  • wait()与sleep()和notify()理解

            wait()方法针对的是一个被同步代码块加锁的对象,在进入WAITING状态时会释放对象的锁,直到被notify()或notifyAll()唤醒线程,被唤醒的线程便会进入该对象的锁池中,锁池中的线程会去竞争该锁对象。
            sleep()方法针对的是一个线程,执行sleep()会进入TIME_WAITING状态,仅仅让你的线程进入睡眠状态,而不会释放对象的锁。

三、开启线程的方式


  • 开启线程共三种方式

1.继承Thread类创建线程类

        (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表线程要完成的任务。
        (2)创建Thread子类的实例。
        (3)调用线程对象的start()方法来启动线程。

****示例代码:****
//第一步:定义Thread类的子类
public class TestThread extends Thread{
    //重写run方法,run()方法称为执行体。
    public void run(){
        System.out.println("执行方法体")
    }
    
    public static void main(String[] args){
        //第二步、第三步:创建Thread子类的实例并执行start()来启动线程
        new TestThread().start();
    }
}

2.通过Runnable接口创建线程类

        (1)定义Runnable接口的实现类,并重写该接口的run方法。
        (2)创建Runable实现类的实例,并依此实例作为Thread的参数来创建Thread对象,该Thread对象才是真正的线程对象。
        (3)调用线程对象的start()方法来启动该线程。

****示例代码:****
//第一步:定义Rnnable接口的实现类,并重写该接口的run方法
public class TestRunnable implement Runnable{
    public void run(){
        System.out.println("执行Runnable执行体")
    }
    
    public void main(String[] args){
        //第二步上半节:创建Runnable实现类的实例
        TestRunnable tr = new TestRunnable();
        //第二步下半节、第三步:依据创建的Runnable实例作为参数传进Thread对象,并执行其start()方法
        new Thread(tr,"线程1")。start();
    }
}


3.通过Callable和Future创建线程

        (1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
        (2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutrueTask对象封装了该Callable对象的call()方法的返回值。
        (3)使用FutureTask对象作为Thread对象的参数创建并启动新线程。
        (4)调用FutrueTask对象的get()方法来获得子线程执行结束后的返回值。

****示例代码:****
//第一步:实现Callable接口,并实现call()方法
public class CallableTest implement Callable<Integer>{
        //call()方法作为线程执行体,有返回值
        @Override
        public Integer call() throw Exception{
            int i = 100;
            return i;
        }
    
        public static void main(String[] args){
            //第二步上半节:创建Callable实现类的实例
            CallableTest ct = new CallableTest();
            //第二步下半节:使用FutrueTask类来包装Callable对象
            FutureTask<Integer> ft = new FutureTask<>(ct);
            //第三步:调用FutureTask对象作为Thread对象的参数创建并启动线程
            new Thread(ft).start();
            
            try{
                //第四步:调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
                System.out.println("子线程返回值:"+ft.get());
            }catch(ExecutionException e){
                e.printStackTrace();
            }
            
        }
}



备注:

  • FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。

通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

public class FutureTask<V> implements RunnableFuture<V> {
    //....
}


public interface RunnableFuture<V> extends Runnable, Future<V> {
   void run();
}

四、线程关闭


  • 个人认为有两种方式:

1.使用标志位

        很简单的设置一个标志位,启动线程后,定期检查里这个标志位。如果检测到这个标志位为某个值,线程马上结束。

****示例代码:****
public class MyThread implements Runnable{
    //设置标志位,需要为volatile,保证线程读取时isCancelled是最新数据。
    private volatitle boolean isCancelled;
    
    public void run(){
        //定期检查这个标志位,如果检测到某个值,线程马上结束
        //但并不完善。考虑下,如果线程执行的方法被阻塞,那么如何执行isCancelled的检查呢?线程有可能永远不会去检查标志位,也就卡住了。
        while(!isCancelled){
            //doSomething
        }
    }
    
    public void cancel(){
        isCancelled = true;
    }
}

2.使用中断

        每个线程都有个boolean类型的中断状态。当使用Thread的interrupt()方法时,线程的中断状态会被设置为true。
        对线程调用interrupt()方法,不会真正中断正在运行的线程,只是发出一个请求,由线程在合适时候结束自己。

Thread.currentThread().interrupt();


备注:

  • Java提供了中断机制,Thread类下有三个重要方法。
(1)public void interrupt()
(2)public boolean isInterrupted()
(3)public static boolean interrupted(); // 清除中断标志,并返回原状态


        

五、线程阻塞的原因


  • 导致线程阻塞一般有一下原因:
原因说明
线程进行了休眠线程执行了Thread.sleep(int n)方法,线程放弃CPU,睡眠n毫秒,然后恢复运行
线程执行wait()进入阻塞状态线程执行了一个对象的wait()方法,进入阻塞状态,只要等到其他线程执行了该对象的notify或notifyAll()方法,才能将其唤醒。
等待相关资源线程执行I/O操作或进行远程通信时,会因为等待相关资源而进入阻塞状态。
线程等待获取同步锁才能进行下一步操作线程要执行一段同步代码,由于无法获得相关的同步锁,只好进入阻塞状态,等到获取了同步锁,才能恢复运行。
请求连接时请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法或Connect()方法返回。

备注:

1)线程同步和线程协作的理解

        1-1)线程同步(Synchronization):确保多个线程对共享资源的访问是互斥且有序的,防止数据竞争(Data Race)导致的数据不一致或程序崩溃。

        典型场景:

  • 多个线程同时修改一个共享计数器。
  • 多个线程读写同一个文件或数据库连接。

        1-2)线程协作(Cooperation):让多个线程按照特定顺序协同工作,例如一个线程等待另一个线程完成任务后再继续执行。

        典型场景:

  • 生产者-消费者模型:生产者生成数据,消费者消费数据,缓冲区空或满时线程等待。
  • 多阶段任务:多个线程分阶段执行任务,每个阶段需要所有线程完成后才能进入下一阶段。

2)Runnable和completableFulture的区别?

2-1)定义与核心目标

特性

Runnable

CompletableFuture

本质

一个接口,定义线程任务

一个类,表示异步计算的结果

核心目标

描述一个可运行的任务

处理异步操作的结果,支持链式调用和组合任务

返回值

void

(无返回值)

支持泛型返回值(如 CompletableFuture<T>

异常处理

无法抛出受检异常(只能内部处理)

支持显式异常处理(如 exceptionally()

2-2)使用场景

Runnable

  • 简单异步任务:不需要返回结果,只关注任务执行本身。
  • 线程池提交:通过 ExecutorService.submit(Runnable) 提交任务。
  • 示例:日志记录、发送通知、定时任务等。
Runnable task = () -> {
    System.out.println("Task executed by: " + Thread.currentThread().getName());
};
new Thread(task).start();

CompletableFuture

  • 复杂异步流程:需要组合多个异步操作,处理结果依赖或异常。
  • 链式调用:通过 thenApply(), thenCompose(), thenCombine() 等组合任务。
  • 示例:调用多个服务后聚合结果、异步任务依赖处理等。
CompletableFuture.supplyAsync(() -> fetchDataFromAPI())
.thenApply(data -> processData(data))
.thenAccept(result -> saveResult(result))
.exceptionally(ex -> {
    System.err.println("Error: " + ex.getMessage());
    return null;
});

更多java基础总结(适合于java基础学习、java面试常规题):

总结篇(1)---复用类

总结篇(2)---多态

总结篇(3)---内部类 (1)内部类的基本概念

总结篇(4)---内部类 (2)内部类之静态内部类

总结篇(5)---内部类 (3)内部类之成员内部类

总结篇(6)---内部类 (4)内部类之局部内部类

总结篇(7)---内部类 (5)内部类之匿名内部类

总结篇(8)---序列化

总结篇(9)---字符串及基本类 (1)字符串及基本类之基本数据类型

总结篇(10)---字符串及基本类 (2)字符串及基本类之java中公共方法及操作

总结篇(11)---字符串及基本类 (3)String对象

总结篇(12)---字符串及基本类 (4)Integer对象

总结篇(13)--- Java注解及元注解

总结篇(14)---JVM(java虚拟机) (1)JVM虚拟机概括

总结篇(15)---JVM(java虚拟机) (2)类加载器

总结篇(16)---JVM(java虚拟机) (3)运行时数据区

总结篇(17)---JVM(java虚拟机) (4)垃圾回收

总结篇(18)---JVM(java虚拟机) (5)垃圾回收算法

总结篇(19)---JVM(java虚拟机) (6)JVM调优

总结篇(20)---反射

总结篇(21)---Java IO

总结篇(22)---Java 进程

总结篇(23)---Java线程及其相关(1)线程介绍

总结篇(24)---Java线程及其相关(2)多线程及其问题

总结篇(25)---Java线程及其相关(3)线程池及其问题

总结篇(26)---Java线程及其相关(4)ThreadLocal

总结篇(27)---Java并发及锁(1)Synchronized

总结篇(28)---Java并发及锁(2)Volatile

总结篇(29)---Java并发及锁(3)Lock

总结篇(30)---Java并发及锁(4)常见锁及分类

总结篇(31)---JUC工具类(1)CountDownLatch

总结篇(32)---JUC工具类(2)CyclicBarrier

总结篇(33)---JUC工具类(3)Semaphore

总结篇(34)---JUC工具类(4)Exchanger

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

sun cat

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

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

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

打赏作者

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

抵扣说明:

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

余额充值