线程详解(代码片段)

我又null了 我又null了     2022-12-02     155

关键词:

 

 

线程概述

运行一个音乐播放器播放一首歌,音乐播放器就是一个进程,在程序执行时,既有声音的输出,同时还有该歌曲的字幕展示,这就是进程中的两个线程

线程与进程

程序进入内存就变成了进程,进程就是处于运行中的程序

进程特征:

  • 独立性:每个进程都有自己的私有地址,一个进程不能直接访问其他进程
  • 动态性:进程有自己的生命周期和不同状态,而程序不具备
  • 并发性:多个进程可以在单个处理器上并发执行,进程之间互不影响

并发: 进程在cpu中切换执行  并行:进程在cpu上一起执行

对于一个CPU而言,它在某个时间点只能执行一个程序,也就是说,只能运行一个进程,
CPU不断地在这些进程之间轮换执行,虽然CPU在多个进程间轮换执行,但是我们感觉到好像有多个进程在同时进行

线程是进程的执行单元,对于绝大多数的应用程序来说,通常仅要求有一个主线程,
但也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程(子线程),
每个线程也是相互独立的

线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,
但不拥有系统资源,它与父进程的其他线程共享该进程所有拥有的全部资源

一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

 

 

多线程的优势

  • 进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其他的每个线程的状态
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,提高运行效率
  • 线程共享的环境包括:进程代码段、进程的公有数据
  • 进程之间不能共享内存,但线程之间共享内存非常容易
  • 系统创建进程是需要为该进程重新分配系统资源,但创建线程则代价小得多

 

线程的创建与启动

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例

每个线程的作用是完成一定的任务,实际上就是执行一段程序代码。

Java使用线程执行体来代表这段程序代码。

Ø  继承Thread创建线程

  1. 定义Thread类的子类,并重写该类的run()方法,run()方法的方法体代表线程需要完成的任务。因此把run方法称为线程执行体。

  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程。
public class Test2 extends Thread

    // 重写run方法,run方法的方法体就是子线程的执行体
    @Override
    public void run() 
        for (int i = 0; i < 100; i++) 
            // 继承Thread类后,从父类继承的getName方法可以获取当前线程的名称
            System.out.println("线程名称:"+this.getName()+" "+i);
        
    

    public static void main(String[] args) 
        for (int i = 0; i < 100; i++) 
            // 调用Thread的currentThread()方法获取当前线程对象
            // 这里就不能用this来获取name了
            System.out.println("线程名称;"+Thread.currentThread().getName()+"="+i);

            //创建两个子线程,并运行
            if (i == 20)
                new Test2().start();
                new Test2().start();
            
        

    


线程是以抢占式的方式运行的,虽然只创建了两个线程实例,实际上有三个线程在运行
(两个子线程,一个主线程main)

通过setName(String name)的方式来为线程设置名称,也可以通过getName的方式来得到线程的名称。
在默认情况下,主线程的名称为main,用户启动的多线程的名称依次为Thread-0,Thread-1,Thread-3..Thread-n

 

 

实现Runnable接口创建线程

  1. 定义Runnable接口的实现类,并重写该接口的run方法
  2. 创建Runnable实现类的实例对象,并以此实例对象作为Thread的target来创建Thread类,该Thread对象才是真正的线程对象。

  3. 调用线程对象的start()方法来启动该线程
public class Test3 implements Runnable
    @Override
    public void run() 
        for(int i = 0;i < 100;i++) 
            // 实现了Runnable接口的类其本质并不是线程类,因此没有getName方法,
            // 因此需要通过Thread类来获取当前线程,仅仅是一个任务体,仍需交给Thread去执行
            System.out.println("线程名称:"+Thread.currentThread().getName()+" "+i);
        
    

    public static void main(String[] args) 
        /*new一个实现了Runnable接口的实例,这个实例不是线程对象
        * 不能test3.start()来运行子线程,执行run方法体
        * 实际的线程对象要通过new Thread()来获取,只不过对于实现了Runnable接口的实现类的实例
        *   作为参数传入到new Thread(test3).start()来执行子线程
        *   意义是让线程对象来执行test3实例的run方法体
        * */
        Test3 test3 = new Test3();
        new Thread(test3).start();
        new Thread(test3).start();
    

 

又因为Runnable是一个函数式接口,所以可以使用lamda表达式来进行代码编写

public class Test4 




    public static void main(String[] args) 

        /*
        * 用lamda表达式的写法,里面写的就是run方法体
        * 将runnable传入到 new Thread(runnable,"子线程1")里面,就表示了创建了子线程
        *   并执行run方法体,第二个参数是为子线程起名字
        * */
        Runnable runnable = ()->
            for (int i = 0; i < 100; i++) 
                System.out.println("线程名字:"+Thread.currentThread().getName()+"="+i);
            
        ;

        Test4 test4 = new Test4();
        new Thread(runnable,"子线程1").start();
        new Thread(runnable,"子线程2").start();
    

 

 

