Java之多线程
Thread(耦合,不推荐)
Runnable(解耦,推荐)
Executors
ExecutorService
Callable
1. Thread概述
Thread类在使用的方法在JDK手册中有介绍,它实现了Runnable接口中的唯一方法run(), run方法是线程执行的主体,因此在使用的时候需要重写run()方法去定义线程需要执行的功能,并且自定义Thread类的子类。
线程的执行开始点是调用了Thread类中的start()方法,start方法中实际上是去调用重写的run方法,这样一个线程就开始执行了
JDK手册中对两种创建和启动线程的例子Thread构造方法:
Thread()
Thread(Runnable target,String name)
Thread(ThreadGroup group,Runnable target)
Thread(ThreadGroup group,Runnable target,String name)
Thread(ThreadGroup group,Runnable target,String name, long stackSize)
Thread(ThreadGroup group,String name)
2. 继承Thread类创建运行线程
main线程的退出不影响自定义线程的运行
自定义线程类MyThread,使用默认线程名的构造方法:
public class MyThread extends Thread{
long minPrime;
public MyThread() {}
public void run() {
int cnt = 0;
while(cnt != 5) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.printf(" ThreadName[%s] myThread cnt:%d\n", super.getName(), cnt);
cnt++;
}
out.println("myThread exit@@@@@@@@");
}
}
2.1. 自定义线程先退出
2.2. main线程先退出
3. 关于线程的命名
3.1. 获取线程名
在线程中使用方法:super.getName(); 因为Mythread是继承了父类Thread,supper也可以不写,但是是调用的Thread中的方法
在2中的线程没有对线程命名,默认是Thread-N(0,1,2,……,N),主线程的名字是Thread-main
3.2. 获取当前线程的引用来获取线程名
Thread类中的静态方法:static Thread currentThread();
这个静态方法返回当前线程的Thread对象引用,在线程中拿到了Thread对象就能调用getName()方法了
例:在main线程中不能直接按照3.1的方式来获取线程名,使用静态方法currentThread()来获取线程名。
3.2. 自定义线程名
我们也可以对每个线程自定义名字
3.2.1. 通过构造方法设置线程名
原理是Thread的构造方法除了空参构造方法,还可以加入线程名的构造方法
Thread(String name)构造方法3.2.2. 在启动线程之前设置线程名
4. 线程休眠sleep
static void sleep(long millis) 毫秒级休眠 1000 = 1s
static void sleep(long millis, int nanos) 纳秒级休眠 1/1000000000s = 1纳秒
静态方法直接调用,休眠1s 休眠1微秒5. 实现Runnable接口创建启动线程(推荐使用)
该方式是使用Thread的构造函数:Thread(Runnable target)
参数是实现了Runnable接口抽象方法的一个子类
public class RunnableThread implements Runnable{
public RunnableThread() {}
public void run() {
int cnt = 0;
Thread thd = Thread.currentThread();
while(cnt != 5) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.printf(" ThreadName[%s] myThread cnt:%d\n", thd.getName(), cnt);
cnt++;
}
out.println("myThread exit@@@@@@@@");
}
}
6. 继承Thread方式和实现Runnable接口 方式对比
7. 使用匿名内部类实现多线程
复习一下内部类
前提:
要有继承或者接口实现
语法:
new 父类或者接口(){
重写抽象方法abstractMethd(){
}
}
7.1. 方式一:子类继承Thread,匿名子类
其实就是子类继承Thread,子类匿名了7.2. 方式二:实现Runnable接口
7.3. 方式三:前2种方法的结合,匿名第二种方式的接口实现类
8. 线程的状态
9. 线程池
JDK5以前要使用线程池,开发人员需要用集合维护线程池:
常用ArrayList:
旧的线程池做法从JDK5开始,提供了内置线程池
Executors类提供了创建线程池功能,是一个工具类都是静态方法
线程池使用场景:
假如你要做一个服务器端,一直在后台运行不会退出重启等操作,那么线程池是一个很好的选择,它避免了频繁创建销毁线程的系统开销,而且当线程池中的线程运行结束,线程池会回收该线程资源让其阻塞等待下一次被激活
如果是非一直需要运行的后台服务器,则没有必要使用线程池,因为如果没有强行终止,线程池中的线程是会一直存在的,即便main线程退出也会存在
9.1. 使用Executors工具类创建线程池
Executors工具类中的方法创建线程池:static ExecutorService newFixedThreadPool(int nThreads)
参数nThreads:是要固定创建的线程数量
返回值ExecutorService:本身是一个接口,返回的是该接口实现类的对象
9.1.1 Future submit(Runnable task)
submit方法就是用来提交线程执行任务的,参数是Runnable接口的实现类
定义一个类SubThread实现Runnable接口中的run方法:
public class SubThread implements Runnable {
private String thdName = null;
private int intParam = 0;
public SubThread(String thdName, int param){
this.thdName = thdName;
this.intParam = param;
}
public void run() {
Thread thd = Thread.currentThread();
int cnt = 0;
thd.setName(thdName);
while(cnt != 5) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.printf(" ThreadName[%s] thdN[%s] base[%d] myThread cnt:%d\n", thdName, thd.getName(), intParam, cnt);
cnt++;
}
out.println("myThread exit@@@@@@@@");
}
}
9.1.2. 使用Callable创建线程池
Callable接口是一个跟Runnable功能类似的接口,但是功能比Runnable强大
call方法Callable接口中也只有一个方法来描述线程任务,类似于Runnable中的run方法,但是call方法有一个泛型返回值,且可以抛出异常,这是run方法做不到的
定义一个SubThreadCallable类:
import java.util.concurrent.Callable;public class SubThreadCallable implements Callable {
private String thdName = null;
private int intParam = 0;
public SubThreadCallable(String thdName, int param){
this.thdName = thdName;
this.intParam = param;
}
public Integer call() {
Thread thd = Thread.currentThread();
int cnt = 0;
thd.setName(thdName);
while(cnt != 5) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.printf(" ThreadName[%s] thdN[%s] base[%d] myThread cnt:%d\n", thdName, thd.getName(), intParam, cnt);
cnt++;
}
out.println("subThread exit@@@@@@@@");
return cnt;
}
}
从运行结果可以看出2行submit代码瞬间运行打印出main1,之后在Integer i1 = ft.get(); 这一行阻塞,使用Callable的实现类作为submit方法的参数,会等待所有线程获取到了返回值才会向下继续执行,但是线程是异步执行的,结果确是同步返回。
所以这种方式在main线程的最后一个返回值是阻塞的,跟python的线程很类似,改成异步锁效率更高,当释放一个线程就又使释放的线程又运行起来。
再不要求返回值的情况下,应该尽量采用Runnable这种方式。
改进版:
让结果返回到最后去获取9.1.3. void shutdown()
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
9.2. 非线程池 和 线程池 性能测试
计算1+2+3+……+1000的总和
思路:
要做线程池和非线程池的对比,就分两个程序测试,并计算得出结果的时间差
由于要想线程返回结果,目前只能用Callable方式来做(下一节可以用锁来做同步)
9.2.1. 使用非线程池计算
多次运算,diffTime是1600ms左右9.2.2. 使用Callable线程池
将1加到1000,分成2部分相加同时运行
Callable 可见,运行速度提升了一倍10. Daemon Thread守护线程
先看一段非守护线程的代码:
子线程是非守护线程由于子线程是非守护线程,主线程退出了,子线程依然在运行。
将子线程改为守护线程:
子线程设置为守护线将子线程设置为了Daemon true,当主线程退出,子线程也自动销毁了。