从设计的角度讨论Java中线程的两种创建方式

Java中的多线程让我们的程序可以同时运行多个任务,即使我们的CPU是单核的。当然我们都明白这种情况下的同时运行,并不是真正的同时运行,而是JVM中的线程调度器根据时间片轮转的方式快速的在不同线程间的切换。线程调度器让JVM一会运行这个线程,一会运行那个线程,切换的速度很快便我们产生了这些线程好像同时运行的假象。

线程的创建方式有两种,这两种方式究竟有什么不同?性能有什么差异?为什么要设计两种方式呢?带着这些疑问,我们首先回顾一下线程的两种创建方式。

第一种方式,通过实现Runnable接口来创建一个线程:

public class StudyThread{
	public static void main(String[] args){
		MyRunnable myRunnable = new MyRunnable();
		Thread thread = new Thread(myRunnable);
		thread.start();
		
	}
}

class MyRunnable implements Runnable{
	public void run(){
		System.out.println("第一种创建方式");
	}
}


第二种方式,通过继承Thread类来创建一个线程:

public class StudyThreadTwo{
	public static void main(String[] args){
		MyThread myThread = new MyThread();
		myThread.start();
	}
}

class MyThread extends Thread{
	public void run(){
		System.out.println("第二种启动方式");	
	}	
}


通过这两种方式,都能正常创建一个新的线程,性能方面我觉得应该没有多大差异,我们来从设计的角度看一下它们究竟有什么不同。

第一种创建方式是最经常被使用的创建方式。它创建一个线程,需要两个相关类,一个Java提供的Thread类,另一个是要我们自己实现的:实现Runnable接口,并实现run方法。我们要做的就是创建一个Thread对象,在创建时需要一个Runnable类型的参数是需要我们自己实现的。在实际中我们通常使用匿名内部类来实现这种方式,具体如下:

public class StudyThread{
	public static void main(String[] args){
		Thread thread = new Thread(new Runnable(){
			public void run(){
				System.out.println("使用匿名内部类创建线程");
			}
		});
		thread.start();
	}
}

第二种创建方式只需要一个类,就是我们继承自Thread类,然后重写run方法。这种创建方式相比第一种仿佛更简单。实际上这种简单是有代价的,如果我们从设计的角度(设计原则、设计模式)来考虑的话,就会明白我们为什么在实际中很少用这种方式。

根据设计原则中的单一职责原则,我们的类应该仅实现一类功能,应该有且仅有一个原因引起类的变更,应该让界面显示的类只负责界面显示,数据处理只负责数据处理,控制的只负责控制,最合适的例子就是MVC这种复合设计模式。我们再回到上面的分析,为了便于理解,我们可以将Thread类看做一个工人(a worker),Runnable(中的run方法)就是这个工人的工作(work),在第一种创建方式中,工人是工人,工作是工作(松耦合),工作(Runnable)的变更不会影响到工人(Thread);而在第二种创建方式中,工人与工作绑定死了(紧耦合),工作一变,工人也跟着变,这种牵一发而动全身的编程方式,在简单的工程中还好说,如果在一个及其庞大的项目中的话,如果你的一个地方的修改要引起整个系统的更改的话,那你与你的项目会一起崩溃的。

既然第二种相比第一种有如此弊端,为什么还要设计第一种呢?我觉得我们可以从继承的角度来理解。继承可以让子类自动共享父类的数据和方法,并允许子类添加新的数据或者方法。我们有时就是需要一个工作与工人绑定的,并如果被大量使用的,第二种方式就有优点了。不知道这种理解方式合适与否,欢迎大家积极参与讨论。

上一篇:perl web框架


下一篇:Kubernetes Client-go Informer 源码分析