线程的创建
我们有许多种方式来创建一个线程,我们在这里主要阐述五种创建线程的方式。
1.继承Thread类
public class CreateThread1 {
static class MyThread extends Thread{
@Override
public void run() {
System.out.println("thread_0");
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
- 创建一个MyThread类继承Thread类
- 重写run方法
- start启动线程
2.实现Runnable接口
public class CreateThread2 {
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("thread_0");
}
}
public static void main(String[] args) {
Thread t2 = new Thread(new MyRunnable());
t2.start();
}
}
- 实现Runnable接口
- 重写run方法
- start启动线程
3.使用匿名内部类
这种方法的本质同1,2一样,只不过使用了匿名内部类,让我们用两个小例子来阐述这种方法的使用
//继承Thread类,使用匿名内部类的写法
public class CreateThread3 {
public static void main(String[] args) {
Thread t3 = new Thread(){
@Override
public void run() {
System.out.println("thread0");;
}
};
t3.start();
}
}
//实现Runnable接口,使用匿名内部类的写法
public class CreateThread4 {
public static void main(String[] args) {
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread_0");
}
});
t4.start();
}
}
4.使用lambda表达式
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
我们可以看到,Runnable接口是一个函数式接口,可以使用lambda表达式
从而,实现Runnable接口可以写成
public class CreateThread5 {
public static void main(String[] args) {
Thread t5 = new Thread(() -> {
System.out.println("thread_0");
});
t5.start();
}
}
线程的启动
在上一个部分,我们可以看到,我们通过t.start()
来启动线程,而没有通过t.run()
来启动线程,这是什么原因呢?
start()和run()的区别
在探究start()和run()的区别之前,我们来看一下这样一个例子
public class StartAndRun {
static class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+";\t"+i);
}
}
public MyThread(String name) {
super(name);
}
}
public static void main(String[] args) {
MyThread t1 =new MyThread("thread_1");
MyThread t2 =new MyThread("thread_2");
MyThread t3 =new MyThread("thread_3");
t1.start();//t1.run()
t2.start();//t2.run()
t3.start();//t3.run()
}
}
我们执行这么一段代码,可以得到如下的结果:
start | run |
---|---|
thread_2; 0 thread_3; 0 thread_1; 0 thread_2; 1 thread_1; 1 thread_3; 1 thread_2; 2 thread_3; 2 thread_1; 2 thread_1; 3 thread_2; 3 thread_3; 3 thread_1; 4 thread_2; 4 thread_3; 4 | thread_1; 0 thread_1; 1 thread_1; 2 thread_1; 3 thread_1; 4 thread_2; 0 thread_2; 1 thread_2; 2 thread_2; 3 thread_2; 4 thread_3; 0 thread_3; 1 thread_3; 2 thread_3; 3 thread_3; 4 |
左边为start()方法的执行结果,右边为run()方法的执行结果
我们可以看到,start方法的thread_?打印的顺序是thread_1,thread_2,thread_3为一组一起打印的,而run方法的thread_?则先是1,再是2,再是3。
这表明run方法没有启用多线程
事实也确是如此,我们查看start源码可以发现,真正创建线程的是start方法中调用的start0()
方法。run()
只是一个普通的成员方法,他只是为start()
方法提供一个“行动指南”。
结论:调用start方法,才真正的在操作系统的底层创建出一个线程,并将其置于就绪状态,随时准备调度cpu资源。run()
方法只是一个普通的成员方法,其为start()
提供一个“行动指南”。
线程的中断
当一个线程进入到工作状态,他就会按照重写的Run方法进行工作,有时候,我们需要停止这种工作状态,这里就涉及到我们停止线程的方式了。
目前常见的有以下两种方式来停止线程:
1.通过共享的标记来进行沟通
2.调用interrupt()方法来通知
共享标记
public class InterruptThread {
public static boolean flg = false;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!flg){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread_0");
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flg = true;
System.out.println("主线程结束");
}
}
通过共享一个标志flg
来控制线程
调用Interrupt()方法
public class InterruptThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
System.out.println("thread_0");
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
System.out.println("主线程结束");
}
}
- 通过Interrupt()来给进程添加一个中断标记,用
Thread.currentThread().isInterrupted()
来标记位的判断- 如果线程因为调用wait/join/sleep等方法而被阻塞挂起,则以InterruptException异常的形式通知,清除中断标志,当InterruptException出现的时候,要不要结束线程取决于catch中代码的写法。
- 正常情况下,只是内部一个中断标记被设置
Thread.interrupted()
会清除中断标记Thread.currentThread().isInterrupted()
不清除中断标记
线程的等待
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" working...");
}
};
Thread t1 = new Thread(target,"thread_1");
Thread t2 = new Thread(target,"thread_2");
System.out.println("thread_1 start...");
t1.start();
t1.join();
System.out.println("thread_1 end...,thread_2 start...");
t2.start();
t2.join();
System.out.println("thread_2 end...");
}
}
通过.join()
方法使线程等待,在哪个线程调用t.join()
方法,则哪个线程等待t的结束。
以上述代码为例,t1.join()
在主线程中书写,则t1结束后,主线程再继续往下执行。
线程休眠
线程的休眠我们在之前的代码中用的比较多了,我们通过Thread.sleep()
方法来使当前线程进行休眠。
但有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
获取当前线程的引用
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
我们用该方法来获取当前线程的引用。