通过对比上面两种创建线程的方式,继承Thread 和 实现Runnable接口,第一种主线程和子线程

分别执行一遍任务。第二种主线程和子线程共同完成一个任务。

 

Ø  使用Callable&Future创建线程

在Java 1.5开始,Java提供了Callable接口,该接口实际上可看成是Runnable接口的增强版,Callable接口提供了一个call()的方法可以作为线程的执行体,但call()方法比run()方法功能更加强大。

这是因为:

1. call()方法可以有返回值

2. call()方法可以声明抛出异常

因此我们可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。但是存在以下两个问题:

1. Callable接口是Java 5提供的一个新的接口而不是Runnable接口的子接口,所以Callable对象不能直接作为Thread类的target目标执行类。

2. call()方法还有一个返回值——call()方法并不是直接调用,它是作为线程执行体被调用的。如何获取call()方法的返回值?

为了解决以上两个问题,Java 1.5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runable接口——可以作为Thread类的target。

 

 

创建并启动有返回值的线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且该call()方法有返回值
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。

  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程
  4. 调用FutureTask对象的get()方法来获得子线程结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 实现Callable接口时指定的泛型为返回值的类型
 */
public class Test5 implements Callable<Integer> 

    /*
    * 对于实现了Callable接口的类,重写的call方法就是子线程要执行的方法体
    *   这个call方法与run方法的区别就是,call有返回值,可以声明抛出异常
    *
    * 实现的Callable接口可以看做是Runnable接口的增强版,所以可以提供一个Callable对象作为target传给线程对象
    *   但是问题就是,Callable接口不是Runnable接口的子接口,不能直接作为target
    *   call方法有返回值
    *
    * */
    @Override
    public Integer call() throws Exception 
        for (int i = 0; i < 100; i++) 
            /*与实现了Runnable接口的run方法相似,也是不能使用this来获取name*/
            System.out.println("当前线程名称:"+Thread.currentThread().getName()+" "+i);
            Thread.sleep(200);
        
        return 100;
    

    public static void main(String[] args) throws ExecutionException, InterruptedException 
        /*创建Callable对象,因为当前类实现了Callable接口  多态*/
        Callable<Integer> callable = new Test5();
        // 创建FutureTask对象,并将call对象封装在FutureTask内部,FutureTask的泛型为Callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        /*创建线程对象*/
        new Thread(futureTask).start();
        // 获取线程结束后的返回值
        System.out.println("线程执行结束后的返回值:"+futureTask.get());

    

 

Callable接口是一个函数式接口,所以可以用lamda表达式写法

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

public class Test6 

    public static void main(String[] args) throws Exception 
        /*
        * Callable接口也是一个函数式接口,所以可以用lamda表达式写法
        *  里面写的就是call的方法体
        * */
        Callable<Integer> callable = ()->
            for (int i = 0; i < 100; i++) 
                System.out.println("线程名称:"+Thread.currentThread().getName()+"="+i);
            
            return 100;
        ;

        // 创建FutureTask对象,并将call对象封装在FutureTask内部,FutureTask的泛型为Callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        /*创建线程对象  并将FutureTask封装好的Callable对象传入线程对象中*/
        new Thread(futureTask).start();

        // 获取线程结束后的返回值
        System.out.println("线程执行结束后的返回值:"+futureTask.get());

    

 

 

Ø  创建线程的三种方式比较

通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常,并且Callable需要FutureTask来进行封装成Thread可识别的target目标。因此可以将实现Runnable接口和实现Callable接口归纳为一种方式。这种方式与继承Thread方式之间的主要差别如下

 

 

线程的声明周期

当线程被创建并启动后,并不是一启动就进入了执行状态,也不是一直处于执行状态,

在线程的生命周期中,它要经历新建、就绪、运行、阻塞和死亡5种状态。

尤其是当线程启动以后,它不可能一直占用CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会在运行、阻塞之间切换。

 

新建状态:当new了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样仅仅由Java虚拟机为其分配内存,并初始化其他成员变量的值

就绪状态:当线程对象调用了start方法之后,该线程就处于就绪状态,Java虚拟机会为这个线程对象创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于什么时候开始运行,则取决于JVM里的线程调度器的调度。

运行状态:处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程就处于运行状态

阻塞状态:

  • 线程调用sleep()方法主动放弃所占用的处理器资源
  • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
  • 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有
  • 线程在等待某个通知(notify)

  • 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

死亡状态

  • run()或call()方法执行完成,线程正常结束
  • 线程抛出一个未捕获的Exception或者直接Error错误
  • 直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用

当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动之后,它就拥有和主线程相同的地位

不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,该线程不可以再次作为线程执行。

 

控制线程

 

多线程详解(代码片段)

一.多线程的创建与启动1、继承Thread类:步骤:①、定义类继承Thread;②、复写Thread类中的run方法;目的:将自定义代码存储在run方法,让线程运行③、调用线程的start方法: 该方法有两步:启动线程,调用run方法。不建议使... 查看详情

