目录
一. 线程的定义方法
线程有五种定义方法
- 第一种: 创建一个类继承Thread
class thread1 extends Thread{
@Override
public void run(){
System.out.println("thread1");
}
}
public class TestDemo {
public static void main(String[] args) {
//第一种方式:继承Thread
Thread thread1 = new thread1();
thread1.start();
}
}
- 第二种: 创建一个类实现Runnable接口
class thread2 implements Runnable{
@Override
public void run() {
System.out.println("thread2");
}
}
public class TestDemo {
public static void main(String[] args) {
//第二种方式:实现Runnable接口
Thread thread2 = new Thread(new thread2());
thread2.start();
}
}
- 第三种: 使用匿名内部类
public class TestDemo {
public static void main(String[] args) {
//第三种方式:匿名内部类
Thread thread3 = new Thread(){
@Override
public void run() {
System.out.println("thread3");
}
};
thread3.start();
}
}
- 第四种: 使用Runnable的匿名内部类
public class TestDemo {
public static void main(String[] args) {
//第四种方式:Runnable的匿名内部类
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread4");
}
});
thread4.start();
}
}
- 第五种: lambda表达式, 本质上还是实现了Runnable接口
public class TestDemo {
public static void main(String[] args) {
//第五种方式:lambda表达式,这里是代替了Runnable接口
Thread thread5 = new Thread(() ->{
System.out.println("thread5");
});
thread5.start();
}
}
这么多种定义方式中, 推荐使用Runnable,因为它只描述了任务,不关心由谁来执行,并且接口不关心多继承问题.
二. 线程的并发执行
这里我们实现两个线程一起运行案例:
这里先创建一个Thread类,内部的sleep方法用于让线程休眠
class MyThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("hello world");
try {
Thread.sleep(1000);//强制让线程休眠1s,进入阻塞状态
//1000的含义是1000ms内上不了CPU
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
然后创建main函数线程,main函数启动时会自带一个线程
public class TestDemo2 {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();//创建线程
//这是main方法的线程,两个线程同时发生了并行或者并发
//宏观区分不了并行并发,取决于系统的调度,所以统称为并发
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//休眠1s后唤醒哪个线程不知道,顺序完全随机(抢占式执行)
}
}
看运行结果,可以看出两个线程交替运行
由于休眠1s后唤醒哪个线程不知道,所以执行顺序是随机的,也叫抢占式执行.
三. 查看线程效率
这里再来写一个案例:对一个数字进行10亿次自增,测试用循环和线程来执行的时间
先写一个普通循环的方法: (注意这里的测试数据不能太小, 因为创建线程也需要时间)
//这里测试数据也不能太小,因为创建线程也需要时间
public static final long Default_Test_Num = 10_0000_0000;
//普通循环:
public static void serial(){
long beg = System.currentTimeMillis();
long a = 0;
for (long i = 0 ; i < Default_Test_Num; i++){
a++;
}
long b = 0;
for (long i = 0 ; i < Default_Test_Num; i++){
b++;
}
long end = System.currentTimeMillis();
System.out.println("循环执行时间:" + (end - beg) + "ms");
}
再来一个利用线程的方法;
public static void concurrency() throws InterruptedException {
long beg = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
long a = 0;
for (long i = 0 ; i < Default_Test_Num; i++){
a++;
}
});
t1.start();
Thread t2 = new Thread(() -> {
long b = 0;
for (long i = 0 ; i < Default_Test_Num; i++){
b++;
}
});
t2.start();
//此处不能直接这样记录结束时间,因为求时间戳的代码是在main线程中
//main与t1,t2是并发执行的,此时两个线程没执行完,就直接记录main函数结束时间了
//应该让main等待两个线程跑完了再记录结束时间
t1.join();//main等待t1执行完
t2.join();//main等待t2执行完
long end = System.currentTimeMillis();
System.out.println("线程执行时间:" + (end - beg) + "ms");
}
其中用到了join方法,此方法用于让main线程等待线程执行完
如果不加的话,main线程会提前结束,不会等待线程执行完,这样的测试数据不准确.
这里我们用到了如下的时间戳来对程序计时
long beg = System.currentTimeMillis();
//执行的代码
long end = System.currentTimeMillis();
System.out.println("线程执行时间:" + (end - beg) + "ms");
结果如下, 线程的执行时间明显要快不少
四. Thread类中的方法
首先介绍两个方法:
- isDaemon() 是否是后台进程. t1,t2默认都是前台进程,即使main结束,他俩也不会退出,都执行完才退出 但是后台进程在main结束后就退出,他俩被强行终止
- isAlive() 是否存活. start执行之前就是false,之后就是true
接下来是run和start方法的区别:
- run只是单纯得描述了任务的内容,在main中创建就是运行在main线程中,从前到后运行
- start则是一个特殊的方法,内部会在系统中创建线程
最后来介绍一下线程名:
我们可以给线程取名,方便在 jconsole(jdk的bin目录的文件) 中观察此线程
如下代码给 t1, t2 线程取了名字
public static void main1(String[] args) {
//线程也可以用构造方法去取名,再jconsole中观察
Thread t1 = new Thread(() ->{
while (true){
System.out.println("thread1");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread t1");
t1.start();
Thread t2 = new Thread(() ->{
while (true){
System.out.println("thread2");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "thread t2");
t2.start();
}
再运行此代码, 进入jconsole观察一下:
这个就是正在运行的线程名称.
如果我们想在IDEA中获取线程名称,也可以如下方式:
public static void main1(String[] args) {
Thread t = new Thread(() -> {
//System.out.println(this.getName());//Runnable接口没有name属性
//所以在Runnable接口中想要获取名字只能用currentThread()
System.out.println(Thread.currentThread().getName());
});
t.start();
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println(this.getName());//Thread类才有name属性
}
};
System.out.println(Thread.currentThread().getName());
}
这里注意, 如果线程用Runnable接口实现,是没有name属性的
需要用currentThread方法获取实例后才能获得名字.
五. 中断线程
想要中断线程,我们可以设置一个标志位,在运行到一定次数后,改变标志位就可以中断线程.
这里列举两种方式:
- 第一种方式:自己设置一个标志位来控制线程结束,但不够严谨
/*第一种方式*/
public static boolean isQuit = false;
public static void main1(String[] args) {
Thread t = new Thread(() -> {
while (!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//过了5s后,更改这个标志位,让这个线程停下来
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("线程中断");
}
- 第二种方式:使用Thread中内置的一个标志位来判定
Thread内置标签有个:
Thread.interrupted(); | 这是一个静态方法 |
Thread.currentThread().isInterrupted(); | currentThread()能获取当前线程的实例 (推荐) |
这里用第二种来写一下:
/*第二种方式*/
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();
//触发异常后,立即退出
System.out.println("ByeBye!!!");
break;
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
/*此方法可能会有两种情况:
1)如果t线程是处在就绪状态,就是设置线程的标志位为true
2)如果t线程处在阻塞状态,就会出发一个异常
我们这里的代码每次都会sleep进入阻塞状态,所以会报异常
*/
}
这里的 interrupt() 就充当了改变标志位的身份, 能够中断线程
但此处结果报了一个异常,因为此方法会面对两种情况:
- 如果t线程是处在就绪状态,就是设置线程的标志位为true
- 如果t线程处在阻塞状态,就会出发一个异常
因为每次都在阻塞,所以会报异常.
最后,报了异常后我们要注意及时break退出.
六. 线程等待
上面讲过join方法,此方法也可以给上参数,表示线程等待的时间.
public class TestDemo6 {
public static void main1(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("hello thread");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//join就是让main等待t执行完,此时main在阻塞状态
//t.join();
//但是总不能死等吧,所以join提供了另一个版本,可以设置一个最长等待时间
t.join(10000);//超过10s,不等了
}
}
谢谢你能看到这(ง •_•)ง