Runnable和Thread实现多线程到底有什么区别?

本文通过代码示例详细探讨了在Java中使用Runnable接口与继承Thread类实现多线程的区别,特别是针对资源共享方面进行了深入分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于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();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值