首页 > 其他分享 >第一部分 多线程基础

第一部分 多线程基础

时间:2024-05-26 14:05:06浏览次数:12  
标签:第一 Thread 基础 thread 线程 interrupt sleep 多线程 public

  • 本系列博客,主要是面向Java8的源码。
  • 本系列博客主要参考汪文君老师 《Java高并发编程详解》一书
  • 转载请注明出处,多谢~。

1. 线程的start方法剖析

/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 */
public synchronized void start() {
        
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        
        group.add(this);

        boolean started = false;
        try {
            start0();//核心部分就是这个start0本地方法,也就是JNI方法,run方法就是被此方法调用
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                
            }
        }
    }

2. Runnable接口的引入

//Runnable接口非常简单,只定义了一个无参数无返回值的run方法
public interface Runnable {
    public abstract void run();
}

可以通过继承Thread然后重写run方法实现自己的业务逻辑,也可以实现Runnable接口实现

//构造Thread时传递Runnable
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        
    }
});

//Lambda简化
Thread thread = new Thread(() -> {

});

注意:

  • 准确地讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元则有两种方式
  • Thread负责线程本身相关的职责和控制,而Runnable则负责逻辑执行单元的部分

3. 线程的父子关系

Thread的所有构造函数,最终都会调用一个方法 init()

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    Thread parent = currentThread(); //获取当前线程作为父线程
    SecurityManager security = System.getSecurityManager();
    ......
}

结论:

  • 一个线程的创建肯定是由另一个线程完成的
  • 被创建的线程的父线程就是创建它的线程

4. Thread与ThreadGroup

在Thread的构造函数中,可以显示的指定线程的Group,也就是ThreadGroup

//接着读Thread init方法的源码,你就会发现
SecurityManager security = System.getSecurityManager();
if (g == null) {
    /* Determine if it's an applet or not */

    /* If there is a security manager, ask the security manager
               what to do. */
    if (security != null) {
        g = security.getThreadGroup();
    }

    /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
    if (g == null) {
        g = parent.getThreadGroup();
    }
}

结论:

  • main线程所在的ThreadGroup成为main
  • 构造一个线程时如果没有显示的指定ThreadGroup,那么它将会和父线程同属于一个ThreadGroup

5. 守护线程

守护线程是一类比较特殊的线程,一般用于处理一些后台的工作,比如JDK的垃圾回收线程

JVM程序在什么情况下会退出?

The Java Virtual Machine exits when the only threads running are all daemon threads.

在正常退出的情况下,若JVM中没有一个非守护线程,则JVM的进程会退出

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true); //将thread设置为守护线程
        thread.start(); //启动thread线程
        Thread.sleep(2000L);
        System.out.println("Main thread exit");
    }
}

运行结果:main线程结束生命周期后,JVM也随之退出运行

6. Thread API

6.1 线程sleep
//sleep是一个静态方法,会让当前线程进入指定毫秒数的休眠
//注意:休眠不会放弃monitor锁的所有权
Thread.sleep()
    
//使用TimeUnit代替Thread.sleep
TimeUnit.HOURS.sleep(3)//休眠3小时
TimeUnit.MINUTES.sleep(24)//休眠24分钟
TimeUnit.SECONDS.sleep(17)//休眠17秒
TimeUnit.MILLISECONDS.sleep(12)//休眠12毫秒
6.2 线程yield
//yield方法属于一种启发式的方法
//其会提醒调度器我愿意放弃当前的CPU资源,如果CPU资源不紧张,则会忽略 RUNNING->RUNNABLE
public static native void yield();

注意:

  • sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗
  • yield只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换
  • sleep会使线程短暂block,会在给定的时间内释放CPU资源
  • 一个线程sleep另一个线程调用interrupt会捕获到中断信号,而yield则不会
6.3 设置线程优先级

public final void setPriority(int newPriority) 为线程设置优先级

public final int getPriority() 获取线程的优先级

源码分析:

