Java多线程实现

一、线程与进程

从操作系统来讲,可以被民用的系统最早是DOS,但是传统的DOS系统有一个特征:电脑出现病毒之后系统会死机,因为传统的DOS系统采用的是单进程的处理方式。而后来到了windows时代,那么采用的是多进程的处理方式,在同一个时间段上会有多个程序并发执行,轮流强占CPU资源。

但是进程的启动和销毁还是很慢的。所以后来人们开始尝试在进程上做进一步的优化,那么就产生了线程的概念,即:线程是在进程基础之上扩充的。线程的启动和销毁将比进程更快,一个进程上是可以划分出多个线程的,而进程消失的话线程一定也会消失,Java是为数不多的支持多线程编程的语言之一。


二、Thread类实现

如果要想实现多线程的开发,那么就必须像主类存在那样,也需要一个线程的主体类,但是这个类并不能单独定义,必须让其继承Thread类或者是实现Runnable接口(如果从实际的开发来讲一定是Runnable接口)。

线程的主体类只需要通过extends关键字继承Thread类,那么这个类就可以用于线程的控制,当继承了Thread类之中用户还需要覆写Thread类之中的run()方法“public void run()”。

范例:定义线程的主体类

class MyThread extends Thread {
	private String name; // 理解为对象的名字

	public MyThread(String name) {
		this.name = name;
	}

	@Override
	public void run() { // 线程的主方法
		for (int x = 0; x < 10; x++) {
			System.out.println(this.name + ",x = " + x);
		}
	}
}

此时每一个MyThread类的对象都是一个线程,那么可以产生多个线程。

但是当线程主体类定义完成后,必须通过对象进行访问,但是调用的并不是类中的run()方法,而是Thread类的start()方法:“public void start()”。调用此方法就相当于调用了run()方法。

范例:启动多线程

public class TestDemo {
	public static void main(String[] args) {
		MyThread mtA = new MyThread("线程A");
		MyThread mtB = new MyThread("线程B");
		MyThread mtC = new MyThread("线程C");
		mtA.start();
		mtB.start();
		mtC.start();
	}
}

如果使用的是run()方法实际上只是一个方法的普通调用,将采用顺序的方式执行,而如果调用的是start()方法则表示将启动多线程,多个线程彼此之间并发运行。

疑问:通过简短的分析,应该已经知道多线程的基本运行过程了,但是为什么非要通过start()来调用run()呢?

为了方便理解,打开关于Thread类中start()方法的定义:

	public synchronized void start() {
		if (threadStatus != 0)
			throw new IllegalThreadStateException();
		group.add(this);
		boolean started = false;
		try {
			start0();
			started = true;
		} finally {
			try {
				if (!started) {
					group.threadStartFailed(this);
				}
			} catch (Throwable ignore) {
			}
		}
	}

本方法会抛出一个“IllegalThreadStateException”异常,但是如果是手工的使用throw抛出异常,应该使用try…catch处理才对。但是此时并没有强制性要求,因为通过观察“IllegalThreadStateException”继承结构:

java.lang.Object

         java.lang.Throwable

                   java.lang.Exception

                            java.lang.RuntimeException

                                     java.lang.IllegalArgumentException

                                               java.lang.IllegalThreadStateException

因为RuntimeException子类,一般当一个线程被重复启动的时候就会抛出此异常。所以一个线程只能够启动一次。

可以发现在调用start()方法的时候会自动调用start0()方法,而在start0()方法的声明处发现有一个native关键字,那么此关键字表示的是,将通过本地的原始函数进行代码的实现。

线程的操作一定是需要进行CPU资源调度,每个操作系统的资源调度是不一样的,所以此方法实际上会由JVM实现,那么编写的时候只给出了一个抽象方法的名称,而具体的实现将根据不同的操作系统进行覆写,所以才实现可移植性。

 所以可以得出结论,当用户执行start()方法的时候意味着将进行操作系统的资源调配,调配之后才会执行run()方法,即:任何时候只要是启动多线程,都一定要使用Thread类之中的start()方法。


三、Runnable接口实现

