1、核心一:多线程的创建方式
官网说法:实现多线程一共有两种方法💟
- 实现Runnable接口
- 继承Thread类
/**
* 用 Runnable方式创建线程
*/
public class RunnableStyle implements Runnable{
public static void main (String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run () {
System.out.println("用 Runnable方式创建线程");
}
}
/**
* 用 Thread方式实现线程
*/
public class ThreadStyle extends Thread{
@Override
public void run () {
System.out.println("用 Thread方式实现线程");
}
public static void main (String[] args) {
new ThreadStyle().start();
}
}
注意:方法1更好💟
- 从代码架构的角度考虑,具体执行的任务(run方法的内容),它应该和线程创建以及运行的机制(即Thread类)是解耦的。两者不可混为一谈。所以从解耦的角度方法1更好。【Runnable就相当于一个任务,而Thread才是真正的处理线程,我们需要的只是定义这个任务,然后将任务交给线程去处理,这样就达到了松耦合】
- 从资源节约的角度,Java中的线程实例是一个“特殊”的Runnable实例,因为在创建它的时候JVM会为其分配调用栈空间、内核线程等资源。因此,创建一个线程实例比起创建一个普通的Runnable实例来说,其成本相对高。使用方法2,每次我们想要新建一个任务,只能去新建一个独立的线程(这样损耗比较大,需要创建和执行以及销毁)。如果使用方法1,我们就可以利用后面的线程池等工具(我们只需要创建Runnable实例,而不用创建一个线程实例),大大减少创建线程和销毁线程所带来的损耗
- 使用方法2,一个类只能继承一个类,大大限制了可扩展性
两种方法的本质区别:
方法1:最终调用target.run()
方法2:run()整个都被重写
彻底搞懂Java中的Runnable和Thread-腾讯云开发者社区-腾讯云 (tencent.com)
1.1 源码解读💟
💟就是run()的源码。明白两种创建线程的本质区别
重要字段
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
public class Thread implements Runnable {
private Runnable target;//这个很重要
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
方法1
public class RunnableStyle implements Runnable{
public static void main (String[] args) {
Thread thread = new Thread(new RunnableStyle());//1
thread.start();//2
}
@Override
public void run () {
System.out.println("用 Runnable方式创建线程");
}
}
1Thread thread = new Thread(new RunnableStyle());
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
⬇
public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {
this(group, target, name, stackSize, null, true);
}
⬇
private Thread(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(
SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
this.tid = nextThreadID();
}
看到了没,我们创建的 Runnable 对象(new RunnableStyle()
)最后被赋予Thread类中的target字段。
2thread.start();
/**
* 使此线程开始执行;Java 虚拟机调用 run 此线程的方法。
*/
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
看,自动调用run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
所以我们调用run方法,本质上是调用我们创建的RunnaleStyle对象的run方法
方法2
public class ThreadStyle extends Thread{
@Override
public void run () {
System.out.println("用 Thread方式实现线程");
}
public static void main (String[] args) {
new ThreadStyle()//1
.start();//2
}
}
1new ThreadStyle()
public Thread() {
this(null, null, "Thread-" + nextThreadNum(), 0);
}
⬇
public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {
this(group, target, name, stackSize, null, true);
}
⬇
private Thread(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(
SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
this.tid = nextThreadID();
}
2start();
本质上和前面是一样的,调用start方法后自动调用Thread类的run方法。不过,此时的run方法已经被重写为
@Override
public void run () {
System.out.println("用 Thread方式实现线程");
}
1.2 同时使用两种方法
/**
* 同时使用Runnable 和Thread两种方式实现线程
*/
public class BothRunnableThread {
public static void main (String[] args) {
new Thread(new Runnable() {
@Override
public void run () {
System.out.println("使用Runnable 方式实现线程");
}
}){
@Override
public void run () {
System.out.println("使用Thread 方式实现线程");
}
}.start();
}
}
输出:
使用Thread 方式实现线程
原因:
从面向对象的思想去考虑,使用匿名内部类重写run方法。此时,run方法已经被重写过了,所以不会执行target.run()
1.3 创建线程的方式(面试题)💟
答:从不同的角度看,有不同的答案
从官方文档的来看,总共有两种。分别是实现接口Runnable方式和继承类Thread方式
展开来说两个方法哪个好哪个不好。实现Runnable有三点优势是优于继承Thread类的
- 从代码架构的角度考虑,具体执行的任务(run方法的内容),它应该和线程创建以及运行的机制(即Thread类)是解耦的。两者不可混为一谈。所以从解耦的角度方法1更好
- 从资源节约的角度,使用方法2,每次我们想要新建一个任务,只能去新建一个独立的线程(这样损耗比较大,需要创建和执行以及销毁)。如果使用方法1,我们就可以利用后面的线程池等工具,大大减少创建线程和销毁线程所带来的损耗
- 使用方法2,一个类只能继承一个类,大大限制了可扩展性
刚才虽然说到了两种方法,但是看原理这两种方法本质都是一样。其实都是执行Thread类的run方法。只不过一种是执行Runnable接口的run方法,一种是重写Thread类的run方法
然后展开来说,除了上面说的两种方法,还有很多方法创建多线程。比如,线程池、定时器。但是他们在源码上面都和上面两个方法一样,万变不离其宗
总结,创建线程只有一种方式,那就是通过new Thread
方式来创建线程.,而实现线程的执行单元有两种方式
方式一:实现Runnable接口的run方法,并把Runnable实现传给Thread类。然后,调用Thread类的run方法
方式二:重写Thread的run方法(继承Thread类)
创建线程为何只有一种方式? - Java_上创建线程只有一个方法,那就是thread,其他的所有创建方式最终都调用了它-CSDN博客
1.4 创建线程的错误说法
“线程池创建线程也是创建线程的方式”
public class ThreadPools {
public static void main (String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.submit(new Task(){});
}
}
}
class Task implements Runnable{
@Override
public void run () {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
本质上和方法1类似,将我们创建的Runnable对象r传递给Thread类的target字段
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
“通过Callable和FutureTask创建线程,也算是一种创建线程的方式”
所以本质是离不开Runnable接口的
public class CallerTask implements Callable<String > {
@Override
public String call () throws Exception {
return "Hello";
}
public static void main (String[] args) {
FutureTask<String> f = new FutureTask<>(new CallerTask());
new Thread(f).start();
try {
String result = f.get();
System.out.println(result);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
“无返回值是实现runnable接口,有返回值是实现callable接口,所以callable是新的实现线程的方式”
“定时器”
public class DemoTimmerTask {
public static void main (String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run () {
System.out.println(Thread.currentThread().getName());
}
},1000,1000);
}
}
“匿名内部类”
/**
* 使用匿名内部类创建线程
*/
public class AnonymousInnerClassDemo {
public static void main (String[] args) {
new Thread(){
@Override
public void run () {
System.out.println(Thread.currentThread().getName());
}
}.start();
new Thread(new Runnable() {
@Override
public void run () {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
不要被表象所迷惑,创建线程的方式其实和之前的方式没有任何的区别
“Lambda表达式”
public class Lambda {
public static void main (String[] args) {
new Thread(
() -> System.out.println(Thread.currentThread().getName())
).start();
}
}
总结:💟
多线程的实现方式,在代码中写法千变万化,但其本质还是一样的
1.5 实现Runnable接口和继承Thread类哪种方式更好(面试题)
- 从代码架构角度,这里分为两件事情,第一件事情是我们具体的任务(即run方法的内容)。第二个事情和整个线程生命周期相关(比如说,创建线程,销毁线程),这个其实是由Thread类实现的。这两个事情他们的目的不一样,站在代码架构的角度应该去解耦。所以实现Runnable接口方式更好,因为它实现了解耦的目的。
- 新建线程的损耗,如果使用了继承Thread方法,通常我们想要新建一个任务,只能去new一个类。这样做的损耗比较大,我们需要去新建一个线程,执行完之后还要去销毁。如果使用实现Runnable接口的方式,我们可以反复地利用当前线程,比如说线程池就是这样做的,这样一来我们对于线程的损耗就少了
- Java不支持双继承。大大限制了扩展性
- 所以说实现Runnable接口比继承Thread类方式更好