1. 线程与进程
- Java是一门为数不多的多线程支持的编程语言。
- 什么是进程?在操作系统的定义中,进程指的是一次程序的完整运行,在这个运行的过程中内存、处理器、IO等资源操作都要为这个进程进行服务。在最早的DOS时代,有一个特点:如果你电脑病毒发作了,那么你的电脑几乎就不能动了。因为所有的资源都被病毒软件所占用,其他程序无法抢占这个资源。到了Windows时代电脑中病毒了,电脑也可以运行(就是慢点)。
Windows属于多进程的操作系统,每一个进程都需要有资源的支持,多个进程怎么分配资源呢? - 在同一个时间段上,会有多进程轮流去抢占资源,但是在某一个时间点上,只有一个进程。
- 线程是在进程的基础上划分的结果,即:一个进程上可以同时创建多个线程。
- 线程是比进程更快的内存处理单元,而且所占的资源也小。多线程的应用也是性能最高的应用。
- 线程的存在离不开进程,进程如果消失后,线程一定会消失;线程消失后,进程不一定会消失
2.Thread类实现
掌握java中三种多线程的实现方式(JDK1.5之后增加了第三种)
如果要想在java中实现多线程,有两种途径:
- 继承Thread类
- 实现Runnable接口(Callable接口)
继承Thread类
Thread是一个支持多线程的一个功能类,只要有一个子类就可以多线程的支持。
- 所有程序的起点都是main()方法,但是所有线程也一定要有一个自己的起点,那就是
run()
方法,也就是说在多线程的每个主体类 - 在多线程的每个主体类之中都必须覆写Thread类中所提供的run方法
这个方法上没有返回值,也就意味着线程一旦开始就要一直执行不能返回内容。
//线程操作主类
class MyThread extends Thread{//这就是一个多线程的操作类
private String name;
public MyThread(String name)
{
this.name=name;
}
@Override
public void run() {//覆写run方法作为线程的主题操作方法
for(int x=0;x<200;x++)
{
System.out.println(this.name+"-->"+x);
}
}
}
public class Main {//主类
public static void main(String[] args) throws Exception {//正常工作一定要通过try catch处理
MyThread m1 = new MyThread("threadA");
MyThread m2 = new MyThread("threadb");
m1.run();
m2.run();
}
}
//本线程类的功能是进行循环的输出操作,所有的线程与进程是一样的,都必须去轮流抢占资源,多线程的执行,应该是多线程彼此交替执行
//也就是说如果直接调用run()方法,那么并不能够启动多线程,多线程启动的唯一方法就是Thread类中的Start方法:
public void start()
调用此方法执行的方法体是run方法定义的
//线程操作主类
class MyThread extends Thread{//这就是一个多线程的操作类
private String name;
public MyThread(String name)
{
this.name=name;
}
@Override
public void run() {//覆写run方法作为线程的主题操作方法
for(int x=0;x<200;x++)
{
System.out.println(this.name+"-->"+x);
}
}
}
//本线程类的功能是进行循环的输出操作,所有的线程与进程是一样的,都必须去轮流抢占资源,多线程的执行,应该是多线程彼此交替执行
//也就是说如果直接调用run()方法,那么并不能够启动多线程,多线程启动的唯一方法就是Thread类中的Start方法
public class Main {//主类
public static void main(String[] args) throws Exception {//正常工作一定要通过try catch处理
MyThread m1 = new MyThread("threadA");
MyThread m2 = new MyThread("threadB");
m1.start();
m2.start();
}
}
此时,每个thread是交替执行的,它们执行的东西都是一样的。要想启动多线程就要调用start()
为什么多线程启动不是调用run而必须调用start()?
看一下java的源代码中start的定义:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
//是否被启动一次
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();//关键
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
- 首先发现在Thread类的start方法里面存在这样一个异常抛出:
IllegalThreadStateException();
本方法里面使用throw抛出异常,通常道理上应该用try…catch处理或者在start方法上使用throws声明,但是此处并没有这样的代码,因为此异常属于RuntimeException的子类属于选择性处理
如果一个线程对象重复启动就会抛出此异常 - 发现在start方法中使用了
start0()
方法,而且此方法的结构与抽象方法类似,使用了native声明,在java开发里面有一门技术叫做JNI技术(Java Native Interface),使用Java调用本操作系统的函数,但是有一个缺点就是不能离开特定的操作系统。属于丧失这种可移植性。如果要想线程执行,需要操作系统进行资源分配。
private native void start0();
如果要想线程执行,需要操作系统来进行资源分配,所以此操作严格来讲主要是由JVM负责根据不同的操作系统来实现的。
即:使用Thread类的start()方法不仅仅要启动多线程的一个执行代码还要根据不同的操作系统进行资源的分配。
2.实现Runnable接口
虽然Thread类是可以实现多线程的一个主体类的定义,但是它有一个问题,Java具有单继承局限,正因为如此,在任何情况下,针对类的继承都应该是回避的问题,那么多线程也一样,为了解决单继承的限制,在java里面专门提供了Runnable接口
函数式接口:JDK1.5之后用此接口,函数式接口的特征是一个接口只能定义一个方法,因为要作为方法引用使用
public interface Runnable{
public void run();
}
任何接口里面定义的方法都是public定义的权限,不允许存在默认的权限。即使不写也是public。
那么只需要让一个类实现Runnable接口即可,并且也需要覆写run方法
//线程操作主类
class MyThread implements Runnable{//这就是一个多线程的操作类
private String name;
public MyThread(String name)
{
this.name=name;
}
@Override
public void run() {//覆写run方法作为线程的主题操作方法
for(int x=0;x<200;x++)
{
System.out.println(this.name+"-->"+x);
}
}
}
此时的MyThread类在结构上与之前是没有区别的,但是有一点是有严重区别的,如果此时继承了Thread类,那么可以直接继承start()方法,但如果实现的是Runnable接口没有start方法可以被继承。
不管何种情况下,要想启动多线程,要依靠Thread类完成,在Thread类里面定义有以下的构造方法:
public Thread(Runnable target);
这个构造方法接收实现Runnable接口的对象!
//线程操作主类
class MyThread implements Runnable{//这就是一个多线程的操作类
private String name;
public MyThread(String name)
{
this.name=name;
}
@Override
public void run() {//覆写run方法作为线程的主题操作方法
for(int x=0;x<200;x++)
{
System.out.println(this.name+"-->"+x);
}
}
}
//本线程类的功能是进行循环的输出操作,所有的线程与进程是一样的,都必须去轮流抢占资源,多线程的执行,应该是多线程彼此交替执行
//也就是说如果直接调用run()方法,那么并不能够启动多线程,多线程启动的唯一方法就是Thread类中的Start方法
public class Main {//主类
public static void main(String[] args) throws Exception {//正常工作一定要通过try catch处理
MyThread m1 = new MyThread("threadA");
MyThread m2 = new MyThread("threadB");
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m2);
t1.start();
t2.start();
//m1.start();
//m2.start();
}
}
此时就避免了单继承局限,那么也就是说实现过程中使用接口是最合适的
3.多线程两种实现方式的区别(面试题)
首先要明确的是使用Runnable接口与Thread类相比解决了单继承的定义局限,不管后面的区别是什么,这一点上就已经下了死定义。如果要使用一定使用Runnable接口。
首先观察一下Thread类的定义。
public class Thread
extends Object
implements Runnable
MyThread和Thread都实现了Runnable接口,但是MyThread类通过Thread类实现功能:
- MyThread主要实现多线程类的核心功能
- Thread类进行操作系统的资源分配;
调用run(){}方法(核心功能)
- 客户端使用的是Thread类,调用的是其start方法,start方法再去调用MyThread类的核心功能。Thread起到一个代理的作用,MyThread是一个真实操作主题。
- 整个的定义结构非常像代理设计模式,但是还有一个不太合适的地方,如果是代理设计模式,客户端调用的代理调用方法也应该是接口定义的方法也应该是run才对。
- 除了以上的联系还有一点:使用Runnable接口可以别Thread类更好的描述出数据共享这一概念。此时的数据共享指的是多个线程访问统一资源的操作。
范例:每一个线程对象都必须通过start启动
//线程操作主类
class MyThread extends Thread{//这就是一个多线程的操作类
private int ticket=10;
@Override
public void run() {//覆写run方法作为线程的主题操作方法
for(int x=0;x<100;x++)
{
if(this.ticket>0)
System.out.println("ticket="+this.ticket--);
}
}
}
//本身这个类里面是有start方法的
public class Main {//主类
public static void main(String[] args) throws Exception {//正常工作一定要通过try catch处理
//由于MyThread类有start方法,所以每一个MyThread类对象就是1个线程对象,可以直接启动
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
mt1.start();
mt2.start();
mt3.start();
//m2.start();
}
}
//本程序声明了三个MyThread类的对象,并且分别调用了三次start方法启动线程对象
//但是发现每一个线程都在卖各自的10张票,因为开辟了三个同样的内存空间
此时并不存在有数据共享这一概念
范例:利用Runnable实现
MyThread mt1 = new MyThread();
MyThread mt2 = mt1;
MyThread mt3 = mt1;
mt1.start();
mt2.start();
mt3.start();
//线程操作主类
class MyThread implements Runnable{//这就是一个多线程的操作类
private int ticket=10;
@Override
public void run() {//覆写run方法作为线程的主题操作方法
for(int x=0;x<100;x++)
{
if(this.ticket>0)
System.out.println("ticket="+this.ticket--);
}
}
}
//本身这个类里面是有start方法的
public class Main {//主类
public static void main(String[] args) throws Exception {//正常工作一定要通过try catch处理
//由于MyThread类有start方法,所以每一个MyThread类对象就是1个线程对象,可以直接启动
MyThread mt1 = new MyThread();
new Thread(mt1).start();
new Thread(mt1).start();
new Thread(mt1).start();
}
}
整个代码中有三个线程类对象,但是在空间中是怎么存的?
此时也属于三个线程对象的,但是唯一的区别就是这三个线程对象都占用了同一个MyThread类对象的空间,也就是说这三个线程对象都直接访问同一个数据资源。
以后多个线程对象访问一块内存资源就使用Runnable接口定义线程主体类,用Thread类启动线程。
面试题:请解释Thread类与Runnable接口实现多线程的区别?(请解释多线程两种实现方式的区别)
- Thread类是Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承的局限
- Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念。(还是没太理解???)
面试题:请写出多线程的两种实现操作?
把上面的代码都写出来,把Thread类继承的方式和Runnable接口的实现方法都写出来
4.Callable接口(理解)
使用Runnable接口实现的多线程可以避免单继承的局限,但是有一个问题,不能返回操作结果。为了解决这样的矛盾,提供了一个新的接口:Callable接口
@FunctionalInterface
public interface Callable<V>
{public V call();}
执行完主题功能之后可以返回一个结果,而返回结果的类型由Callable接口上的泛型来决定。
范例:定义一个线程主体类
import java.util.concurrent.Callable;
//线程操作主类
class MyThread implements Callable<String> {//这就是一个多线程的操作类
private int ticket=10;
@Override
public String call ()throws Exception {//覆写run方法作为线程的主题操作方法
for(int x=0;x<100;x++)
{
if(this.ticket>0)
System.out.println("ticket="+this.ticket--);
}
return "票已卖光";
}
}
//此时观察Thread方法类中没有实现Callable的接口
//本身这个类里面是有start方法的接口
在JDK1.5之后有一个public class FutureTask<V>
类,这个类主要是负责Callable接口对象操作的,这个接口的定义结构:
public class FutureTask<V>
extends Object
implements RunnableFuture<V>
实现了一个RunnableFuture接口:
public interface RunnableFuture<V>
extends Runnable, Future<V>
public interface Future的方法:
在FutureTask里面有如下的构造方法:
接收的目的只有一个:取得call()方法的返回结果。
import java.awt.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
//线程操作主类
class MyThread implements Callable<String> {//这就是一个多线程的操作类
private int ticket=10;
@Override
public String call ()throws Exception {//覆写run方法作为线程的主题操作方法
for(int x=0;x<100;x++)
{
if(this.ticket>0)
System.out.println("ticket="+this.ticket--);
}
return "票已卖光";
}
}
//此时观察Thread方法类中没有实现Callable的接口
//本身这个类里面是有start方法的
public class Main {//主类
public static void main(String[] args) throws Exception {//正常工作一定要通过try catch处理
//由于MyThread类有start方法,所以每一个MyThread类对象就是1个线程对象,可以直接启动
MyThread mt1 = new MyThread();
FutureTask<String > task1 = new FutureTask<String>(mt1);
MyThread mt2 = new MyThread();
FutureTask<String > task2 = new FutureTask<String>(mt1);
//为了取得call的返回结果
//Runnable的接口子类,所以可以使用Thread类的构造来接收task对象
//启动线程
new Thread(task1).start();
new Thread(task2).start();
//多线程执行完毕之后可以取得内容,依靠FutureTask的父接口Future中的get方法完成
System.out.println("A线程的返回结果"+task1.get());
System.out.println("B线程的返回结果"+task2.get());
}
}
最麻烦的问题是需要接收返回值的信息,并且又要与原始的多线程的实现靠拢(向Thread类靠拢)
总结
- 对于多线程的实现,重点在于Runnable接口和Thread类启动的配合上
- 对于JDK1.5的新特性,了解知道区别在于返回结果上