public final static int MAX_PRIORITY = 10;
public final static int MIN_PRIORITY = 1;
public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { //线程的优先级不能小于1也不能大于10
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        //如果线程的优先级大于线程所在group的优先级,指定的优先级就会失效,取而代之的是group的最大优先级
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}
6.4 获取线程ID

public long getId()

线程的ID在整个JVM进程中都会是唯一的,并且都是从0开始逐次递增

自己创建的线程绝非第0号线程

6.5 获取当前线程

public static native Thread currentThread();

用于返回当前执行线程的引用

6.6 线程interrupt

public void interrupt()

public static boolean interrupted() 判定当前线程是否被中断

public boolean isInterrupted() 判定当前线程是否被中断

以下方法的调用会使得当前线程进入阻塞状态,调用interrupt方法可以打断阻塞

  • Object的wait()方法
  • Object的wait(long) 方法
  • Object的wait(long,int)方法
  • Thread的sleep(long)方法
  • Thread的sleep(long,int)方法
  • Thread的join()方法
  • Thread的join(long)方法
  • Thread的join(long,int)方法
  • InterruptibleChannel的io操作
  • Selector的wakeup方法
  • 其他方法

上述的方法都会使得当前线程进入阻塞状态

另外一个线程调用被阻塞线程的interrupt方法可以打断阻塞

因此这些方法有时候被称为可中断的方法

注意:仅仅打断了线程的阻塞状态,不等于生命周期结束

interrupt到底做了什么?

①:在一个线程内部存在着名为interrupt flag的标识

②:如果一个线程被interrupt,那么它的flag将被设置

③:线程执行可中断方法被阻塞时,调用interrupt方法将其中断,反而会导致flag被清除

④:如果一个线程是死亡状态,iterrupt会直接被忽略

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                //空的死循环,不会捕捉中断信号
            }
        });

        thread.start(); //启动thread线程
        Thread.sleep(2000L);
        System.out.println(thread.isInterrupted());//false
        thread.interrupt();//
        Thread.sleep(2000L);
        System.out.println(thread.isInterrupted());//true
    }
}


public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000L);//可中断方法,会捕捉到中断信号,并且擦去interrupt标识
                } catch (InterruptedException e) {
                    System.out.println("I am be interrupted");
                }
            }
        });

        thread.start(); 
        Thread.sleep(2000L);
        System.out.println(thread.isInterrupted());//false
        thread.interrupt();
        Thread.sleep(2000L);
        System.out.println(thread.isInterrupted());//false
    }
}

interrupted()isInterrupted()区别

interrupted是一个静态方法,虽然其也用于判断当前线程是否被中断,但是调用该方法会直接擦除线程的interrupt标识

如果当前线程被打断,第一次调用Interrupt方法会返回true,并且擦除interrupt标识,第二次之后的调用都会返回false,除非再次被打断

public class ThreadInterrupted {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println(Thread.interrupted());
            }
        });
        thread.setDaemon(true);
        thread.start();
        TimeUnit.MILLISECONDS.sleep(2);
        thread.interrupt();
    }
}

/**
* 输出
* ...
* false
* false
* true
* false
* false
* ...
*/

interrupt注意事项:

//interrupted源码
public static boolean interrupted() {
    return currentThread().isInterrupted(true); //擦除interrupt标识
}
//isInterrupted源码
public boolean isInterrupted() {
    return isInterrupted(false); //不擦除interrupt标识
}
//都调用了isInterrupted这个本地方法
private native boolean isInterrupted(boolean ClearInterrupted);//ClearInterrupted主要来控制是否擦除interrupt标识

