这是一道面试题。这两种方式有哪种区别?
Runnable将数据共享这一特点完美诠释。
首先一定要明确的是,使用我们的Runnable接口与Thread类相比,解决了单继承的定义局限,所以在优先级上已经确定使用Runnable接口。
首先看jdk源码中Thread类定义:
public class Thread extends Object implements Runnable
发现Thread类实现了Runnable接口,那么这样一来就变为了以下形式。
image.png我们可以看出,Runnable接口实现多线程其实很像代理模式的应用。很好的起到了设计模式中原则中的迪米特原则。之所以是"像",因为如果是代理设计模式,那么客户端调用的应该是接口里面提供的run()方法。
除了以上联系之外,使用Runnable接口可以比Thread类更好的描述出数据共享这一个概念,此时的数据共享指的是多个线程访问同一资源的操作。
范例:观察代码(每一个线程对象都必须通过start()启动)
package TestDemo;
class MyThread extends Thread{
private int ticket=10;//这里的变量会压入局部变量表 因为并没有用static声明
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(this.ticket>0){
System.out.println("sell,ticket="+this.ticket--);
}
}
}
}
public class TestDemo{
public static void main(String[] args) {
MyThread thread1=new MyThread();
MyThread thread2=new MyThread();
MyThread thread3=new MyThread();
thread1.start();
thread2.start();
thread3.start();
}
}
image.png
结果三个thread类都在卖各自的10张票,这是由于我们并没有给ticket设置为static变量,所以这个数据没有被录入元数据区,而被压入了局部变量表,在执行实例化操作后,各自生成了一块堆内存指向,此时并不存在数据共享这一概念。
在JVM内存模型基础上,我们可以看这张基于多线程的内存模型。
image.png也很好的诠释了这一点。而Runnable就完美实现了这张内存模型图,我们可以把主存看作是MyThread类中的元数据,三个new Thread类匿名内部类对象实例看作3块工作内存。
范例:利用Runnable实现。
package TestDemo;
class MyThread implements Runnable{
private int ticket=10;//这里的变量会压入局部变量表 因为并没有用static声明
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(this.ticket>0){
System.out.println("sell,ticket="+this.ticket--);
}
}
}
}
public class TestDemo{
public static void main(String[] args) {
MyThread mt=new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
image.png
原理分析:这是由于三个不同的栈都指向了同一块堆内存对象,确保了他们操作的是同一个实例化对象。
因而当多个线程访问同一资源时候,我们使用Runnable更好的描述了数据共享这个概念(本质上是JVM虚拟栈是否指向同一块堆内存)
总结:请解释Thread类与Runnable接口实现多线程的区别(请解释多线程两种实现方式区别)?
Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承的局限性。
Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念(本质上是JVM虚拟栈是否指向同一块堆内存)
可以将JVM内存布局以及基于多线程的内存模型画出来。
以及将两种多线程实现方式,白板编程出来。
网友评论