多线程编程之线程常用的API大盘点【笔面试必备】

简介: 之前的两篇文章讲解了一些关于线程的相关概念和知识点,线程这块内容在平时的开发或者找工作的面试中也是常常被问起的知识点。之前写文章也是比较乱,想到什么写什么,没有一定的组织性,所以准备后面以系列文章的方式来记录和总结一些关于线程的知识点。我自己也是个学习者,通过写文章的方式来加强自己对于知识点的掌握和理解,所以如果有一些不正确或者不全的地方,欢迎大家指正。

01 前言


之前的两篇文章讲解了一些关于线程的相关概念和知识点,线程这块内容在平时的开发或者找工作的面试中也是常常被问起的知识点。之前写文章也是比较乱,想到什么写什么,没有一定的组织性,所以准备后面以系列文章的方式来记录和总结一些关于线程的知识点。我自己也是个学习者,通过写文章的方式来加强自己对于知识点的掌握和理解,所以如果有一些不正确或者不全的地方,欢迎大家指正。


之前的文章传送门:线程以及线程的常用方法线程的六种状态Java并发编程之Volatile关键字解析


关于线程的基本介绍以及线程的创建和启动前面的文章已经写过了,所以这篇文章准备总结以下关于线程的一些常用的API。

线程常用的API也有很多,这里总结一些比较常用的方法。


02 正文


1、sleep方法


sleep()是静态方法,所以最好的调用方法就是 Thread.sleep()。常用格式如下:


sleep(long millis) :线程睡眠 millis 毫秒


sleep(long millis, int nanos):线程睡眠 millis 毫秒 + nanos 纳秒


线程的sleep方法应该写在线程的run()方法里,就能让对应的线程睡眠指定的时间。


代码如下:


package com.jiangxia.chap1;
/**
 * thread的sleep方法
 * author:jiangxia
 * date:2021-04-14
 */
public class Demo01 {
    public static void main(String[] args) {
        Thread t = new Thread1();
        System.out.println("开始时间"+System.currentTimeMillis());
        //t.start方法启动线程
        t.start();
        System.out.println("结束时间"+System.currentTimeMillis());
    }
}
/**
 * 继承Thread类的方式实现线程
 */
