什么是线程
线程是系统调度的最小单位,是轻量级的“进程”(因为创建和销毁一个线程比进程的成本低,因为线程之间可以共享资源,每次创建不需要分配过多的新资源)。进程相当于一个工厂,而线程相当于工厂中一个个流水线,也就是说一个进程可以包含多个线程。线程之间是相互独立的。线程虽然比进程轻量,但还不够轻量,而协程是线程还轻量的。go,Python等语言就内置了协程。
进程与线程之间的关系
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
- 一个进程内的线程之间是可以共享进程的资源的(主要共享内存资源和打开的文件,线程的上下文,状态,记账信息、优先级是不可以进行共享的,栈空间是每个线程要独立的一份),而进程不可以资源共享。
- 每个进程可以有多个线程,至少有一个线程存在,即主线程。(创建一个进程的时候,至少会随之创建一个线程,即主线程)
- 进程的实际执行单位就是线程
- 线程的存储必须依赖于进程
- 线程更轻量,比进程的并发效率更高
- 调度进程的本质还是在调度线程
- 一个进程中的线程并不是越多越好,一个进程中最多的线程数与CPU的个数和线程执行任务类型有关(CPU密集型(程序一直在执行计算任务)和io密集型(计算很少主要进行输入输出操作)),若为CPU密集型,理论上最多能创建CPU的个数个线程,io密集型理论上可以创建任意多个。
线程常见的一些属性
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
线程管理
线程管理是通过pcb来进行描述的,通过一个双向列表来进行组织的。内核只认识pcb,一个线程之和一个pcb对应,而一个进程可以和多个pcb对应。内核中把属于同一个进程的线程称为“线程组”,如果某几个pcb的的线程组ID是一样的,则这几个线程属于同一个进程。内核中并不区分谁是进程与线程,统一通过pcb进行管理,只要线程组ID一样,他们就属于同一个进程。一个线程只有执行了启动方法才会真正的被创建,创建对应的pcb。
线程休眠的方法
创建线程的放法
方法1-继承 Thread 类
可以通过继承 Thread 来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过Thread.currentThread() 来获取当前线程的引用。
//直接用Thread类创建线程
public class ThreadDemo4 {
public static void main(String[] args) {
// 写法 2
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("当前线程名称:" +
Thread.currentThread().getName());
}
};
// 运行线程
thread.start();
}
}
-------------------------------------------------------------------------------------------
public class ThreadDemo3 {
// 1.继承 Thread 类
static class MyThread extends Thread {
@Override
public void run() {
// 写你的业务代码
// 打印当前线程的名称
System.out.println("子线程名称:" +
Thread.currentThread().getName());
}
}
public static void main(String[] args) {
// 1.继承 Thread 类
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
System.out.println("主线程:" +
Thread.currentThread().getName());
}
}
方法2-实现 Runnable 接口
通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创
建线程对象。该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取
当前线程的引用。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程名:" +
Thread.currentThread().getName());
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
// 创建 Runnable 子对象
MyRunnable myRunnable = new MyRunnable();
// 创建线程
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
}
}
-----------------------------------------------------------------------------------
public class ThreadDemo6 {
public static void main(String[] args) {
// 创建一个匿名 Runnable 类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("当前线程:" +
Thread.currentThread().getName());
}
});
thread.start();
}
}
--------------------------------------------------------------------------------------
其他变形(了解)
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
创建一个有返回值的线程
package thread.thread0424;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建一个有返回值的线程
*/
public class ThreadDemo8 {
// 创建线程
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 产生随机数
int num = new Random().nextInt(10);
System.out.println(String.format("线程:%s,生产了随机数:%d",
Thread.currentThread().getName(), num));
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.创建 Callable 子对象
MyCallable callable = new MyCallable();
// 2.使用 FutrueTask 接收 Callable
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 3.创建线程并设置任务
Thread thread = new Thread(futureTask);
// 执行线程
thread.start();
// 得到线程的执行结果
int num = futureTask.get();
System.out.println("线程返回结果:" + num);
}
}
线程分组
public class ThreadDemo10 {
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("group1");
Thread t1 = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("选手1达到终点了~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1200);
System.out.println("选手2达到终点了~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
// 等待所有人员到达终点
while (threadGroup.activeCount() != 0) {
}
System.out.println("宣布比赛结果");
}
}
线程的常见属性
/**
* 演示线程的常见属性
*/
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Thread t = Thread.currentThread();
System.out.println("线程ID:" + t.getId());
System.out.println("线程名称:" + t.getName());
System.out.println("线程状态:" + t.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
// 启动线程
t1.start();
// 打印线程状态
Thread.sleep(100);
System.out.println("t1 线程状态:" + t1.getState());
System.out.println("优先级:" + t1.getPriority());
System.out.println("是否为守护线程:" + t1.isDaemon());
System.out.println("是否存活:" + t1.isAlive());
System.out.println("是否被中断:" + t1.isInterrupted());
// 等待线程执行完
t1.join(); // 方式一
while (!t1.isAlive()) { // 方式二
}
}
}
带优先级的线程
public class ThreadDemo12 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
// 权重最低的线程
Thread t1 = new Thread(() -> {
System.out.println("t1");
}, "t1");
// 线程一优先级最低
t1.setPriority(1);
t1.start();
// 权重最高的线程
Thread t2 = new Thread(() -> {
System.out.println("t2");
}, "t2");
// 线程一优先级最低
t2.setPriority(5);
t2.start();
// 权重最高的线程
Thread t3 = new Thread(() -> {
System.out.println("t3");
}, "t3");
// 线程一优先级最低
t3.setPriority(10);
t3.start();
}
}
}
守护线程
守护线程的使用场景有1、java垃圾回收器,监控监测任务(比如tcp的保活机制)
注意事项
- 守护线程的设置必须放在start之前
- 守护线程内创建的线程也是守护线程
public class ThreadDemo13 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 设置后台线程(守护线程)
t1.setDaemon(true);
t1.start();
System.out.println("线程类型:" + t1.isDaemon());
}
}
run() vs start():
1.run() 方法是一个对象的普通方法,它使用的是主线程来执行任务的。
2.start()是线程的开启方法,它使用新的线程来执行任务的。
3.start()方法只能执行一次,而run()可以调用n次。
线程中断的方式
1.自定义全局标识来实现中断,比较温柔的方式,不会立马终止,而是等待当前的一次任务执行完在终止。
// 全局变量
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!flag) {
System.out.println("我正在转账...");
// try {
// // 休眠线程
// Thread.sleep(100);
// System.out.println("我正在转账...");
// } catch (InterruptedException e) {
// e.printStackTrace();
// break;
// }
}
System.out.println("啊?差点误了大事。");
}, "张三");
// 开启任务
t1.start();
// 休眠主线程一段时间
Thread.sleep(310);
// 终止线程
System.out.println("停止交易,有内鬼.");
flag = true;
}
2.使用Thread的intrrupted来中断。使用系统的 Intrruput()可以及时立马的终止线程,比较暴力。
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (!Thread.interrupted()) {
//另一种判断线程终止的方法
//while ( ! Thread.currentThread().isInterrupted()) {
try {
// 休眠线程
Thread.sleep(100);
System.out.println("我正在转账...");
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("啊?差点误了大事。");
}, "张三");
// 开启任务
t1.start();
// 休眠主线程一段时间
Thread.sleep(310);
// 终止线程
System.out.println("停止交易,有内鬼.");
t1.interrupt();
}
静态方法Thread.interrupted():第一次接收到终止状态是true,之后就会将状态复位(恢复成false),因为是全局的,可能会被好多人使用 。普通方法 .isInterrupted()只用来得到线程的状态,并不会进行复位。因为是私有的