有句话说前头:
Java使用Thread类表示线程,所有线程都会是Thread类或者其子类的实例。
你所能想到的创建方式
先来说说最常用的实现多线程的两种方式:Thread和Runnable
- Runnable是一个接口,包含一个run()方法
public interface Runnable{
public abstract void run();
}
通过实现Runnable的方式来创建线程,实现多线程。比如:
class MyThread implements Runnable {
private int i;
//run方法:线程执行体
public void run() {
for (; i < 20; i++) {
//当线程类实现Runnable接口时
//如果想要获得当前线程,只能用Thread.currentThread()方法
System.out.println(Thread.currentThread().getName()+ " " + i);
}
}
public static void main(String[]args){
//启动线程
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 2) {
MyThread mt = new MyThread();
//通过new Thread(target,name)方法创建新线程
new Thread(mt, "新线程1").start();
new Thread(mt, "新线程2").start();
}
}
}
}
运行结果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
新线程1 0
新线程1 1
新线程1 2
新线程1 3
新线程1 4
新线程1 5
新线程1 6
新线程2 6
新线程2 8
新线程2 9
新线程2 10
新线程2 11
新线程2 12
新线程2 13
新线程2 14
新线程2 15
新线程2 16
新线程2 17
新线程2 18
新线程1 7
新线程2 19
从Java8开始,Runnable接口使用了@FunctionalInterface修饰,也就是说,Runnable是函数式接口。
- Thread是一个类,本身就实现了Runnable接口,声明如下:
public class Thread implements Runnable{}
实例如下:
class MyThread extends Thread {
private int i;
//run方法:线程执行体
public void run() {
for (; i < 20; i++) {
//当线程类实现Runnable接口时
//如果想要获得当前线程,只能用Thread.currentThread()方法
System.out.println(Thread.currentThread().getName()+ " " + i);
}
}
public static void main(String[]args){
//启动线程
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 2) {
MyThread mt = new MyThread();
//通过new Thread(target,name)方法创建新线程
new Thread(mt, "新线程1").start();
new Thread(mt, "新线程2").start();
}
}
}
}
执行结果如下:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
新线程1 0
新线程2 0
新线程2 1
新线程2 2
新线程2 4
新线程2 5
新线程2 6
新线程2 7
新线程2 8
新线程2 9
新线程2 10
新线程2 11
新线程2 12
新线程2 13
新线程2 14
新线程2 15
新线程2 16
新线程2 17
新线程2 18
新线程2 19
新线程1 20
通过上边的运行结果我们可以发现,线程1和线程2共享线程资源,一共创建了20个线程(共享MyThread)
- 使用Callable和Future创建线程
从Java5之后提供了一种callable接口来创建线程,Callable接口提供了一个call()方法可以作为线程的执行体,和run()方法相比call()方法更为强大,如:
- 可以有返回值
- 可以声明抛出异常
因此可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该对象的call方法,但是Callable不是Runnable的子接口,所以Callable对象不能直接作为Thread的target,而需要借助Future接口来代表Callable接口里call()方法的返回值,Future接口提供了一个FutureTask实现类,此类不仅实现了Future接口还实现了Runnable接口。
实例如下:
class MyThread {
public static void main(String[] args) {
//创建Callable对象
MyThread mt = new MyThread();
//先使用Lambda表达式创建Callable<Integer>对象
//使用FutureTask包装callable对象
FutureTask<Integer> task = new FutureTask<>((Callable<Integer>) () -> {
int i = 0;
for (; i < 20; i++) {
//当线程类实现Runnable接口时
//如果想要获得当前线程,只能用Thread.currentThread()方法
System.out.println(Thread.currentThread().getName() + " " + i);
}
//call方法返回值
return i;
});
//启动线程
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 2) {
//实质上还是使用callable对象创建并启动线程的
new Thread(task, "有返回值的线程").start();
}
}
try {
//获取线程返回值
System.out.println("子线程返回值: " + task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行结果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
有返回值的线程 0
有返回值的线程 1
有返回值的线程 2
有返回值的线程 3
有返回值的线程 4
有返回值的线程 5
有返回值的线程 6
有返回值的线程 7
有返回值的线程 8
有返回值的线程 9
有返回值的线程 10
有返回值的线程 11
有返回值的线程 12
有返回值的线程 13
有返回值的线程 14
有返回值的线程 15
有返回值的线程 16
有返回值的线程 17
有返回值的线程 18
有返回值的线程 19
子线程返回值: 20
孰优孰劣???
很难讲哪种方式就一定好,需要针对于具体的场景来说
采用实现Runnable、Callable接口的方式创建多线程:
只是实现了Runnable接口或者Callable接口,还可以继承其他类
多个线程可以共享一个target对象,非常适合多个相同的线程处理同一份资源的情况,从而将CPU、代码和数据分开,形成清晰的模型
但是不足的就是编程复杂,如果要访问当前线程需要使用Thread.currentThread()方法
使用继承Thread类的方式创建多线程:
编程简单,如果需要访问当前线程只需要用this指针就可以
但是不足的就是因为继承了Thread类,就不能再继承其他父类了
简单总结一下:一般我们推荐的是使用实现Runnable接口或者Callable接口的方式来创建多线程