目录
1.Tread类介绍
2线程的构造方法——创建线程
1.继承Thread类,重写run()方法
2.使用Runnbable接口创建线程
3.继承 Thread, 重写 run, 使用匿名内部类
4.实现 Runnable, 重写 run, 使用匿名内部类
5.使用 lambda 表达式(重点掌握)
3.Tread类常见方法解读
3.1Tread类常见构造方法
3.2 Tread类的几个常见属性
3.3启动一个线程-start()方法
3.4中断一个线程
3.5等待一个线程-join()
3.6休眠线程
3.7 实现一个简单的多线程
4.线程的状态
4.1线程的六种状态
4.2线程状态和状态转移
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联,每个执行流(线程),也需要有一个对象来描述, Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度、线程管理。
无论使用哪一个方法创建线程,我们都需要将其中的run方法重写(run方法中写入的是线程需要执行的任务),它的作用就相当于主线程的main方法。
这个构造方法没有参数,通过创造一个类继承Thread类来实现线程 ,线程必须重写run()方法才算完成。当我们new一个线程时,此时的线程还没开始执行,再用创建的类调用start()方法才算运行了这个线程。
class TestTread extends Thread{@Overridepublic void run() {System.out.println("this tread1");}
}
public class testDemo2 {public static void main(String[] args) {Thread thread=new TestTread();thread.start();}}
我们可以看出,该构造方法的参数类型是一个接口类,因此我们需要创建一个类来实现这个接口,再new一个实现Runnable的类对象,再new一个线程,将之前创建的对象放入到参数这里。
class TestTread2 implements Runnable {@Overridepublic void run() {System.out.println("this t2");}
}
public class testDemo2 {public static void main(String[] args) {//new一个实现Runnable接口的对象TestTread2 testTread2=new TestTread2();//传入参数完成线程的创建Thread t2=new Thread(testTread2);t2.start();}}
使用匿名内部类可以省略创建对象的过程,可以减少资源消耗。
public class testDemo3 {public static void main(String[] args) {//通过Thread匿名内部类的方法创建一个线程Thread t1=new Thread() {@Overridepublic void run() {System.out.println("this t1");}};t1.start();}}
这个和3一样,可以少创建一个对象,减少资源的消耗。
public class testDemo4 {public static void main(String[] args) {Thread t1=new Thread(new Runnable() {@Overridepublic void run() {System.out.println("this t1");}});t1.start();}
}
它的实现原理和匿名内部类相似,这点不了解lambda表达式的,可以去看一下如何使用。
public class testDemo4 {public static void main(String[] args) Thread t2=new Thread(()->{System.out.println("this t2");});t2.start();}
}
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】Thread(ThreadGroup group, Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可 |
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
调用 start 方法, 才算是在操作系统的底层创建出一个线程.
public class testDemo4 {public static void main(String[] args) {Thread t1=new Thread(new Runnable() {@Overridepublic void run() {System.out.println("this t1");}});t1.start();}
}
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
public class testDemo6 {public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(() ->{while (!Thread.interrupted()) {System.out.println(Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}}});t1.start();Thread.sleep(1000*2);t1.interrupt();}
}
t1收到通知的方式有两种:
1.如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志.
- 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程(加break).
2.否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
标志位是否清除, 就类似于一个开关.
Thread.Interrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 "清除标志位".
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 10; i++) {// //打印标准位System.out.println(Thread.interrupted());}});thread.start();thread.interrupt();}
}
结果:
观察结果可以看出,已知interrupted()初始是true,之后就打印的值就是false,因为标志位已经被删除了。
标志位是否清除, 就类似于一个开关.
Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为"不清除标志位".
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 10; i++) {// //打印标准位System.out.println(Thread.currentThread().isInterrupted());}});thread.start();thread.interrupt();}
}
结果:
观察结果可以清晰的看出,打印的全是true,这是因为标志位没有被删除,它的值还是true。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
public class testDemo7 {public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println("第"+i+"次打印"+Thread.currentThread().getName());}System.out.println("----------");});Thread t2=new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println("第"+i+"次打印"+Thread.currentThread().getName());}});t1.start();//t2等待t1执行完毕,t2才可执行t1.join();t2.start();}
}
结果:
可以看出使用了join(),两个线程不再是杂乱运行了,而是先运行完t1线程,再运行的t2线程。这就是join 的作用。
这个是比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
这两个线程任务都是打印自己的名字,其中使用的currentThread()方法是或者当前线程的引用,getName()方法就是或者线程的名字(就算我们没有给线程起名字,系统也会给它自定义一个名字)。
public class testDemo5 {public static void main(String[] args) {Thread t1=new Thread(()->{while (true) {//打印线程名称System.out.println(Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2=new Thread(()->{while (true){//打印线程名称System.out.println(Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//执行线程任务t1.start();t2.start();}
}
代码实现结果:
观察可以看出,两个线程是交叉运行,并且是杂乱运行,好不规律可言。没错,线程的并发就是没有规律的,谁先运行取决于操作系统如何调度线程,因此线程是"抢占式执行"。
举个例子,看下图:
上一篇:性能优化之防抖与节流
下一篇:miller素数判断