cyclicbarrier详解(代码片段)

CyclicBarrier详解简介阻塞一组线程,直到某个事件发生.所有线程必须都到达栅栏位置时,才能继续执行.使得一定数量的线程反复在栅栏位置汇集.需要等待集合的线程调用await()方法在栅栏处阻塞.栅栏释放阻塞的线程后会重置以便下... 查看详情

java线程池详解(代码片段)

构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情

java线程池详解(代码片段)

构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。基础知识Executors创建线程池Java中创建线程池很简单,... 查看详情

javadaemonthread守护线程详解(代码片段)

原文链接:https://www.cnblogs.com/ziq711/p/8228255.html用户线程和守护线程在Java中有两类线程:UserThread(用户线程)、DaemonThread(守护线程) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:只要当前JVM实例... 查看详情

线程池拒绝策略详解(代码片段)

文章目录1.前言2.ThreadPoolExecutor创建线程方式3.ThreadPoolExecutor拒绝策略测试3.1.AbortPolicy3.2.CallerRunsPolicy3.3.DiscardPolicy3.4.DiscardOldestPolicy3.5.自定义拒绝策略4.第三方实现的拒绝策略4.1.dubbo中的线程拒绝策略4.2.Netty中的线程池拒绝策略4.3... 查看详情

线程池拒绝策略详解(代码片段)

文章目录1.前言2.ThreadPoolExecutor创建线程方式3.ThreadPoolExecutor拒绝策略测试3.1.AbortPolicy3.2.CallerRunsPolicy3.3.DiscardPolicy3.4.DiscardOldestPolicy3.5.自定义拒绝策略4.第三方实现的拒绝策略4.1.dubbo中的线程拒绝策略4.2.Netty中的线程池拒绝策略4.3... 查看详情

exchanger详解(代码片段)

Exchanger详解简介当一个线程到达栅栏时,会检查是否有其他线程已经到达栅栏.若没有,则该线程进入等待.若有,则与等待的其他线程交换各自的数据,然后继续执行.原理内部类Participant继承自ThreadLocal,用来保存线程本地变量Node.Node存... 查看详情

reentrantreadwritelock详解(代码片段)

...ntrantReadWriteLock详解简介特点:ReentrantReadWriteLock允许多个读线程同时访问,不允许写线程和读线程,写线程和写线程同时访问.一般情况下,共享数据的读操作远多于写操作,比ReentrantLock提供更好的并发性和吞吐量.读写锁内部维护两个... 查看详情

java多线程详解(代码片段)

多线程创建方式Thread类定义一个子类MyThread继承线程类Java.lang.Thread,重写run()方法创建MyThread对象调用线程对象的start()方法启动线程(启动后还是执行run方法)优缺点优点:编码简单缺点:线程类已经继承Thread,无法继承其他类... 查看详情

java多线程:线程池详解(代码片段)

文章目录1.线程池1.1线程池概述1.1.1线程池的概念1.1.2线程池的工作机制1.1.3使用线程池的原因1.1.3线程池的设计思路:1.2线程池的创建1.2.1Executors默认线程池newCachedThreadPool:创建默认线程池,最多容纳int类型的最大值newFixedT... 查看详情

java多线程详解(代码片段)

线程对象是可以产生线程的对象。比如在Java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线... 查看详情

线程详解(代码片段)

  线程概述运行一个音乐播放器播放一首歌,音乐播放器就是一个进程,在程序执行时,既有声音的输出,同时还有该歌曲的字幕展示,这就是进程中的两个线程线程与进程程序进入内存就变成了进程,进程就是处于运行... 查看详情

ios多线程详解(代码片段)

iOS多线程详解Slogan:可能是最通俗易懂的iOS多线程详细解析文章1.基础概念1.1进程进程是计算机中已运行程序的实体,是线程的容器维基百科-进程。每个进程之间是相互独立的,每个进程均运行在器专用且收保护的内存空... 查看详情

线程安全性详解(代码片段)

线程安全性定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的线程... 查看详情

02java进阶--线程池详解(代码片段)

线程池线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后,启动这些任务,如果线程超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行... 查看详情

狂神说java笔记--多线程详解部分笔记(代码片段)

传送门==>B站遇见狂神说Java多线程详解做笔记时有的知识点并没有整理;ml1.线程创建之继承Thread类图片下载练习2.线程创建之实现Runnable接口买票案例模拟龟兔赛跑3.线程创建之实现Callable接口4.静态代理模式5.Lambda表达式6.... 查看详情

c#多线程thread实例详解(代码片段)

1.Thread线程启动由于ThreadStart是一个委托,所以可以简化写法staticvoidMain(string[]args)Console.WriteLine("----------主程序开始,线程ID是0-----------------",Thread.CurrentThread.ManagedThreadId);for(inti=0;i<5;i++)ThreadSta 查看详情