Java中线程的3种创建方式:
- 继承Thread类
创建自定义线程类并继承Thread类–>重写run()–>实例化自定义线程类–>调用自定义实现类的start()启动线程
public class ThreadDemo extends Thread {
@Override//重写run()
public void run() {
System.out.println(Thread.currentThread().getName()+" subthread");
}
}
public class App {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" main");//打印当前线程(主线程)的线程名
ThreadDemo threadDemo=new ThreadDemo();//实例化自定义的线程类
threadDemo.start();//开启线程
}
}
- 实现Runnable接口
创建自定义线程类并实现Runnable接口–>实现Runnable中的run()–>实例化自定义的线程类–>将创建的实例当作Thread类构造方法的参数来实例化Thread类–>调用Thread类实例的start()方法启动线程
public class RunnableDemo implements Runnable {//创建自定义线程类
@Override//实现run()
public void run() {//一个线程所有的操作都要在run()中
System.out.println(Thread.currentThread().getName()+" subthread");//打印当前线程线程名
}
}
public class App {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" main");//打印当前线程(主线程)的线程名
RunnableDemo runnable = new RunnableDemo();//实例化自定义线程类
Thread thread=new Thread(runnable);//实例化Thread类
thread.start();//开启一个线程
}
}
- 实现Callable接口
创建自定义线程类并实现Callable类–>实现call()方法–>实例化自定义线程类–>将自定义线程类的实例当作参数来实例化FutureTask类–>将FutureTask类的实例当作Thread类构造方法的参数来实例化Thread类–>调用Thread类实例的start()方法启动线程
import java.util.concurrent.Callable;
public class CallableDemo implements Callable<Integer> {
@Override//实现call()
public Integer call() throws Exception {
int i=100;
System.out.println(Thread.currentThread().getName()+" subthread");
return i;//返回当前线程的执行结果
}
}
import java.util.concurrent.*;
public class App {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" main");//打印当前线程(主线程)的线程名
CallableDemo callable=new CallableDemo();//实例化自定义线程类
FutureTask<Integer> result = new FutureTask<>(callable);
Thread thread = new Thread(result);
thread.start();
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
三种方法对比:
- 继承Thread类为单继承,而实现接口为多实现,所以实现接口方式在使用上比较灵活。
- 实现接口创建的线程可以放入线程池来管理,而继承Thread类创建的线程不可以放入线程池。
- Thread类底层实现了Runnable接口,底层也实现了run()方法,我们继承Thread类需要重写该方法;而实现Runnable接口需要实现run()方法。可以试一下,当我们继承Thread类时,不重写run()方法不会报错,而实现Runnable接口,不实现run()会报错。
- 继承Thread类的线程类的实例直接调用start()方法去启动该线程;
实现Runnable接口的线程类的实例需要作为参数来实例化Thread类,然后再由Thread类实例调用start()去启动线程;
实现Callable接口的线程类的实例需要作为参数来实例化FutureTask类,然后再把FutureTask类实例作为参数来实例化Thread类,然后再由Thread类实例调用start()去启动线程。 - 继承Thread类和实现runnable接口创建的线程的操作都在run()方法中;
实现Callable接口创建的线程操作都在call()方法中,而且在实现接口时可以指定一个参数类型作为call()返回值的类型,call()方法是一个有返回值而且抛异常的方法。返回值由FutureTask负责获取。
总结:继承Thread类这种方法使用起来比较方便,但底层还是实现Runnable接口;实现Runnable接口的方法启动线程需要用Thread类的start()方法或放在线程池中启动;实现Callable接口的方法也需要用Thread类的start()方法或放在线程池中启动,而且需要先实例化FutureTask,但可以得到线程执行的返回值。
注意:
调用run()/call()只是调用执行该方法,并没有开启线程,还是单线程(主线程)在运行,必须调用start()或者放入线程池才能启动线程,实现多线程并发执行。每个线程的start()只能调用一次!重复调用会报错。
一些常用方法:
- Thread.currentThread().getName() ; 获取当前线程的名称
- thread.setName(""); 继承Thread类创建的线程可以调用此方法设置线程名称
- Thread thread=new Thread(runnable,"");实现Runnable接口创建的线程在实例化Thread时可以传一个String型参数来设置线程名
- Thread thread = new Thread(result,"");实现Callable接口创建的线程在实例化Thread时可以传一个String型参数来设置线程名
- result.cancel(true);FutureTask中的cancel()用来取消该线程的执行
我在获取结果的代码前加了cancel(),打印结果会报错,因为没有执行就没有结果。 - Thread.currentThread().getState();获取线程的状态
- Thread.currentThread().setDaemon(true);设置线程为守护线程(如GC)
- Thread.sleep(long time);让当前线程阻塞(睡眠)time,单位是ms,后面可以再传一个参数,设置时间单位。会抛interruptedException异常。是静态方法。
- Thread.yield();让正在执行的线程让步或暂停,让优先级高或相等的线程来执行。(如果没有优先级比当前线程高或相等的线程,或当前只有一个线程在执行,当前线程会立即获取cpu使用权);是一个静态方法,如果不是静态方法,得创建Thread实例才能调用该方法。
- Thread中的join();让正在执行的线程等待,join()的调用方先执行。可以控制程序的执行顺序。例如:在a线程中,b调用了join(),那就让a线程等b执行完再执行。传时间参数可以设置的等待时间,时间过后以前等待的线程就开始执行,而不是等调用方执行完才开始执行。可以传第二个参数设置时间单位,类似sleep()。会抛InterruptedException异常。
//让ABC三个线程按照BAC的顺序执行
class Thread1 extends Thread{
private Thread2 thread2;
public Thread1(Thread2 thread2) {
this.thread2=thread2;
}
@Override
public void run() {
try {
thread2.join();//让A等待,先执行B
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 执行A");
}
}
class Thread2 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 执行B");
}
}
class Thread3 extends Thread{
private Thread1 thread1;
public Thread3(Thread1 thread1) {
this.thread1=thread1;
}
@Override
public void run() {
try {
thread1.join();//让C等待,先执行A
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 执行C");
}
}
public class Test {
public static void main(String[] args) {
Thread2 thread2=new Thread2();
Thread1 thread1=new Thread1(thread2);
Thread3 thread3=new Thread3(thread1);
thread1.setName("A");
thread2.setName("B");
thread3.setName("C");
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果:
不用join()的话,可能每次的执行顺序都不同,可以自己试一下。
- Thread中的interrupt():中断操作,中断调用该方法的线程。底层调用了一个native方法interrupt0(),这个native方法是修改该线程的中断标志位为中断。
如果作用于运行中的线程,只是修改线程的中断标志位,不会中断线程;
如果作用于阻塞线程(调用了sleep()、join()),修改中断标志位,并令当前阻塞中的线程感知到标志位做了修改,就会中断阻塞,抛出InterruptedException异常。 - Thread中的IsInterrupted():判断调用该方法的线程中断标志位是否被修改为中断,返回值为boolean型,true为修改,false为未修改。
- Thread中的setDaemon():传参为true–>设为守护线程;false–>设为用户线程(默认)。
- Thread中的isDaemon():判断当前线程是否为守护线程,返回true为守护线程。
守护线程:脱离于控制终端,提供一些通用服务的线程。如负责垃圾回收的线程(GC)。生命周期依赖于用户线程,当有用户线程存在就会有守护线程。
用户线程:进入run()到出run()为用户线程的生命周期。 - Thread中的setPriority(int newPriority):设置线程的优先级(1~10)。默认为5,最小为1,最大为5;数字越大,优先级越高,被优先执行的概率越大;优先级设置的相近(如4、5)可能执行的顺序为4比5早。
这里设置的优先级只是设置被cpu优先执行的概率,最终执行的先后取决于操作系统。如:设置一个线程的优先级为2,操作系统中可能有其他优先级高于2的本地线程,可能这个优先级为2的线程永远无法执行,为了避免这种情况,操作系统为每个线程设置权重,每次遍历队列发现某线程未执行就给它的权重+1,过一段时间可能这个优先级为2的线程权重变得很大,优先级也就改变了。 - Thread中的setPriority():获取当前线程的优先级。