进程与线程:
Java是一门为数不多的多线程支持的语言;
线程是在进程基础上的一个划分,即一个进程可以创建多个线程;线程是比进程更快的处理单元;
如果进程消失了,那么线程一定会消失,但是如果线程消失了,进程不一定会消失;
多线程的实现:
Java中实现多线程有2种途径:
- 继承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 test {// 主类
public static void main(String args[]) {
MyThread mt1 = new MyThread("线程A");
MyThread mt2 = new MyThread("线程B");
MyThread mt3 = new MyThread("线程C");
mt1.run();
mt2.run();
mt3.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);
}
}
}
public class test {// 主类
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();
}
}
- 为什么多线程启动不是调用run()而必须调用start()?
使用Thread类里面的start()方法,不仅要启动多线程的执行方法,而且还要根据操作系统进行资源的分配;
实现Runnable接口:
Thread类可以实现多线程的主题类定义,但是因为单继承权限,对于类的继承都是应该回避的问题,那么多线程也是一样;为了解决单继承问题出现了Runnable接口,此接口的定义如下:
@FunctionalInterface
public interface Runnable{
public void run();
}
在接口里任何方法都是public定义的权限,不存在默认的权限;
只需要让一个类实现Runnable接口即可,并且需要覆写run()方法;
如果继承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);
}
}
}
public class test {// 主类
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();
}
}
此时避免了单继承局限,在实际工作中使用Runnable接口较好
面试题:多线程两种实现方式的区别?
(或Thread类与Runnable接口实现多线程的区别?)
使用Runnable接口与Thread类相比,解决了单继承的定义局限,如果要使用,一定要使用Runnable接口;
- Thread类是Runnable接口的子类,使用Runnable接口实现多继承可以解决单继承局限;
- Runnable接口实现的多线程可以比Thread类更加清楚的描述数据共享的概念;数据共享指的是多个线程访问统一资源的操作;
Callable接口(了解):
因为Runnable接口里面的 run() 方法不返回操作结果(void);为了解决这个问题,出现了Callable接口,定义如下:
@FunctionalInterface //只能有一个方法
public interface Callable<V>{
public V call() throws Exception;
}
call()方法执行完线程的主题操作后可以返回一个结果,返回结果的类型由泛型决定;
范例:定义一个线程主体类:
class MyThread implements Callable<String> {
private int ticket = 10;
@Override
public String call() throws Exception {
for (int x = 0; x < 100; x++) {
if (this.ticket > 0) {
System.out.println("卖票, ticket = " + this.ticket);
}
}
return "票已卖光!";
}
}
此时观察Thread类里面发现并没有支持Callable接口的多线程应用;可以通过FutureTask方法接收Callable接口;接收的目的只有一个,取得call()方法的返回结果;
class MyThread implements Callable<String> {
private int ticket = 10;
@Override
public String call() throws Exception {
for (int x = 0; x < 100; x++) {
if (this.ticket > 0) {
System.out.println("卖票, ticket = " + this.ticket--);
}
}
return "票已卖光!";
}
}
public class test {
public static void main(String args[]) throws Exception {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
FutureTask<String> task1 = new FutureTask<String>(mt1); // 目的是为了取得call()的返回值
FutureTask<String> task2 = new FutureTask<String>(mt2);
// FutureTask是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类靠拢)