class Thread1 extends  Thread{
    @Override
    public void run() {
        /**
         * 线程休眠10秒
         */
        try {
            System.out.println("线程开始时间:"+System.currentTimeMillis());
            Thread.sleep(10000);
            System.out.println("线程结束时间"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码


942206831a604536bb73c55a73bdf709~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


2、currentThread方法


currentThread()方法可以返回段正在被哪个线程调用的信息。Thread.currentThread()的引用对象和this引用对象不同,Thread.currendThread()获取的事使用该方法的线程对象,this获取的是当前对象。


代码如下:


package com.jiangxia.chap1;
/**
 * 线程的currentThread方法
 * author:jiangxia
 * date:2021-04-14
 */
public class Demo02 {
    public static void main(String[] args) {
        Thread t = new Demo8Thread();
        t.start();
        System.out.println("main方法:"+Thread.currentThread().getName());
    }
}
class Demo8Thread extends Thread{
    public Demo8Thread(){
        System.out.println("构造方法开始:");
        System.out.println("构造方法:"+Thread.currentThread().getName());
        System.out.println(this.getName());
        System.out.println("构造方法结束:'");
    }
    @Override
    public void run() {
        System.out.println("run方法开始:");
        System.out.println("run方法:"+Thread.currentThread().getName());
        System.out.println(this.getName());
        System.out.println("run方法结束:");
    }
}
复制代码


fbec04f7b72b4a258e068da4fb381bc5~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


3、getName()、getId()


Thread.getName() 方法返回当前线程的名字。getId的作用则是获取当前线程的唯一标识。


package com.jiangxia.chap1;
/**
 * 线程的getName和getId
 * author:jiangxia
 * date:2021-04-14
 */
public class Demo03 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println("线程的Name:"+thread.getName());
        System.out.println("线程的ID:"+thread.getId());
    }
}
复制代码


b0a5e44464d1426890987d244a77ecb6~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


4、isAlive方法


方法isAlive() 的功能是判断当前的线程是否处于活动状态;活动状态就是线程已经启动尚未终止,那么这时候线程就是存活的,则返回true,否则则返回false。


代码如下:


package com.jiangxia.chap1;
/**
 * isAlive方法
 * author:jiangxia
 * date:2021-04-14
 */
public class Demo9 {
    public static void main(String[] args) {
        Thread t = new Demo9Thread();
        System.out.println("准备启动线程:"+t.isAlive());
        t.start();//启动线程
        System.out.println("线程已启动:"+t.isAlive());
    }
}
class Demo9Thread extends Thread{
    public Demo9Thread(){
        System.out.println("构造方法开始");
        //Thread.currentThread().isAlive() 主线程的状态
        System.out.println(Thread.currentThread().isAlive());
        System.out.println(this.isAlive());
        System.out.println("构造方法结束");
    }
    @Override
    public void run() {
        System.out.println("run方法开始");
        //当前线程的状态
        System.out.println(Thread.currentThread().isAlive());
        System.out.println("run方法的运行状态:"+this.isAlive());
        System.out.println("run方法结束");
    }
}
复制代码


d294d431ecbd4174a03e4dfa018a7a4a~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


5、getState方法


getState() 方法返回该线程的状态。线程主要有以下几种状态:


状态 描述
NEW 尚未启动的线程
RUNNABLE 在Java虚拟机中正在执行的线程
BLOCKED 补阻塞等待对象锁的线程
WAITING 正在等待另一个线程执行特定动作的线程
TIME_WAITING 正在等待另一个线程执行到指定等待时间的线程
TERMINATED 已经退出运行的线程
package com.jiangxia.chap1;
/**
 * 线程的状态
 * author:jiangxia
 * date:2021-04-14
 */
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Demo1Thread();
        System.out.println("线程在main方法的状态A:"+t.getState());
        Thread.sleep(2000);
        t.start();
        Thread.sleep(2000);
        System.out.println("线程在main方法的状态B:"+t.getState());
    }
}
class Demo1Thread extends Thread{
    public Demo1Thread(){
        System.out.println("构造方法的状态:"+Thread.currentThread().getState());
    }
    @Override
    public void run() {
        System.out.println("Run方法的状态:"+Thread.currentThread().getState());
    }
}
复制代码


           d294d431ecbd4174a03e4dfa018a7a4a~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


6、yield方法


yield方法的作用是放弃当前的CPU资源,将资源让给其它的任务去占用CPU执行。但放弃时间不确定,有可能刚刚放弃,马上又获取CPU时间片。


代码如下:


package com.jiangxia.chap1;
/**
 * yield方法
 * author:jiangxia
 * date:2021-04-14
 */
public class Demo23 {
    public static void main(String[] args) {
        Thread t1 = new Demo23Thread();
        t1.start();
    }
}
class Demo23Thread extends Thread{
    @Override
    public void run() {
        int count =0;
        long starttime = System.currentTimeMillis();
        for (int i = 0; i < 100000 ; i++) {
            yield();
            count+=i;
        }
        long endtime = System.currentTimeMillis();
        System.out.println("计算总共用时:"+(endtime-starttime)+"毫秒");
    }
}
复制代码


复制代码


7、getPriority方法、setPriority方法获取和set线程的优先级


在操作系统中,线程可以划分优先级,优先级较高的线程得到更多的CPU资源,也就CPU会优先执行优先级较高的线程对象中的任务。设置线程优先有助于帮助『线程调度器』确定在下一次选择哪个线程优先执行。


设置线程的优先级使用setPriority方法,优级分为1~10个级别,如果设置优先级小于1或大于10,JDK抛出

