Java多线程编程

进程与线程

进程与线程的区别
最初的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()方法启动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值