JAVA中的多线程

创建线程的三种方法

1、继承Thread类,重写run()方法,调用start()方法执行。

2、实现Runnable接口,实现run()方法,创建Thread对象时传入Runnable的子类对象,调用start()方法执行。

3、实现Callable接口,实现call()方法,可返回结果并允许此方法抛出异常。实例化FutureTask类时,将实现Callable接口的类的对象作为参数传入,然后实例化Thread类,将FutureTask对象传入,最后执行start()。

JAVA线程类结构的设计:

MmZORe.md.png

Callable和Runnable接口是为了那些实例可能被另一个线程执行的类设计的。

多线程执行过程

程序默认的线程执行在栈区,当创建一个新线程时,在栈区开辟一个新的内存空间,执行代码(变量的创建都在新的内存区中)。当线程的代码结束了,线程自动在栈内存中释放了,当所有线程都结束了,那么进程就结束了。

(main方法的线程和main方法中创建的新线程有主从关系吗?main线程会等“子线程”结束再结束吗?)

要注意多线程中的并发执行≠并行执行。(同一时间,多个事件间隔发生【时间片单位】;同一时间,多个事件同时发生。)并发是在多台处理器上同时处理多个任务。所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

下列Demo中线程任务执行的方法中加睡眠方法的原因是:让线程进入等待状态,方便其他线程抢占资源,这样可以方便的观察到多线程并发执行的情况

Thread

DemoDemo2是两个多线程类,Test里面测试多线程执行

只有调用start()方法才会创建新线程,直接调用run()方法是不会创建新线程的,只是在main线程中的一个方法顺序执行

Demo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package MuliThread.one;

public class Demo extends Thread {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(800);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
System.out.println(this.getName()+":"+i);
}
}

}

Demo2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package MuliThread.one;

public class Demo2 extends Thread {

@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
System.out.println(this.getName()+":"+i);
}
}

}

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package MuliThread;

public class Test {
public static void main(String[] args) {
/*
创建线程的目的是什么?
是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。
例如:导入功能。
*/
Demo d1 = new Demo();
Demo2 d2 = new Demo2();
d1.setName("=====");
d2.setName("+++++");

//两个线程并发执行
d1.start();
d2.start();
//当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

//调用run方法无法开启线程,因为直接调用run方法是执行方法,还是在主线程种。而start()方法才会开启新线程。
//d1.run();//run执行完才会往下执行,所以不会在并发执行,而是顺序执行
//d2.start();
}
}

多线程并发执行:

MeXxkq.md.png

为什么要创建类继承Thread,而不是直接调用Threadstart()方法呢?(只有傻逼才会问这种问题)

Thread类的run()方法没有做事,所以你需要重写run()方法,将你创建新线程需要执行的代码写进去。

匿名内部类

另一种使用是直接创建Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package MuliThread.one;

public class Test {
public static void main(String[] args) {
//可以改成lamdba表达式
new Thread(){
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("==="+i);
}
}
}.start();

new Thread(){
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("+++"+i);
}
}
}.start();

}
}

多线程并发执行:

MeX2OH.md.png

Runnable

Demo是线程任务,实现Runnable接口,实现run()方法;创建Thread线程对象,传入Demo类,调用start()方法即可。

Demo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package MuliThread.two;

//线程任务
public class Demo implements Runnable {
private String name;

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(name+":"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
}
System.out.println("执行完毕");
}
}

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package MuliThread.two;

public class Test {
public static void main(String[] args) {
Demo demo1 = new Demo("hanxu");//线程任务
Thread thread1 = new Thread(demo1);//线程对象
Demo demo2 = new Demo("ctj");
Thread thread2 = new Thread(demo2);

thread1.start();
thread2.start();
}
}

多线程并发执行:

MexVW6.md.png

使用Runnable的好处:将线程任务线程对象解耦,更加符合面向对象。(还是不知道有什么实际的好处)

匿名内部类

另一种方式是使用匿名内部类,不用新建类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package MuliThread.two;