IllegalArgumentException。JDK默认设置3个优先级常量,MIN_PRIORITY=1(最小值),NORM_PRIORITY=5(中间值,默认),MAX_PRIORITY=10(最大值)。


获取线程的优先级使用getPriority方法。设置线程的优先级使用setPriority方法。


package com.jiangxia.chap1;
/**
 * getPriority、setPriority
 * author;jiangxia
 * date:2021-04-14
 */
public class Demo24 {
    public static void main(String[] args) {
        //默认的优先级是5
        System.out.println("主线程的运行优先级:"+Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(9);
        System.out.println("使用setPriority()方法设置优先级后的主线程的优先级:"+Thread.currentThread().getPriority());
        Thread t = new Thread24();
        t.start();
    }
}
class Thread24 extends Thread{
    @Override
    public void run() {
        //由于线程的优先级具有继承性,所以在主线程使用了Thread.currentThread().setPriority(9)后,该线程的优先级也是9
        System.out.println("线程的优先级:"+this.getPriority());
    }
}
复制代码


72d9523b6a9540029c79eb0c2d016853~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


注:高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部执行完。当线程优先级的等级差距很大时,谁先执行完和代码的调用顺序无关。


线程的优先还有『随机性』,也就是说优先级高的线不一定每一次都先执行完成。


比如:


package com.jiangxia.chap1;
import java.util.Random;
/**
 * 线程优先级
 * author :jiangxia
 * date:2021-04-14
 */
public class Demo25 {
    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            Thread t1 = new Thread25();
            t1.setName("A"+i);
            //t1线程设置最高级别
            t1.setPriority(Thread.MAX_PRIORITY);
            t1.start();
            Thread t2 = new Thread25();
            t2.setName("B"+i);
            //t2线程设置最低级别
            t2.setPriority(Thread.MIN_PRIORITY);
            t2.start();
        }
    }
}
class Thread25 extends Thread{
    @Override
    public void run() {
        long start = System.currentTimeMillis();
        long count = 0;
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j <50000 ; j++) {
                Random r = new Random();
                count = i*j + r.nextInt();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("线程"+this.getName()+"执行完成使用了"+(end-start)+"毫秒");
    }
}
复制代码


93c13a0ff4ae4f518ff324c1c2bc1aa1~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


8、setDaemon设置守护线程


在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)


守护线程是一种特殊的线程,特殊指的是当进程中不存在用户线程时,守护线程会自动销毁。典型的守护线程的例子就是垃圾回收线程,当进程中没有用户线程,垃圾回收线程就没有存在的必要了,会自动销毁。


可以理解为:任何一个守护线程都是整个JVM中所有非守护线程的保姆。只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器)。


用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。


setDaemon方法可以将指定的线程设置为守护线程。比如:


package com.jiangxia.chap1;
/**
 * 守护线程
 * author:jiangxia
 * date:2021-04-15
 */
public class Demo26 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread26();
        thread.setDaemon(true);//把指定线程设置为守护线程
        thread.start();
        //IllegalThreadStateException 错误信息
        //thread.setDaemon(true);//把指定线程设置为守护线程
        Thread.sleep(5000);
        System.out.println("主线程结束");
    }
}
class  Thread26 extends Thread{
    @Override
    public void run() {
        while(true){
            try {
                System.out.println("当前时间:"+System.currentTimeMillis());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
复制代码


d47505a7c42d477c8db6d3171f6d1565~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


注意:


1、thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。


2、在Daemon线程中产生的新线程也是Daemon的。


3、守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。


4、Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能用Java的线程池。


03 总结


以上就是关于线程的一些常用的API的总结,如果你觉得写的不错或者对你有用,就分享给更多的人呗。


后面将继续分享关于多线程编程的一些其他的知识点。


目录
相关文章
|
5天前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
114 2
|
1天前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
39 16
|
10天前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
50 11
|
9天前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
26 6
|
14天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
29天前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
84 3
|
2月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
28天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
28天前
|
Java 调度