☕☕ Java进阶攻坚克难,持续更新,一网打尽IO、注解、多线程…等
java-se
进阶内容。
想要搞明白什么是线程,首先要对程序与进程有一个清晰的概念,大学课堂上的《操作系统》这门课开篇就是这部分内容,学过这门课的同学对程序、进程和线程应该已经不陌生了。
程序(program): 为完成特定任务,用某种语言编写的一组指令集合,程序是静态的,这是它与进程的区别。
进程(process): 程序的一次执行过程,或是正在运行的一个程序,进程强调的是动态的过程。
在不引入线程这个概念之前,进程是系统运行时资源分配和调度的基本单位。为了充分利用系统资源,提高程序的运行效率,将进程进一步细化为线程,取代进程成为了调度和执行的基本单位。
✨线程(Thread)的特点:
并行: 多个CPU同时执行多个任务。
并发: 一个CPU同时执行多个任务。这里的“同时”并不是真的有多个任务在同时执行,CPU在一次还是只能执行一个任务,每个任务执行一段时间后就切换其他任务,只不过CPU的速度很快,看起来像是多个任务在同时执行。(操作系统讲到的CPU时间片轮转调度算法就是典型的并行执行例子)
JVM启动我们的Java程序以后,进程中会有默认的线程,比如Main主方法线程与GC垃圾回收线程。通常我们编写的代码都是在Main主方法线程中执行的,又称作用户线程,而GC垃圾回收线程称作守护线程。
✨Java中想要自己创建线程,有以下三种方式:✨
继承Thread类,并重写其中的run()
方法,通过调用start()
方法开启线程。
// 方式一:继承Thread类
// 1.创建子类
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
System.out.println("创建一个线程");
}
}
public class Test {
public static void main(String[] args) {
// 3.创建对象
MyThread myThread = new MyThread();
// 4.调用start()
myThread.start();
}
}
写一个小Demo,看一下我们自己创建的线程与主方法线程在运行时的顺序:
(为了避免CPU运行速度过快看不出多线程的效果,可以将循坏次数设置的大一些)
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 300; i++) {
System.out.println("***这是自己创建的线程***MyThread---");
}
}
}
public class Demo {
public static void main(String[] args) {
new MyThread().start();
for (int i = 0; i < 300; i++)
System.out.println("***这是主方法默认线程***Main---");
}
}
运行,可以看到即便代码中是先运行MyThread线程,但是两个线程中的输出语句是交替出现的。
声明实现类Runnable接口,并实现其中的run()
方法,然后分配类的实例,在创建Thread时作为参数传递(这里用了静态代理设计模式),调用Thread下的start()
方法启动。
// 方式二:实现Runnable接口
class MyThread implements Runnable {
// 实现run()方法线程体
@Override
public void run() {
System.out.println("创建一个线程");
}
}
public class Test {
public static void main(String[] args) {
// 2.创建Runnable接口的实现类对象
MyThread myThread = new MyThread();
// 3.创建线程对象并传递参数,通过线程对象开启我们的线程
new Thread(myThread).start();
}
}
使用匿名内部类的方式传递Runnable接口的实现类,让代码更简洁灵活:
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("创建一个线程");
}
}).start();
}
}
JDK1.8新特性,通过Lambda表达式进一步简化代码:
public class Test {
public static void main(String[] args) {
new Thread(() -> {
System.out.println("创建一个线程");
}).start();
}
}
Callable接口创建线程的流程:
实现Callable接口→重写call方法→创建目标对象→ 创建执行服务→提交执行→获取结果→关闭服务
import java.util.concurrent.*;
// 方式三:实现Callable接口
// 实现Callable接口
class TestCallable implements Callable<Boolean> {
// 实现call()方法,执行结束后返回一个值
@Override
public Boolean call() throws Exception {
System.out.println("创建一个线程");
return true;
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建目标对象
TestCallable t = new TestCallable();
// 创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(1);
// 提交执行
Future<Boolean> r = ser.submit(t);
// 获取结果
boolean res = r.get();
// 关闭服务
ser.shutdown();
}
}
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,不推荐使用 |
boolean isAlive() | 测试线程是否处于活动状态 |
调用线程方法可以控制线程的运行状态:
stop()
、destroy()
方法已经废弃,不推荐使用。flag=false
时终止线程运行。Demo案例:
class MyThread implements Runnable {
// 1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("Thread is running..." + i++);
}
}
// 2.设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
}
public class TestStop {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 运行myThread线程
new Thread(myThread).start();
// 当主线程循环到180次时结束myThread
for (int i = 0; i < 300; i++) {
System.out.println("***main***" + i);
if (i == 180) {
myThread.stop();
System.out.println("myThread线程已经停止");
}
}
}
}
运行结果:
线程休眠:
sleep(long millis)
指定当前线程阻塞的毫秒数。sleep(long millis)
方法存在异常InterruptedException
。Demo案例:
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSleep {
// 打印当前系统时间
public static void main(String[] args) throws InterruptedException {
// 获取当前时间
Date time = new Date(System.currentTimeMillis());
while (true) {
// 每隔1s打印一次时间
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));
// 更新时间
time = new Date(System.currentTimeMillis());
}
}
}
运行结果:
线程礼让:
yield()
方法即可让当前正在执行的线程暂停,但不阻塞。强制执行:
join()
方法合并线程,其他线程阻塞,待此线程执行完成后,再执行其他线程。查看线程状态:
调用getState()
方法查看线程状态:
示例代码:
public class TestState {
public static void main(String[] args) {
Thread myThread = new Thread(() -> {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这是一个线程");
}
});
// 查看线程状态
Thread.State state = myThread.getState();
System.out.println(state);
// 观察启动后
myThread.start();
System.out.println(myThread.getState());
}
}
输出结果:
NEW
RUNNABLE
这是一个线程
这是一个线程
这是一个线程
Process finished with exit code 0
线程优先级:
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
getPriority()
setPriority(int x)
线程分为用户线程与守护线程,操作日志、内存监控、垃圾回收等都属于守护线程。虚拟机只需要确保用户线程执行完毕,而不用等待守护线程也执行完。
通过setDaemon(boolean x)
方法设置守护线程,默认值是false
,代表用户线程,正常的线程都是用户线程,setDaemon(true)
将线程切换为守护线程。
代码示例:
public class TestDaemon {
public static void main(String[] args) {
// 设置用户线程执行20次
Thread userThread = new Thread(() -> {
for (int i = 0; i < 20; i++) {
System.out.println("***我是一个用户线程***" + i);
}
});
// 设置守护线程执行100次
Thread daemonThread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("---我是一个守护线程---" + i);
}
});
// 将线程设定为守护线程
daemonThread.setDaemon(true);
userThread.start();
daemonThread.start();
}
}
运行结果:
可以发现即便代码部分守护线程循环了100次,但是用户线程结束,整个程序都会结束,JVM不会再等待守护线程也执行完。
如果用过数据库连接池的同学对线程池也有一定概念了,它们都属于池化技术,目的就是提高程序的性能。
为什么需要线程池?
频繁创建和调用线程尤其是并发情况下的线程,对性能的影响很大。如果提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回线程池中,可以有效避免线程的频繁创建销毁,实现重复利用。
引入线程池技术的好处:
使用线程池:
JDK5.0版本提供的线程池工具类:ExecutorService 和 Executors,其中Executors是线程池的工厂类,用于创建并返回不同类型的线程池,ExecutorService是真正的线程池接口。
Executors.newFixedThreadPool(int parm)
方法返回一个线程池池,参数执行线程池的大小。execute(Runnable command)
方法用于将线程丢进线程池并执行线程。shutdown()
方法关闭线程池链接。线程池编程3步:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MyThread implements Runnable {
@Override
public void run() {
// 循环输出线程名称+循环次数
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " :" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
// 1.创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.将线程丢进线程池并启动
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3.关闭线程池链接
service.shutdown();
}
}
执行结果:
创作不易,如果觉得本文对你有所帮助,欢迎点赞、关注、收藏。♀️
@作者:Mymel_晗,计算机专业练习时长两年半的Java练习生~♂️