分析下面两段程序,有什么不同?

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        // 1,判断当前线程是否被中断
        System.out.println("Main Thread is interrupted? " + Thread.interrupted());

        //2,中断当前线程
        Thread.currentThread().interrupt();

        //3,判断当前线程是否已经被中断
        System.out.println("Main Thread is interrupted? " + Thread.currentThread().isInterrupted());

       try {
           //4,执行可中断方法
           TimeUnit.SECONDS.sleep(10);
       } catch (InterruptedException e) {
           System.out.println("Interrupted Exception");
       }
    }
}
//输出
// Main Thread is interrupted? false
// Main Thread is interrupted? true
// Interrupted Exception

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        // 1,判断当前线程是否被中断
        System.out.println("Main Thread is interrupted? " + Thread.currentThread().isInterrupted());

        //2,中断当前线程
        Thread.currentThread().interrupt();

        //3,判断当前线程是否已经被中断
        System.out.println("Main Thread is interrupted? " + Thread.interrupted());

       try {
           //4,执行可中断方法
           TimeUnit.SECONDS.sleep(10);
       } catch (InterruptedException e) {
           System.out.println("Interrupted Exception");
       }
    }
}
//输出
//Main Thread is interrupted? false
//Main Thread is interrupted? true

可以看出Demo2的可中断方法没有抛出异常,意味着没有被中断,而Demo1被中断了

原因:

  • interrupted()立即擦除了Interrupt标识
  • isInterrupted()捕获到了InterruptedException之后才会擦除Interrupt标识
6.7 线程join

可中断方法

join某个线程A,会使当前线程B进入等待,知道A生命周期结束,或者到达给定时间,B此时是BLOCKED的

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        //1.定义两个线程
        List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList());

        //2.启动两个线程
        threads.forEach(Thread::start);

        //3.执行这两个线程的join方法
        for (Thread thread : threads) {
            thread.join();
        }

        //4.main线程循环输出
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "#" + i);
            shortSleep();
        }
    }

    //构造一个简单线程,每个线程只是简单的循环输出
    private static Thread create(int seq) {
        return new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "#" + i);
                shortSleep();
            }
        }, String.valueOf(seq));
    }

    private static void shortSleep() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行上面的代码:

//输出             线程一和线程二交替输出直到生命周期结束,main线程才开始运行
...
2#8
1#8
1#9
2#9
main#0
main#1
main#2
...
//注释掉3			线程一和线程二和main线程交替执行

总结:join方法会使当前线程永远的等待下去,直到期间被另外的线程中断,或者join的线程执行结束,或者join的另外两个重载方法指定的毫秒数时间到达后,当前线程才会退出阻塞

