进程和线程的关系和区别:
线程存在的意义:
由于并发编程成为刚需,虽然多进程能够实现并发编程,但是线程比进程更加轻量,能够更加充分的利用多核 CPU 的资源:
线程可以视为是轻量级进程,但随着创建和销毁频率提高,线程也就显得比较重了,进一步的解决方案如下:
Java 的线程和操作系统线程的关系:
Thread 类是 JVM 用来管理线程的一个类,每个创建的 Thread 实例都是和系统中的一个线程是对应的,用来描述一个线程执行流,JVM 会将 Thread 对象组织起来,用于线性调度和线程管理。
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Tread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组几位线程组 |
Java 中实现多线程主要有以下两种方式:
Runnable 接口时线程辅助类,仅定义了一个 fun() 方法。该方法中描述了要实现的线程的内容。
优点:
Thread 用法一:
创建一个类,继承自 Thread,重写 Thread 中的 run 方法,该 run 方法内部包含了这个线程要执行的代码,当线程跑起来时就会依次来执行这个 run 方法中的代码。
// 定义的这个类描述了要创建出的线程是啥样的,但是并未真正创建出来
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("hello thread!");
}
}
public class Demo1 {
public static void main(String[] args) {
// 1. 创建 Thread 实例,此处创建的 MyThread1
MyThread1 t = new MyThread1();
// 2. 调用 Thread 的 start 方法,这会在系统内部真正创建出线程
t.start();
}
}
Thread 用法二:
创建一个类,实现 Runnable 接口,重写 run 方法。创建 Thread 实例,然后把刚才的 Runnable 实例给设置进去。
class MyRunnable implements Runnable{
@Override
public void run() {
// 描述这个任务具体要进行的工作
System.out.println("hello thread2!");
}
}
public class Demo2 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
Thread 用法三:
在用法一的基础上改造,采用匿名内部类。
public class Demo3 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("hello thread3!");
}
};
t.start();
}
}
Thread 用法四:
在用法二的基础上改造,采用匿名内部类。
public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello thread4!");
}
});
t.start();
}
}
Thread 用法五:
在用法二的基础上改造,搭配 lambda 表达式。
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("hello thread5!");
});
t.start();
}
}
如果对创建的线程对象调用 run 方法,那么并没有创建出一个新线程,并在新线程中执行指定的任务,而是在原本的线程中执行指定的任务,并只有在指定任务结束后才会执行后续的操作。
属性 | 补充 | 获取方法 |
---|---|---|
ID | ID 是线程唯一标识 | getId() |
名称 | 名称是便于调试的,JVM 会默认指定名称 | getName() |
状态 | 状态表示线程当前所处的一个情况 | getState() |
优先级 | 理论上优先级越高越容易被调度到 | getPriority() |
是否后台线程 | JVM 会在一个进程的所有非后台线程结束后,才会结束运行 | isDaemon() |
是否存活 | 表示 run 方法是否结束运行,NEW 和 TERMINATED 状态是未存活。 | isAlive() |
是否被中断 | isInterrupted() | |
返回当前线程对象的引用 | 以上属性在被调用时,要先通过 Thread 类的 currentThread() 方法获取到正在被执行的线程引用才能够调用。 | currentThread() |
补充:如果是通过继承 Thread 来创建的线程,此时直接在 run 方法中通过 this,就能够拿到这个线程的实例。
线程只有执行完 run 方法中的内容才会结束,但如果该线程执行的内容是一个死循环,并且涉及到风险,那么就要能够手动停止线程了。
目前常见的中断线程的方式如下:
手动设置标志位,来作为循环结束的判定条件(需要给标志位加上 volatile 关键字,该方法存在缺陷)。
volatile 的作用如下:
保证变量的内存可见性
禁止指令重排序
public class Demo6 {
// 通过这个变量来控制线程是否结束
public volatile static boolean isQuit = true;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(isQuit) {
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = false;
System.out.println("hello main!");
}
}
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位为 true。 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清楚标志位。 |
public boolean isInterrupted() | 判断当前线程的中断标志位是否设置,调用后不清楚标志位。 |
对于以上方法设置标志位,Thread 收到通的方式有两种:
public class Demo7 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
System.out.println("hello main!");
}
}
有时我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。通过 join 方法,就能够起到阻塞线程的作用,从而达到手动控制线程执行顺序的一种办法(join 是控制结束顺序)。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等待 millis 毫秒 |
public void join(long millis, int nanos) | 等待线程结束,最多等待 millis 毫秒,精度更高 |
注意:在 main 线程中调用 t.join() 方法,是让 main 线程来等待 t 线程执行完毕。
应用:使用 Scanner 进行输入操作时,就运用了阻塞等待。
通过 sleep 方法就能够使调用 sleep 方法的线程进入阻塞等待的状态,至于阻塞的时间则取决于 sleep 方法中设置的时间参数。
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 休眠当前线程 millis 毫秒,精度更高 |
注意:因为线程的调度是不可控的,所以这个方法只能够保证实际休眠时间是大于等于参数设置的休眠时间的。
一个进程中有多个线程,每个线程对应一个 PCB,即一个进程对应一组 PCB。操作系统调度 PCB 的时候,会从就绪队列中挑出一个 PCB 去 CPU 上执行,而处于阻塞队列中的 PCB 是不会被系统调度到 CPU 上执行的。而调用了 sleep 方法后,该线程就会被移动到阻塞队列中,只有到达了 sleep 的睡眠时间,该 PCB 才会被移动回就绪队列,但此时并不意味着该线程就能够在 CPU 上执行了,因为线程调度是不可控的。
线程的状态是一个枚举类型 Thread.State,具体状态如下:
状态 | 说明 | 补充 |
---|---|---|
NEW | 安排了工作,还未开始行动 | Thread 创建出了对象,但是内核里面的线程还没创建,即未调用 start 方法。 |
RUNNABLE | 可以工作的,又可以分成正在工作中和即将开始工作 | 就绪状态 |
BLOCKED | 表示排队等着其它事情 | 阻塞状态,等待锁的时候产生的状态。 |
WAITING | 表示排队等着其它事情 | 阻塞状态,通过 wait 方法产生的状态。 |
TIMED_WAITING | 表示排队等着其它事情 | 阻塞状态,通过 sleep 方法产生的状态。 |
TERMINATED | 工作完成了 | 内核里的线程已经结束了,然后 Thread 创建的对象还在。 |
通过下面这段代码,就能够将 Java 线程的所有状态打印出来:
public class Demo8 {
public static void main(String[] args) {
for(Thread.State state: Thread.State.values()){
System.out.println(state);
}
}
}
|
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://t4dmw.blog.csdn.net/article/details/126339307
内容来源于网络,如有侵权,请联系作者删除!