对于通过继承Thread类和实现Runnable接口这两种方式来创建线程的理解(含少量源码分析)

举例说明如何通过这两种方式创建线程

继承Thread类创建线程类

public class FirstThread extends Thread {
	int i = 0;	//不同线程对象不能共享该成员变量
	
	/**
	 * 重写run方法,该方法为线程的执行体
	 */
	@Override
	public void run() {
		for (; i < 10; i++) {
			System.out.println(getName() + ": i = " + i);
		}
	}
}
public class TestFirstThread {

	public static void main(String[] args) {
		new FirstThread().start();
		new FirstThread().start();
	}

}

在main方法中创建了两次该线程类,并调用start方法开启线程,然后执行线程中的run方法。

注意:使用继承Thread类创建线程类时,不同线程对象不能共享该线程类的成员变量

上述代码输出结果

Thread-0: i = 0
Thread-1: i = 0
Thread-1: i = 1
Thread-0: i = 1
Thread-1: i = 2
Thread-1: i = 3
Thread-1: i = 4
Thread-1: i = 5
Thread-1: i = 6
Thread-1: i = 7
Thread-1: i = 8
Thread-1: i = 9
Thread-0: i = 2
Thread-0: i = 3
Thread-0: i = 4
Thread-0: i = 5
Thread-0: i = 6
Thread-0: i = 7
Thread-0: i = 8
Thread-0: i = 9

实现Runnable接口创建线程

public class SecondThread implements Runnable {
	
	int i = 0;

	@Override
	public void run() {
		for (; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + ": i = " + i);
		}
	}

}
public class TestSecondThread {

	public static void main(String[] args) {
		SecondThread st = new SecondThread();	//作为target传入Thread的构造方法
		new Thread(st).start();
		new Thread(st).start();
	}

}

运行结果

Thread-0: i = 0
Thread-0: i = 1
Thread-1: i = 0
Thread-0: i = 2
Thread-0: i = 4
Thread-0: i = 5
Thread-0: i = 6
Thread-1: i = 3
Thread-1: i = 8
Thread-1: i = 9
Thread-0: i = 7

从运行结果可以发现用这种方法创建的多个线程可以共享线程类(严格来说不是线程类,只是和线程类一样实现了Runnable接口,说它是线程类的target更合适)的成员变量。下面会分析为什么会这样。

从源码角度分析以上两种方法

其实查看源码可以发现,以上这两种方法虽然实现不同,但内在的原理是一样的。

继承Thread

class Thread implements Runnable {
    //........
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

可以看到Thread实现了Runnable接口并重写了run方法。FirstThread方法继承了Thread,其实也就是实现了Runnable接口,然后重写了run方法。
第一种方法开启线程是这样的:new FirstThread().start(); 它执行了Thread的无参构造方法

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

我们重点看第二个参数Runnable target,这里的target为null,也就是说第一种方式实现不需要target,但第二种方式会不一样。

实现Runnable

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

可以看到Runnable接口只有一个方法,而SecondThread实现了该接口并重写了这个方法。所以实际上SecondThread也是一个Runnable,也就是说SecondThread可以作为Thread类的一个target。

第二种方法开启线程是这样的:new Thread(st).start(); 它执行了Thread的含有一个Runnable参数的构造方法

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

这里的第二个参数不在是null,而是传来的参数target,然后在init方法执行过后,SecondThread对象被赋给了Thread的成员变量

private Runnable target;

所以现在虽然Thread的run方法没有被重写,但它实际上是调用了target的run方法,也就是SecondThread中重写的run方法。不妨再看一次Thread重写的run方法:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

所以说,这两种方法的实质都是重写了Thread中的run方法,只不过是直接重写该方法和调用target所重写的该方法的区别。

两种方式所带来的不同

分析过源码后,现在就更加清楚为什么不同的实现,会产生成员变量是否可以共享的问题。

  • 首先是第一种方式,这里new了两个Thread的子类,也就是说有两个对象,等于实现了两个接口对象,那么接口中的run方法的变量自然不能共享了。
		new FirstThread().start();
		new FirstThread().start();
  • 然后是第二种,这里虽然也是new了两个Thread,但是只有一个接口对象,就是st,那么接口中的run方法的变量自然就可以共享了(因为就只有这一个接口对象,也就是说只有这个变量,当然只能共享了)。
		SecondThread st = new SecondThread();	//作为target传入Thread的构造方法
		new Thread(st).start();
		new Thread(st).start();

其实弄懂了这些后,我们也可以用第二种方式实现第一种的效果,只要多创建一个SecondThread(Runnable)对象就行了。

		SecondThread st = new SecondThread();
		SecondThread st1 = new SecondThread();
		new Thread(st).start();
		new Thread(st1).start();

改为上述代码之后,再运行就会和第一种方式的效果一样。原因很简单,这里多了一个SecondThread对象,两者的变量当然是不能共享的,因为根本就不在同一块存储空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值