6.8 如何关闭一个线程
6.8.1 正常关闭
  1. 线程结束生命周期正常结束

  2. 捕获中断信号关闭线程

    public class Way1 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread() {
                @Override
                public void run() {
                    while (!isInterrupted()) {
                        //working
                    }
                }
            };
            t.start();
            TimeUnit.MINUTES.sleep(1);
            t.interrupt();
        }
    }
    
    public class Way2 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                for (; ; ) {
                    //working
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            });
            t.start();
            TimeUnit.MINUTES.sleep(1);
            t.interrupt();
        }
    }
    
  3. 使用volatile开关控制

    public class FlagThreadExit {
        static class MyTask extends Thread {
            private volatile boolean closed = false;
    
            @Override
            public void run() {
                System.out.println("I will start work");
    
                while (!closed && !isInterrupted()) {
                    //working...
                }
    
                System.out.println("I will stop work");
            }
    
            public void close() {
                closed = true;
                this.interrupt();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            MyTask t = new MyTask();
            t.start();
            TimeUnit.MINUTES.sleep(1);
            System.out.println("System will be end");
            t.close();
        }
    }
    

    上面的例子定义了一个closed开关变量,并且是用volatile修饰,同样可以关闭线程

6.8.2 异常退出

在一个线程的执行单元中,是不允许抛出checked异常的,不论Thread的run方法还是Runnable的run方法,如果线程运行过程中需要捕获checked异常并且判断是否还有运行下去的必要,那么可以将checked异常封装成unchecked异常(RuntimeException)抛出进而结束线程的生命周期

6.8.3 进程假死

所谓假死,就是进程虽然存在,但是没有日志输出,程序不进行任何的作业,看起来就像死了一样,但事实上并没有死
一般是由于某个线程阻塞了或者出现死锁的情况

标签:第一,Thread,基础,thread,线程,interrupt,sleep,多线程,public
From: https://blog.csdn.net/qq_70003997/article/details/139186553

相关文章

  • GitHub 保姆级教程:从基础到高级,全面掌握 GitHub
    GitHub保姆级使用教程:从入门到精通GitHub是全球最大的代码托管平台,也是开发者进行版本控制、协作开发的重要工具。本教程将带您从GitHub的基础操作开始,逐步深入,掌握GitHub的各项高级功能。入门篇1.注册和登录首先,访问GitHub官网,注册账号并登录。2.创建仓库(Re......
  • 基础6 探索JAVA图形编程桌面:集合组件详解
            我们的团队历经了数不胜数的日夜,全力以赴地进行研发与精心调试,最终成功地推出了一款具有革命性意义的“图形化编程桌面”产品。这款产品的诞生,不仅极为彻底地打破了传统代码开发那长久以来的固有模式,更是把焦点聚集于解决长期以来一直困扰着开发者的一大难题—......
  • 【Java学习】第39节:基础数据结构(二):链表
    目录1. 链表1)概述2)单向链表3)单向链表(带哨兵)4)双向链表(带哨兵)5)环形链表(带哨兵)习题E01.反转单向链表-Leetcode206E02.根据值删除节点-Leetcode203E03.删除倒数节点-Leetcode19E04.有序链表去重-Leetcode83E05.有序链表去重-Leetcode82E06.合......
  • python+k8s——基础练习
    列表core_api=client.CoreV1Api()#管理核心资源(Pod,Service,ConfigMap等)apps_api=client.AppsV1Api()#管理应用资源(Deployment,StatefulSet,DaemonSet等)batch_api=client.BatchV1Api()#管理批处理任务资源(Job,CronJob)rbac_api=client.RbacAuthorizati......
  • python+k8s(基础,遇到的问题)
    python+k8s(基础,遇到的问题)CoreV1Api和ApiClient的区别kubernetes.client.CoreV1Apikubernetes.client.ApiClient两者有什么区别吗kubernetes.client.CoreV1Api和kubernetes.client.ApiClient是KubernetesPython客户端库中的不同类。CoreV1Api:这是KubernetesPyt......
  • Java基础知识点-常见面试题(持续更新...)
    文章目录前言面向对象的三大特征1.封装2.继承3.多态抽象类和接口有什么相同点和区别?Java深拷贝、浅拷贝、引用拷贝Java中==和equals()的区别?为什么重写equals()时必须重写hashCode()方法?1.首先,hashCode()有什么用?2.两个对象有相同的hashCode值并不意味它们一定相等......
  • Python小白必备!清华大牛整理的《Django零基础入门到精通》手册
    Django是Python社区的两大最受欢迎的Web框架之一(另一个是Flask)。凭借功能强大的脚手架和诸多开箱即用的组件,可以使你能够以最小的代价构建和维护高质量的Web应用。从好的方面来看,Web开发激动人心且富于创造性;从另一面来看,它却是份繁琐而令人生厌的工作。通过减少重复......
  • 联邦学习基础
    联邦学习非独立同分布解决方案目录联邦学习联邦学习基础概念联邦学习的定义联邦学习训练流程联邦学习的分类联邦学习架构介绍联邦学习面临问题Non-IID问题引出Non-IID的引出Non-IID的类别Non-IID的衡量指标Non-IID的解决方法Non-IID的解法推广对策1:多任务学习(Multi-taskLe......
  • Java SE入门及基础(53)& 方法引用
    目录方法引用1.应用场景示例分析2.方法引用符示例解释说明3.静态方法引用语法示例4.成员方法引用语法示例示例5.this引用成员方法语法示例6.super引用父类成员方法语法示例7.构造方法引用语法示例方法引用1.应用场景方法引用   来......
  • 数据资产入表「第一讲」-数据资产入表简介
    什么是数据资产入表?    数据资产入表是指将数据资源确认为企业资产负债表中的“资产”一项,即将数据资产以会计科目和货币化形式呈现,推动企业数据资源向数据资产转变,形成规范的数据资产开发、运营和管理体系,提升企业数据治理能级的过程。这一概念的提出和实施,标志着数......