进程与线程
进程与线程的区别
最初的DOS系统有一个特点:只要电脑中病毒了就会死机。因为传统的DOS系统是单进程的操作系统,即:在同一个时间段内只允许有一个程序运行。而后来到了windows时代发生了改变,电脑即使中病毒了也可以继续使用,但是会变慢。因为在一块CPU、一块内存的情况下,程序利用一些轮转算法,可以让一个资源在不同的时间段上可以同时处理多个不同的程序(进程),但是在一个时间点上只允许有一个进程去执行。
在每个进程上可以进一步划分出若干个线程,那么线程的操作一定是比进程更快的,所以多线程的操作性能一定要超过多进程的操作。但是所有的线程都一定是要在进程的基础之上进行划分。所以进程一旦消失,线程也会随之消失。
继承Thread类实现(重点)
实现Java的多线程操作
在Java中对于多线程的实现一定要有一个主类,而线程的主类往往是需要操作一些资源。但是对于这个多线程主类的实现是有一定要求的:
继承Thread类实现多线程
在java.lang包中存在有Thread类,子类继承Thread之后需要覆写Thread类中的run()方法,那么这个方法就属于线程的主方法,定义:publicvoid run()。
范例:实现线程的主体类
class MyThread extends Thread{//表示实现多线程
private String name;
public MyThread(String name){//线程的名字
this.name=name;
}
public void run() {//覆写run()方法,线程的主方法
for(int x=0;x<10;x++){
System.out.println(this.name+",x="+x);
}
};
}
在线程的主类之中只是将内容输出10次。但是需要注意的是:所有的多线程的执行一定是并发完成的,即在同一个时间段上会有多个线程交替执行,所以为了达到这样的目的,绝对不能直接去调用run()方法,而是应该调用Thread类中的start()方法启动多线程:public void start()。
范例:启动多线程
public class Hello{
public static void main(String args[]) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
mt1.start();
mt2.start();
mt3.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) {
}
}
}
private native void start0();
发现方法中有抛出异常:“IllegalThreadStateException()”没有出现throws声明,没有使用try…catch捕获处理,之所以这样是因为此异常属于RuntimeException的子类。
此异常指的是一个线程已经调用了start()方法后又重新执行了start()
在调用start()方法里面发现会调用start0()方法,而start0()方法上使用了native关键字定义,这个关键字指的是要调用本机的操作系统函数。
由于线程的启动牵扯到操作系统中资源的分配问题,所以具体的线程的启动需要根据不同的操作系统有不同的实现,而JVM相当于根据系统中定义的start0()方法来根据不同的操作系统进行该方法的实现。这样在多线程的层次上,start0()方法的名称不改变,而不同操作系统上有不同的实现。
结论:只有Thread类的start()方法才能够进行操作系统资源的分配,所以启动多线程的方式永远就是调用Thread类的start()方法实现。
实现Runnable接口
继承Thread类会产生单继承的局限操作,所以现在最好的做法是利用接口来解决问题,于是就可以利用Runnable接口来完成操作。首先观察一下Runnable接口的定义结构:
@FunctionalInterface
public interface Runnable{
public void run();
}
此时的代码使用的是函数式的接口,可以利用Lamda表达式完成。
范例:按照正常思路实现多线程
class MyThread implements Runnable{//表示实现多线程
private String name;
public MyThread(String name){//线程的名字
this.name=name;
}
public void run() {//覆写run()方法,线程的主方法
for(int x=0;x<10;x++){
System.out.println(this.name+",x="+x);
}
}
}
如果要想启动多线程依靠只能够是Thread类中的start()方法,之前继承Thread类的时候可以直接将start方法继承下来继续使用,但是现在实现的是Runnable接口,所以此方法没有了。
于是来观察Thread类中的构造方法:publicThread(Runnable target)。
public class Hello{
public static void main(String args[]) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
new Thread(mt1).start();
new Thread(mt2).start();
new Thread(mt3).start();
}
}
很多时候为了方便实现,可以使用匿名内部类或者是Lamda实现代码。
范例:观察实现
public class Hello{
public static void main(String args[]) {
String name = "线程对象";
new Thread(new Runnable() {
public void run(){
for(int x=0;x<10;x++){
System.out.println(name+",x="+x);
}
}
}).start();
}
}
范例:使用JDK1.8的Lamda
public class Hello{
public static void main(String args[]) {
String name = "线程对象";
new Thread(()-> {
for(int x=0;x<10;x++){
System.out.println(name+",x="+x);
}
}).start();
}
}
两种实现方式的区别(面试题)
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。Runable接口可以避免单继承局限,推荐。
联系:
Thread类的定义结构:
public class Thread extends Object implements Runnable
Thread类实现了Runable接口
通过类图描述的关系可以发现,整个代码的操作中使用的就是一个代理设计模式,与传统代理设计模式的差别在于,传统代理模式来讲,现在如果要想启动多线程,理论上应该是run()方法,但是实质上现在调用的是start()方法,名称不符合,之所以这样主要是因为长期发展后的产物,最早的时候设计模式就是个梦。
除了以上的继承关系之外还有一点区别:Runnable接口实现的多线程要比Thread类实现的多线程更方便的表示出数据共享的概念。
class MyThread extends Thread{//表示实现多线程
private int ticket = 5;
public void run() {//覆写run()方法,线程的主方法
for(int x=0;x<50;x++){
if(this.ticket>0){
System.out.println("卖票,ticket= "+this.ticket--);
}
}
}
}
public class Hello{
public static void main(String args[]) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
mt1.start();
mt2.start();
mt3.start();
}
}
发现各个线程都在卖着各自的票。
范例:使用Runnable接口来实现多线程
class MyThread implements Runnable{//表示实现多线程
private int ticket = 5;
public void run() {//覆写run()方法,线程的主方法
for(int x=0;x<50;x++){
if(this.ticket>0){
System.out.println("卖票,ticket= "+this.ticket--);
}
}
}
}
public class Hello{
public static void main(String args[]) {
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
面试题:请解释多线程的两种实现方式以及区别?请分别用代码验证。
- 多线程需要一个线程的主类,这个类要么继承Thread类,要么实现Runnable接口;
- 使用Runnable接口可以比Thread类更好的实现数据共享的操作,并且利用Runnable接口可以避免单继承局限。
实现Callable接口
JDK1.5之后对多线程的实现多了一个Callable接口,此接口比Runnable接口唯一的强大之处在于它可以返回执行结果。此接口定义在java.util.concurrent包中。
这个泛型表示的是返回值类型。call()方法就相当于run()方法。
范例:定义线程的主体类
import java.util.concurrent.Callable;
class MyThread implements Callable<String>{//表示实现多线程
private int ticket = 5;
public String call() {//覆写run()方法,线程的主方法
for(int x=0;x<50;x++){
if(this.ticket>0){
System.out.println("卖票,ticket= "+this.ticket--);
}
}
return "票卖完了!";
}
}
问题:Thread类中并没有提供接收Callable接口的对象操作。所以现在如何启动多线程就出现了问题。为了分析启动的操作,需要观察继承结构。
public class Hello{
public static void main(String args[]) throws Exception {
Callable<String> cal = new MyThread();
FutureTask<String> task = new FutureTask<>(cal);//查询执行结果
Thread thread = new Thread(task);
thread.start();
System.out.println(task.get());//取得线程主方法的返回值
}
}
对于线程的第三种实现方式没有特别的要求。
总结
Thread有单继承局限所以不使用,但是所有的线程对象一定要通过Thread类中的start()方法启动