public class Test {
public static void main(String[] args) {

Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
System.out.println("========"+i);
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
System.out.println("++++++"+i);
}
}
};
Thread thread = new Thread(runnable);
Thread thread2 = new Thread(runnable2);
thread.start();
thread2.start();

}
}

多线程并发执行:

MexxAA.md.png

Callable

MmZORe.md.png

要了解Callable,必须了解JAVA的线程体系。

接口FutureRunnable。接口RunnableFuture继承上述两个接口。

FutureTask实现接口RunnableFuture

Future

1
2
3
4
5
6
7
8
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

Runnable

1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}

Thread

1
2
3
public class Thread implements Runnable {
xxx
}

RunnableFuture

1
2
3
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}

FutureTask

1
2
3
public class FutureTask<V> implements RunnableFuture<V> {
xxx
}

类FutureTask有一构造函数,可接受参数Callable类型

Mmeii8.md.png

Callable

1
2
3
4
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}

FutureTask类实现了Runnable和Future接口,所以可以把FutureTask当作Runnable,作为参数,放到Thread里面。

所以使用Callable接口创建线程时是这样的步骤:

1、自定义类实现Callable,实现call()方法,里面写线程执行任务代码。

2、创建FutureTask实例,将上述自定义类的实例作为参数传入。

3、创建Thread实例,将上述FutureTask实例作为参数传入。

4、执行Thread的start(),即可创建新线程并执行任务。

代码实现:

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package MuliThread.three;

import java.util.concurrent.Callable;

public class Demo implements Callable {
private Integer a;
private Integer b;

public Demo(Integer a, Integer b) {
this.a = a;
this.b = b;
}

@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(this.a + "===========" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
}
return this.a + this.b;
}

}

Demo2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package MuliThread.three;

import java.util.concurrent.Callable;

public class Demo2 implements Callable {
private Integer a;
private Integer b;

public Demo2(Integer a, Integer b) {
this.a = a;
this.b = b;
}

@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(this.a + "+++++++++++" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
}
return this.a + this.b;
}
}

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package MuliThread.three;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Test {
public static void main(String[] args) {
Callable demo = new Demo(1,2);
Callable demo2 = new Demo2(3,4);

/*try {
//直接调用call()不会触发新线程的创建,顺序执行两个call()
Object call = demo.call();
Object call2 = demo2.call();

System.out.println("结束语句");
} catch (Exception e) {
e.printStackTrace();
}*/


FutureTask task = new FutureTask(demo);
FutureTask task2 = new FutureTask(demo2);

Thread thread = new Thread(task);
Thread thread2 = new Thread(task2);
thread.start();
thread2.start();
}
}

多线程并发执行任务:

MmnHI0.md.png

FutureTask的应用:

FutureTask继承体系的核心是Future接口,Future的核心思想是:一个方法f,计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,可以通过Future这个数据结构去控制方法f的计算过程。

这里的控制包括:

get方法:获取计算结果(如果还没计算完,也是必须等待的)【重载方法中,可以设置最多等待时间】

cancel方法:还没计算完,可以取消计算过程

isDone方法:判断是否计算完

isCancelled方法:判断计算是否被取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package MuliThread.three;


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable demo = new Demo(1,2);
Callable demo2 = new Demo2(3,4);

FutureTask task = new FutureTask(demo);
FutureTask task2 = new FutureTask(demo2);

Thread thread = new Thread(task);
Thread thread2 = new Thread(task2);
thread.start();
thread2.start();

System.out.println(thread.getName() + "线程是否已经计算出结果了?" + task.isDone());
System.out.println(thread2.getName() + "线程呢?" + task2.isDone());

System.out.println("那我等待他计算完成再获取结果吧(运行到此方法时会阻塞等待call()的结果噢)..." + task.get());
System.out.println("我也等待结果..." + task2.get());

}
}

多线程并发执行:

MmueLd.md.png

ExecutorService

ExecutorService是一个线程池,他的submit方法,该方法重载了多个方法,分别可接受一个Runnable类型或Callable类型的参数。因此可以自定义类实现Callable或Runnable,然后将此类的实例传入submit,即可开启多线程,执行线程任务。

MyRunnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package MuliThread.pool;

import java.util.concurrent.Callable;

public class MyCallable implements Callable {
private String name;

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

@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(name+":"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
}
System.out.println("执行完毕");
return null;
}
}

MyRunnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package MuliThread.pool;

public class MyRunnable implements Runnable {
private String name;

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

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(name+":"+i);
try {
Thread.sleep(600);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
}
System.out.println("执行完毕");
}
}

ThreadPoolDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package MuliThread.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 线程池的使用:
* 1、用 工厂 创建出线程池
* 2、自定义实现Callable或Runnable的类实例化对象
* 3、执行submit(),从 线程池 中拿到一个线程,绑定到上述对象上,并开始执行线程任务
* 4、关闭线程池
*/

public class ThreadPoolDemo {
public static void main(String[] args) {
//得到线程池
ExecutorService pool = Executors.newFixedThreadPool(4);

//创建线程任务
MyRunnable mR = new MyRunnable("线程池练练");
MyRunnable mR2 = new MyRunnable("线程池习习");
MyCallable mC = new MyCallable("CCC线程池练练");
MyCallable mC2 = new MyCallable("CCC线程池习习");

//从线程池中获取线程对象,然后执行mR的任务
pool.submit(mR);
pool.submit(mR2);
pool.submit(mC);
pool.submit(mC2);

//submit方法调用结束后,main线程并不会立即结束。
//因为线程池控制了线程的关闭,将使用完的线程归还到了线程池后还在维护这个线程池
pool.shutdown();//手动关闭线程池后,main程序没有了任务
}
}

多线程并发执行任务:

MmGBM6.md.png

线程池结合Fature

MyNumCallable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package MuliThread.pool;

import java.util.concurrent.Callable;

public class MyNumCallable implements Callable {
private Integer a;
private Integer b;

public MyNumCallable(Integer a, Integer b) {
this.a = a;
this.b = b;
}

@Override
public Integer call() throws Exception {
System.out.println("正在计算结果...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("睡眠方法出错");
}
return this.a+this.b;
}
}

Practice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package MuliThread.pool;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
* 利用线程做任务:加法运算
* 通过Future实现有返回结果的线程
*/
public class Practice {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//得到线程池
ExecutorService pool = Executors.newFixedThreadPool(3);

//自定义线程任务
MyNumCallable mNC = new MyNumCallable(6,8);
MyCallable myC = new MyCallable("另一个任务");

//开启多线程执行任务
Future result = pool.submit(mNC);
pool.submit(myC);
//在计算结果的时候还能执行线程myC的任务

Integer i = (Integer) result.get();
System.out.println("结果是:" + i);

//关闭线程池
pool.shutdown();
}
}

多线程并发执行:

MmJYOf.md.png

线程常用方法

Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
join() //等待该线程终止

//挂起线程的意思:线程会自动的释放掉占有的线程资源锁,以便于其他线程可以获取道线程资源执行他们的任务
//挂起,相当于让线程睡觉,不让他干活;唤醒,相当于叫醒他,让他继续执行任务。(也不一定是一唤醒就执行任务,还需要抢到线程资源)

wait()//挂起线程
notify()/notifyAll()//唤醒线程

park()//挂起线程
unpark()//唤醒线程

sleep()//也能将线程挂起,睡眠时间过了后会自动唤醒线程
interrupt()//主动唤醒睡眠的线程

suspend()//已过时,会引发死锁 挂起线程
resume()//已过时,会引发死锁 唤醒线程

总结

上述三种方式中,1、2两种方式run()均不能存在返回值,不能抛出异常。

由于1方式是直接继承Thread类,JAVA是单继承,继承此类后就不能继承其他类了,所以比较不灵活。

而2方式是实现接口,一个类实现此接口后也可实现其他接口。

而第三种方式代码没有1、2简洁。

而线程池(ExecutorService)的方式其实还是对其他方法的使用而已。

insist,on the road
-------------本文结束感谢您的阅读-------------