线程是什么
Java中一个线程就是一个 “执行流”. 每个线程有自己的任务. 多个线程之间 “同时” 各自完成着自己的任务.
线程和进程的区别
- 进程包含线程,一个进程里可以有一个线程,也可以有多个线程.
- 进程和线程都是为了处理并发编程这样的场景.但是进程有问题,频繁的穿件和释放的时候效率低,相比之下,线程更轻量,创建和释放效率更高.(轻量的原因是减少了申请释放资源的过程)
- 操作系统创建进程,要给进程分配资源,进程是操作系统分配资源的进本单位.而操作系统创建线程,是要在CPU上调度执行,线程是操作系统调度执行的基本单位.
- 进程具有独立性.每个进程有各自的虚拟地址空间,一个进程挂了,不会影响到其他进程.而同一个进程中的多个线程则是共用的一个内存空间,一个线程挂了,就有可能影响到其他线程,甚至导致整个进程奔溃.
Java中创建线程的方式
多线程程序和普通程序的区别:
- 每个线程都是一个独立的执行流
- 多个线程之间是 “并发” 执行的
先来感受一个多线程程序
import java.util.Random;
public class ThreadDemo {
private static class MyThread extends Thread {
@Override
public void run() {
//用户产生随机数
Random random = new Random();
while (true) {
// 打印线程名称 Thread.currentThread()用来获取当前线程实例
System.out.println(Thread.currentThread().getName());
try {
// 随机停止运行 0-9 秒 Thread.sleep();让程序进入阻塞状态.参数为时间,单位为毫秒
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.start();
t2.start();
t3.start();
Random random = new Random();
while (true) {
// 打印线程名称
System.out.println(Thread.currentThread().getName());
try {
//让main线程随机休眠.
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {
// 随机停止运行 0-9 秒
e.printStackTrace();
}
}
}
}
1.继承Thread类
创建子类,继承自Thread,并重写run方法
并不是创建好子类之后就会创建线程的,而是实例化之后调用start()方法才创建了线程
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//线程被异常中断
e.printStackTrace();
}
}
}
}
public class Test1 {
public static void main(String[] args) {
//MyThread t = new MyThread();//可以用Thread类接受实例,也可以使用MyThread接收实例
Thread t = new MyThread();
t.start();
for (int i=0;i<20;i++) {
System.out.println("main");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.实现 Runnable 接口
创建一个类实现Runnable接口,实现run方法
运行方式:
- new一个实现了Runnable接口的类实例
- new 一个Thread(new Runnable);将 Runnable 对象作为 target 参数传入
- 调用类的.start()方法
- 此类方式比较推荐:Runnable单纯的只是描述了一个任务,至于任务是用什么方式来执行,Runnable并不关心(高内聚低耦合)
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable");
}
}
public class Test2 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
对比上面两种方法:
- 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
- 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()来引用线程实例
3.使用匿名内部类
此种方式输入方法1的变形
public class Test3 {
public static void main(String[] args) {
//匿名内部类创建 Thread 子类对象
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Test3");
}
};
thread.start();
}
}
4.使用匿名内部类
此种方式输入方法2的变形
public class Test4 {
public static void main(String[] args) {
//匿名内部类创建 Runnable 子类对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
t1.start();
}
}
3和4看似相同,实则完全是两种方式
3.匿名的是继承Thread的实现类类
4.匿名的则是实现Runnable的接口类
5.lambda表达式.
属于4.方式的lambda表达式方式.
此种方式虽然更简洁,但是对于初学者相对不友好
public class Test5 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("lambda"));
t1.start();
}
}
Thread介绍
了解完线程的创建,我们再来看看Java的Thread类.
1.Thread常见的构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并给线程对象命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
2.Thread 的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复.
- 名称是使用各种调试工具时,可以使用名称来区别线程.
- 状态表示线程当前所处的一个情况,指官方文档中的线程状态.
- 优先级高的线程理论上来说更容易被调度到,但并不是一定调用优先极高的线程.
- 后台线程,JVM就属于后台线程,会在一个进程的所有非后台线程结后,才会结束运行.
- 是否存活,可以简单理解为 run 方法是否运行结束.
- 可以理解为有一个标志位,用于控制线程是否被中断,而此方法则是根据标志位来判断线程是否被中断.
Thread方法介绍
1.启动线程
start()方法
Thread t1 = new Thread(() -> System.out.println("lambda"));
t1.start();
start()方法和run()方法的区别.
- 调用start()方法是区别于当前线程另外开启一个新的线程去运行对象中的run方法.当前线程的后续代码是和新开的线程"并发"运行的.
- 调用run()方法则是在当前线程中调用run()方法运行.并没有开启一个新的线程.当前线程中的后续代码则需要等待run()方法运行完才可以执行.
2.中断线程
- 通过共享的标记来进行沟通,设置标志位,通过在其他线程控制标志位,来终止线程
public class Test9 {
private static volatile boolean isQuit = false;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int i=0;
while(!isQuit) {
System.out.println("t1: "+(i++));
try {
Thread.sleep(700);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("终止t1线程");
}
}
- 调用 interrupt() 方法来通知线程中断.
先来看两个获取线程标志位的方法
1.Thread.interrupted();
静态方法(一个程序只有一个标志位)
2.Thread.currentThread().isInterrupted();
实例方法,获取当前线程实例的标志位(每个线程有自己的标志位,推荐使用)
public class Test10 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int i=0;
while(!Thread.currentThread().isInterrupted()) {
System.out.println("t1: "+(i++));
try {
Thread.sleep(700);
} catch (InterruptedException e) {
//e.printStackTrace();
break;
}
}
});
t1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在主线程中调用 interrupt方法
t1.interrupt();//中断t1
System.out.println("终止t1线程");
}
}
调用线程的t1.interrupt()方法,来改变标志位,该方法有两种情况,
- 线程就绪状态,就会设置标志位为true.
- 线程阻塞状态(sleep),就会触发一个异常(InterruptException).
(解决方式)可以在catch块中使用break;中断线程
3.等待一个线程
join()方法
在a线程中调用b.join(),就是a线程阻塞等待b线程
join()方法在默认情况下是死等,可以给方法设置参数.单位为毫秒(超时时间)
public class Test11 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i=0;i<10;i++) {
System.out.println("t1: "+(i++));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
try {
//main阻塞等待t1
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程阻塞等待t1线程");
}
}
4.获取当前线程引用
Thread.currentThread();
静态方法:返回当前线程对象的引用
public class Test12{
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
5.休眠当前线程
Thread.sleep(3 * 1000);
静态方法:参数为休眠时间,单位为毫秒
作用:休眠调用该方法的线程.
需要注意的是这里设置的时间是在这个时间内不可以唤醒该线程,而不是时间结束就唤醒该线程.休眠时间结束后会进入就绪队列等待调用.