目录
1.创建线程
2.1 继承Thread类,重写run方法
2.2 实现Runnable,重写run方法
2.3 使用匿名内部类继承Thread类,重写run方法
2.4 使用匿名内部类实现Runnable接口,重写run方法
2.5 使用lambda表达式
3.Thread类的构造方法
4.Thread类的常见属性的获取方法
5.线程方法的使用
5.1 前台线程与后台线程
5.2 线程的执行顺序
5.3 线程休眠
5.4 线程中断
5.5 线程等待
5.6 获取线程实例
在我们没有使用多线程编程之前,我们代码的运行全部都由main线程(main线程也是主线程)独自运行,而使用多线程的编程之后,我们的代码可以在多个线程上"同步"运行,下面先介绍创建线程的5种方法
class MyThread extends Thread{
@Override
public void run() {
while(true) {
System.out.println("hello Thread");
}
}
}
public class ThreadDemo1 {
public static void main(String[] args){
Thread t = new MyThread();
t.start();
while(true) {
System.out.println("hello main");
}
}
}
首先创建一个类,由这个类去继承Thread类,重写其中的run方法
run方法: 创建出来的线程去执行的内容
start方法: 创建出一个新的线程,并让其去执行run方法里面的内容
执行上面的代码,会出现hello Thread 和 hello main交替打印的情况
他们的打印杂乱无章,可能是t线程(创建出来的线程)先打印,也可能是main线程先打印
这是由于: 当执行完t.start()之后,创建出来一个新的线程(简称t线程),t线程和main线程同属于在同一个进程上,他们在操作系统内核中的调度情况可能是并行,也可能是并发,是无法预知的,所以打印的前后顺序也是无法预知的.
sleep方法: sleep方法是Thread类中的一个静态方法,它可以让线程进入短暂休息的状态,比如sleep(1000) 就是休息1秒.(注意:sleep方法需要处理异常)
有了sleep方法,就可以更直观的去观察线程的输出情况,如下:
class MyThread extends Thread{
@Override
public void run() {
while(true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args){
//创建一个Thread对象,new MyThread()
Thread t = new MyThread();
t.start();
while(true) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello Thread");
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
}
}
首先创建一个类,实现Runnable接口,重写里面的run方法.
将Runnable对象传到Thread的构造方法中,然后t.start()线程就创建好了.
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("hello Thread");
}
};
t.start();
}
}
和2.1 类似,只不过使用了匿名内部类.
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello Thread");
}
});
t.start();
}
}
和 2.2 类似,将类实现替换成内部类实现
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("hello Thread");
});
t.start();
}
}
仿照代码中去写就好了,大括号里面的内容就相当于重写run方法的内容
方法1:Thread()
创建线程对象
方法2:Thread(Runnable r)
使用Runnable对象创建线程对象
方法3:Thread(String name)
创建线程对象,并初始化线程的名字
方法4:Thread(Runnable r,String name)
使用Runnalbe对象创建线程,并初始化线程的名字
(1) getId()
获取线程的ID
(2) getName()
获取线程的名字
(3) getState()
获取线程的状态
(4) getPriority()
获取线程的优先级
(5) isDaemon()
判断线程是否为后台线程(true/false)
(6) isAlive()
判断线程是否存活(true/false)
(7) isInterrupted()
判断线程是否被中断(true/false)
前台线程: 会阻止进程结束,如果前台线程没有执行完,进程是无法结束的.
后台进程: 不会阻止进程结束,即使后台线程没有执行完,进程也是可以结束的.
我们在main方法中创建的线程会默认为前台线程,但是我们可以使用setDaemon()方法去设置线程
如下面的代码:
当执行完t.setDaemon(true) 之后,t线程就被设置成了后台线程,此时当主线程(main线程)执行完之后,t线程也就结束了.
因为当main线程执行晚最后一条语句时,它的任务就完成了,main线程会自动销毁(t线程如果执行完run方法的内容后也会自动销毁),而此时这个进程中只有main一个前台线程,当main线程被销毁后,其他的后台线程也就结束了.
public class ThreadDemo6 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("hello Thread");
}
}
}, "MyThread");
t.setDaemon(true);
t.start();
}
}
线程的执行顺序是不确定的,如下面这个代码:
public class ThreadDemo7 {
public static void main(String[] args) {
Thread t = new Thread(()->{
for(int i = 0;i < 3;i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
for(int i = 0;i < 3;i++) {
try {
Thread.sleep(1000);
System.out.println("hello main");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
当执行完 t.start() 之后,t线程被创建,此时t线程执行run方法中的内容,同时main线程继续执行main方法中下面的内容,此时,main线程遇到sleep(),休眠1s,所以会先打印 "hello Thread" ,而之后会先打印谁,后打印谁是不确定的.
因为在微观上,操作系统内核在调度两个线程时是使用并行还是并发是不确定的,也就导致了打印的顺序是不确定的.
线程的休眠和前面提到的sleep方法有关
当线程处于sleep状态时,就是处于休眠状态
如下:
public class Test {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
当前这个t线程就是处于休眠状态.
线程中断相当于线程终止(结束),这个操作完全取决于设计线程里面的代码,我们给出的中断信息,只是传递了一个让其中断的信息,至于是否会真的被中断、被什么时候中断都是不确定的.
而传递中断信息可以有两种方法:
第一种:使用自己设置的标志位去传递中断信息
public class ThreadDemo8 {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread(() -> {
while(flag) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Thread.sleep(3000);
flag = false;
}
}
我们设置了一个boolean类型的静态成员变量 flag,让它成为我们的标志位,当我们将flag设置为false时,程序就会停止,至于会迅速停止的原因也是因为对run方法的设计.
第二种:使用Thread类自带的标志位
public class ThreadDemo9 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//等待0.5s在进行终止
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
e.printStackTrace();
}
break;
}
}
});
t.start();
Thread.sleep(3000);
t.interrupt();
}
}
Thread类中有一个方法是Thread.currentThread().isIntercurrpted(),它的返回值默认为false
当外部调用t.interrupt()时 它的返回值会转变为true.
但是如果调用t.interript()方法时,t进程处于休眠状态(sleep状态),会立即被唤醒,被唤醒之后sleep会报出异常,但是代码仍然会继续执行.
此时sleep会进行清除标志位的操作,也就是将修改为true的标志位,修改回false
那么,为什么sleep要进行清除标志位的操作呢?
因为当程序被唤醒后,它不清楚程序是否要进行中断操作,将这个问题抛给了程序员,此时线程是否中断,完全取决于程序员设计的代码.
比如上面的事例代码,我在里面加入了break操作,让循环终止,程序结束,那么线程也就随之销毁,是执行了中断操作;而我又在break前面加上了一个sleep,让其先休眠 0.5s 再进行中断操作,这也就对应上了我最后开始说的:线程是否被中断,取决于其中代码的实现.
线程等待使用的操作是Thread中的join()方法,它用类的实例来调用.
当执行t.join()方法后,当前执行的线程就会进入等待(也成为线程阻塞),等到t线程执行完,才可以继续执行当前的线程.
如下代码:
public class ThreadDemo10 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for(int i = 0;i < 3;i++) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//如果在join之前t线程也就结束了,join就不会再阻塞,会立即返回
//Thread.sleep(5000);
System.out.println("join 之前");
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("join 之后");
}
}
上面的代码的执行顺序是如下这样:
join 之前
...(t线程的三遍 hello Thread 打印)
join 之后
也就是main线程等待t线程,当t线程执行完毕,join()才让main线程停止等待
而如果在执行到 t.join() 之前join线程就执行完了,那么main线程就不会等待.
比如:如果将上面代码注释的sleep(5000)打开,那么执行的结果就会变成下面的情况:
...(t线程的三遍 hello Thread 打印)
join 之前
join 之后
Thread.currentThread()方法
它是一个类方法,作用是获取当前线程的实例
也就是说: 你在哪个线程中去调用这个方法,那么它就会返回这个线程的实例对象.
public class ThreadDemo11 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
},"t线程");
t.start();
}
}
此时上面的代码输出为 -- t线程