关于Runnable和Thread实现多线程区别,在搜索页几篇访问量比较高的几篇博文,发现他们关于资源共享的解释有很多不合理的地方,可能年份比较久远,后期博主也没有更新吧。
大佬的博文连接:
1、https://blog.youkuaiyun.com/ns_code/article/details/17161237
嘿嘿,我很喜欢这个大佬,所以就把他一人的链接放出来了。
先上代码
package ThreadDifRunable;
public class R implements Runnable{
int ticket = 5;
int RNumber;
public R(int RNumber) {
this.RNumber = RNumber;
}
@Override
public void run() {
for(;;) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket > 0) {
System.out.println("R " + RNumber + " -- > ticket =" + ticket--);
}else {
break;
}
}
}
}
package ThreadDifRunable;
public class T extends Thread {
private int ticket = 5;
int TNumber;
public T(int TNumber) {
this.TNumber = TNumber;
}
@Override
public void run() {
for(;;) {
if(ticket > 0) {
System.out.println(" T "+ TNumber + " --> ticket = " + ticket--);
}else {
break;
}
}
}
}
测试代码
package ThreadDifRunable;
public class AppMian {
public static void main(String[] args) {
t1();
// r1();
// t();
// r();
}
static void t() {
new T(1).start();
new T(2).start();
new T(3).start();
}
static void t1() {
T t = new T(4);
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
static void r() {
new Thread(new R(1)).start();
new Thread(new R(2)).start();
new Thread(new R(3)).start();
}
static void r1() {
R r = new R(4);
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
t();输出
T 4 --> ticket = 5
T 4 --> ticket = 3
T 4 --> ticket = 1
T 4 --> ticket = 4
T 4 --> ticket = 2
t();输出
T 1 --> ticket = 5
T 3 --> ticket = 5
T 3 --> ticket = 4
T 3 --> ticket = 3
T 3 --> ticket = 2
T 3 --> ticket = 1
T 2 --> ticket = 5
T 2 --> ticket = 4
T 2 --> ticket = 3
T 2 --> ticket = 2
T 2 --> ticket = 1
T 1 --> ticket = 4
T 1 --> ticket = 3
T 1 --> ticket = 2
T 1 --> ticket = 1
r1();输出
R 4 -- > ticket =4
R 4 -- > ticket =3
R 4 -- > ticket =5
R 4 -- > ticket =2
R 4 -- > ticket =1
r();输出
R 1 -- > ticket =5
R 2 -- > ticket =5
R 3 -- > ticket =5
R 1 -- > ticket =4
R 3 -- > ticket =4
R 2 -- > ticket =4
R 2 -- > ticket =3
R 3 -- > ticket =3
R 1 -- > ticket =3
R 2 -- > ticket =2
R 3 -- > ticket =2
R 1 -- > ticket =2
R 2 -- > ticket =1
R 3 -- > ticket =1
R 1 -- > ticket =1
(注意:每次运行输出可能会不一样。)
当然,在t1()方法中new Thread(t).start(),与r1() 方法中new Thread(r).start(),调用的其实是Thread同一构造器。(t1()这样设计似乎有点画蛇添足,这里只是做一个对比)乍一看好像是有点区别,其实吧,这是内存分配以及java参数传递问题。在Java中如果参数是基本类型,传递的是基本类型的字面量值的拷贝。如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。t()方法和r()方法,分别创建了三个实例对象,内存这时存在了三个ticket,三个线程改变的是自己所属的ticket,和其他线程没关系。而t1()和r1(),内存中只有一个ticket,三个线程分别持有这个ticket的地址引用,所以改变的是同一个对象。
那么假如我吧ticket申明为static,属于类对象。这时就会出现这样的情况:
package ThreadDifRunable;
public class _T extends Thread {
private static int ticket = 5;
int TNumber;
public _T(int TNumber) {
this.TNumber = TNumber;
}
@Override
public void run() {
for(;;) {
if(ticket > 0) {
System.out.println(" _T "+ TNumber + " --> ticket = " + ticket--);
}else {
break;
}
}
}
public static void main(String[] args) {
_t();
}
static void _t() {
new _T(1).start();
new _T(2).start();
new _T(3).start();
}
}
输出:
_T 1 --> ticket = 5
_T 1 --> ticket = 2
_T 1 --> ticket = 1
_T 3 --> ticket = 3
_T 2 --> ticket = 4
无论创建多少个_T的对象,它们改变的都是内存中同一个ticket变量。(资源一旦共享就涉及到数据安全问题了)
可见,能否更好的实现资源的共享,并不是因为是继承Thread,还是实现Runnable,而是在模式和数据的设计上。至于Runnable和Thread实现多线程到底有什么区别?在《ThinkInJava》中有这样的一段介绍:
若某样东西有一个Runable接口,实际只是意味着它有一个run()方法,但不存在与之相关的任何特殊的东西——它不具有任何天生的线程处理能力,这与那些从Thread继承的类是不同的。所以为了从一个Runnable对象产生线程,必须单独创建一个线程,并为其传递Runnable对象;可为其使用一个特殊的构建器,并令其采用一个Runnable作为自己的参数使用。随后便可为那个线程调用start()。
Runnable接口最大的一个有点是所有的东西都从属于相同的类。若需访问什么东西,只需简单地访问它即可,不需要涉及一个独立的对象。但是这种便利是要付出代价的——只可为那个特定的对象运行单独一个线程(尽管可创建多个这种类型的对象,或者在不同的类里创建其他对象)。注意Runnable接口本身并不是造成这一限制的主要原因。是因为由于Runnable与我们主类合并造成的,因为每个应用只能有一个主类的对象。
其实我认为最主要的原因还是因为java语言的特性,java是不支持多继承的,这就限制了当我们继承完Thread之后,就无法继承其他类了。
最后贴上部分源码,大家可自行体会。
Thread:
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(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) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(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);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
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 */
}
}
}
private native void start0();
}
Runnable:
package java.lang;
/**
* The <code>Runnable</code> interface should be implemented by any
* class whose instances are intended to be executed by a thread. The
* class must define a method of no arguments called <code>run</code>.
* <p>
* This interface is designed to provide a common protocol for objects that
* wish to execute code while they are active. For example,
* <code>Runnable</code> is implemented by class <code>Thread</code>.
* Being active simply means that a thread has been started and has not
* yet been stopped.
* <p>
* In addition, <code>Runnable</code> provides the means for a class to be
* active while not subclassing <code>Thread</code>. A class that implements
* <code>Runnable</code> can run without subclassing <code>Thread</code>
* by instantiating a <code>Thread</code> instance and passing itself in
* as the target. In most cases, the <code>Runnable</code> interface should
* be used if you are only planning to override the <code>run()</code>
* method and no other <code>Thread</code> methods.
* This is important because classes should not be subclassed
* unless the programmer intends on modifying or enhancing the fundamental
* behavior of the class.
*
* @author Arthur van Hoff
* @see java.lang.Thread
* @see java.util.concurrent.Callable
* @since JDK1.0
*/
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}