在实际工作中,我们很可能习惯性地选择Runnable或Thread之一直接使用,根本没在意二者的区别,但在面试中很多面试官会经常而且非常严肃的问出:请你解释下Runnable或Thread的区别?尤其是新手就容易上当,不知如何回答,就胡乱编一通。鄙人今天告诉你们这二者本身就没有本质区别,就是接口和类的区别。如果非要说区别,请看如下:
- Runnable的实现方式是实现其接口即可
- Thread的实现方式是继承其类
- Runnable接口支持多继承,但基本上用不到
- Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。
网络上流传的最大的一个错误结论:Runnable更容易可以实现多个线程间的资源共享,而Thread不可以! 这是一个错误的结论!
我们假定一个场景,分3个窗口(线程)买10张票:
一、实现Runnable接口
package com.neonuu.collection.domain; public class RunnableTest { static class MyRunnable implements Runnable{ //共购买10张票 private int ticket = 10; @Override public void run() { //实现Runnable接口 for(int i=0;i<10;i++){ //利用synchronized锁,实现同步,防止超卖 synchronized(this){ if(this.ticket>0){ try { System.out.println(Thread.currentThread().getName()+"购票前剩余:"+this.ticket); ticket--; System.out.println(Thread.currentThread().getName()+"购票后剩余:"+this.ticket); System.out.println("================"); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }else { break; } } } } } public static void main(String[] args){ //实现Runnable接口,3个线程同时购买10张票 MyRunnable myRunable = new MyRunnable(); Thread myRunnable1 = new Thread(myRunable,"myRunnable1"); Thread myRunnable2 = new Thread(myRunable,"myRunnable2"); Thread myRunnable3 = new Thread(myRunable,"myRunnable3"); myRunnable1.start(); myRunnable2.start(); myRunnable3.start(); } }
结果如下:
myRunnable1购票前剩余:10 myRunnable1购票后剩余:9 ================ myRunnable3购票前剩余:9 myRunnable3购票后剩余:8 ================ myRunnable3购票前剩余:8 myRunnable3购票后剩余:7 ================ myRunnable2购票前剩余:7 myRunnable2购票后剩余:6 ================ myRunnable3购票前剩余:6 myRunnable3购票后剩余:5 ================ myRunnable1购票前剩余:5 myRunnable1购票后剩余:4 ================ myRunnable1购票前剩余:4 myRunnable1购票后剩余:3 ================ myRunnable3购票前剩余:3 myRunnable3购票后剩余:2 ================ myRunnable2购票前剩余:2 myRunnable2购票后剩余:1 ================ myRunnable2购票前剩余:1 myRunnable2购票后剩余:0 ================
二、继承Thread
package com.neonuu.collection.domain; public class ThreadTest { static class MyThread extends Thread { //共购买10张票 private int ticket = 10; public void run(){ //继承Thread类 for(int i=0;i<10;i++){ //利用synchronized锁,实现同步,防止超卖 synchronized(this){ if(this.ticket>0){ try { System.out.println(Thread.currentThread().getName()+"购票前剩余:"+this.ticket); ticket--; System.out.println(Thread.currentThread().getName()+"购票后剩余:"+this.ticket); System.out.println("================"); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }else { break; } } } } } public static void main(String[] args){ //方法一:创建3个MyThread实例 //网上多数采用此方法,来证明Thread与Runnable在资源共享上的区别,其实是错误的 //结果:3个线程各买了10张票,共30张票 MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); MyThread myThread3 = new MyThread(); myThread1.start(); myThread2.start(); myThread3.start(); //方法二:创建1个MyThread实例,开启3个线程 //结果:与继承Runnable相同,3个线程同时购买10张票 MyThread myThread = new MyThread(); Thread myThread4 = new Thread(myThread,"myThread1"); Thread myThread5 = new Thread(myThread,"myThread2"); Thread myThread6 = new Thread(myThread,"myThread3"); myThread4.start(); myThread5.start(); myThread6.start(); } }
在main方法中我们使用两种写法
1、方法一的结果:
Thread-0购票前剩余:10 Thread-0购票后剩余:9 Thread-2购票前剩余:10 Thread-1购票前剩余:10 Thread-2购票后剩余:9 ================ ================ Thread-1购票后剩余:9 ================ Thread-1购票前剩余:9 Thread-0购票前剩余:9 Thread-2购票前剩余:9 Thread-1购票后剩余:8 ================ Thread-2购票后剩余:8 ================ Thread-0购票后剩余:8 ================ Thread-0购票前剩余:8 Thread-0购票后剩余:7 ================ Thread-2购票前剩余:8 Thread-2购票后剩余:7 ================ Thread-1购票前剩余:8 Thread-1购票后剩余:7 ================ Thread-2购票前剩余:7 Thread-0购票前剩余:7 Thread-1购票前剩余:7 Thread-0购票后剩余:6 ================ Thread-1购票后剩余:6 ================ Thread-2购票后剩余:6 ================ Thread-0购票前剩余:6 Thread-2购票前剩余:6 Thread-1购票前剩余:6 Thread-2购票后剩余:5 ================ Thread-0购票后剩余:5 ================ Thread-1购票后剩余:5 ================ Thread-1购票前剩余:5 Thread-1购票后剩余:4 ================ Thread-0购票前剩余:5 Thread-0购票后剩余:4 ================ Thread-2购票前剩余:5 Thread-2购票后剩余:4 ================ Thread-2购票前剩余:4 Thread-2购票后剩余:3 ================ Thread-0购票前剩余:4 Thread-0购票后剩余:3 ================ Thread-1购票前剩余:4 Thread-1购票后剩余:3 ================ Thread-1购票前剩余:3 Thread-1购票后剩余:2 ================ Thread-0购票前剩余:3 Thread-0购票后剩余:2 ================ Thread-2购票前剩余:3 Thread-2购票后剩余:2 ================ Thread-2购票前剩余:2 Thread-2购票后剩余:1 ================ Thread-0购票前剩余:2 Thread-0购票后剩余:1 ================ Thread-1购票前剩余:2 Thread-1购票后剩余:1 ================ Thread-2购票前剩余:1 Thread-2购票后剩余:0 ================ Thread-0购票前剩余:1 Thread-0购票后剩余:0 ================ Thread-1购票前剩余:1 Thread-1购票后剩余:0 ================
可以看出,方法一分三个线程分别购买了10张票,共购买了30张。这个结果并不是我们想要的。网上大部分是通过这个来证明Thread不能资源共享,是错误的!
2、方法二的结果
myThread1购票前剩余:10 myThread1购票后剩余:9 ================ myThread3购票前剩余:9 myThread3购票后剩余:8 ================ myThread2购票前剩余:8 myThread2购票后剩余:7 ================ myThread3购票前剩余:7 myThread3购票后剩余:6 ================ myThread1购票前剩余:6 myThread1购票后剩余:5 ================ myThread3购票前剩余:5 myThread3购票后剩余:4 ================ myThread2购票前剩余:4 myThread2购票后剩余:3 ================ myThread3购票前剩余:3 myThread3购票后剩余:2 ================ myThread1购票前剩余:2 myThread1购票后剩余:1 ================ myThread1购票前剩余:1 myThread1购票后剩余:0 ================
可以看出,这个结果与Runnable是相同的,3个线程共同购买10张票
注:synchronized这个关键字是必须的,否则会发生超买,买了11张票,ticket变为负数。
三、结论:
事实是Thread和Runnable没有本质的区别,只是写法不同罢了,,这才是正确的结论,与资源共享无关!