类与类之间的继承并不推荐去使用,但是类实现接口是一个推荐的功能,在Java里面为了解决多线程的单继承局限问题,所以也提供有一个Runnable接口,用户只需要让线程的主体类实现Runnable接口即可。

范例:观察Runnable接口的定义结构

public interface Runnable {
	public void run();
}

范例:使用Runnable定义线程主体类

class MyThread implements Runnable {
	private String name;

	public MyThread(String name) {
		this.name = name;
	}

	@Override
	public void run() { // 线程的主方法
		for (int x = 0; x < 10; x++) {
			System.out.println(this.name + ",x = " + x);
		}
	}
}

通过之前的分析可以得出结论,只要是线程的启动一定要依靠Thread类的start()方法,如果说现在一个类直接继承了Thread类,那么可以继承下start()方法,但是此时实现的是Runnable接口,那么将没有start()可以被继承。那么下面来观察Thread类中构造方法的定义:“public Thread(Runnable target)”。发现可以接收一个Runnable接口子类对象。

范例:启动多线程

public class TestDemo {
	public static void main(String[] args) {
		MyThread mtA = new MyThread("线程A");
		MyThread mtB = new MyThread("线程B");
		MyThread mtC = new MyThread("线程C");
		new Thread(mtA).start();
		new Thread(mtB).start();
		new Thread(mtC).start();
	}
}

此时两者的功能是完全相同的,但是很明显,使用Runnable要比使用Thread更加的合理。


四、两种实现方式的区别

通过一系列的分析之后已经清楚了多线程的两种实现方式,但是这两种方式从结构上讲一定使用的是Runnable,可是除了这一点之外,还有其它的区别吗?

那么首先来观察一下Thread类的定义:

public class Thread extends Object implements Runnable

发现原来Thread类也实现了Runnable接口,于是一件很有意思的事情发生了。

从结构上讲Thread是一个代理类的结构设计,但是又不那么完整。如果是一个纯粹的代理设计模式,那么用户应该调用的是Thread类的run()方法,但是现在调用的却是start()方法(并不是Runnable接口提供的方法),所以在整个操作之中虽然形式是代理结构,但是最终还是有差异的,而这个差异也是由于历史原因造成的。

除了这个基本的联系之外,还有一点不算区别的小区别:使用Runnable接口实现的多线程要比使用Thread类实现的多线程更容易表示出数据共享的概念。

范例:编写一个简单的卖票程序,利用Thread类实现(产生三个线程对象一起卖票)

class MyThread extends Thread {
	private int ticket = 5; // 假设有5张票

	@Override
	public void run() { // 线程的主方法
		for (int x = 0; x < 20; x++) {
			if (this.ticket > 0) {
				System.out.println("卖票:ticket = " + this.ticket--);
			}
		}
	}
}

public class TestDemo {
	public static void main(String[] args) {
		new MyThread().start();// 一个线程
		new MyThread().start();// 两个线程
		new MyThread().start();// 三个线程
	}
}

这个时候发现每一个线程对象都有各自的五张票进行售卖,不符合要求。

范例:利用Runnable接口实现

class MyThread implements Runnable {
	private int ticket = 5; // 假设有5张票

	@Override
	public void run() { // 线程的主方法
		for (int x = 0; x < 20; x++) {
			if (this.ticket > 0) {
				System.out.println("卖票:ticket = " + this.ticket--);
			}
		}
	}
}

public class TestDemo {
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt).start();// 一个线程
		new Thread(mt).start();// 两个线程
		new Thread(mt).start();// 三个线程
	}
}

扩展题目:请解释出多线程两种实现方式的区别?分别编写程序进行说明?

  • 多线程的两种实现方式:继承Thread类、实现Runnable接口;如果继承了Thread类,那么会受到单继承局限,而且不方便表示出数据共享的概念,Thread类是Runnable接口的子类;

  • 如果实现了Runnable接口,那么将不受到单继承的影响,同时可以方便的表示出数据共享的操作;

  • 但是不管使用何种方式,最终一定要通过Thread类的start()方法才可以启动多线程。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值