0%

java之多线程

说明

  • 进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。

  • 多进程的优点:多进程稳定性比多线程高,因为在多进程的情况下,一个进程崩溃不会影响其他进程,而在多线程的情况下,任何一个线程崩溃会直接导致整个进程崩溃。

  • 场景场景:一个进程中至少一个线程,一个进程中多个线程,多个进程中多个线程

  • Java语言内置了多线程支持。当Java程序启动的时候,实际上是启动了一个JVM进程,然后,JVM启动主线程来执行main()方法。在main()方法中,我们又可以启动其他线程

继承Thread

  • Thread派生一个自定义类,然后覆写run()方法:
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
39
public class Main {
public static void main(String[] args) {
//创建线程
MyThread t01 = new MyThread();
MyThread t02 = new MyThread();
MyThread t03 = new MyThread("线程03");
t01.start();
t02.start();
t03.start();

//设置线程名(补救的设置线程名的方式)
t01.setName("线程01");
t02.setName("线程02");
// 设置主线程名称
// Thread.currentThread().setName("主线程");
for(int i=0;i<50;i++) {
// Thread.currentThread() 获取当前正在执行线程的对象
System.out.println("主线程" + Thread.currentThread().getName() + ":" + i);
}


}
}
class MyThread extends Thread {

public MyThread() {
}
public MyThread(String name) {
super(name);
}
//run方法是每个线程运行过程中都必须执行的方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("子线程" + this.getName() + ":" + i);
}
}
}

  • 打印结果发现:主线程50个,子线程有150个(3*50),可以看出来,主线程在和子线程抢占CPU的过程中,交替打印结果

此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。

start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。

启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。

run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

单纯使用run()方法是不能多线程并发的。

实现Runnable

  • 实现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
public class demo {

public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
//创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
//创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
Thread tread = new Thread(runnable);
tread.start();

//通过匿名内部类的方式创建线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println( "主线程:" + i);
}
}
}).start();
}
}

class MyRunnable implements Runnable {

@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println( "子线程:" + i);
}
}
}

实现Callable

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
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestThread {

public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(()->{
for (int i = 0; i < 50; i++) {
System.out.println("子线程1执行中:"+i);
}
return "线程1执行完毕";
});

FutureTask<String> futureTask2 = new FutureTask<>(()->{
for (int i = 0; i < 50; i++) {
System.out.println("子线程2执行中:"+i);
}
return "线程2执行完毕";
});

new Thread(futureTask).start();
new Thread(futureTask2).start();
//get 获取线程最终返回的值
System.out.println(futureTask.get());
System.out.println(futureTask2.get());
}
}

线程池

  • Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads),但是便捷不仅隐藏了复杂性,也为我们埋下了潜在的隐患(OOM,线程耗尽)。

  • Executors创建线程池便捷方法列表:

方法名 功能
newFixedThreadPool(int nThreads) 创建固定大小的线程池
newSingleThreadExecutor() 创建只有一个线程的线程池
newCachedThreadPool() 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行
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
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class TestThread {

public static void main(String[] args) throws ExecutionException, InterruptedException {
testFixedThreadPool();
}

private static void testFixedThreadPool() {
ExecutorService fixThread = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
int finalI = i;
fixThread.execute(new Runnable() {
@Override
public void run() {
//每次只能三个一起执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Thread.currentThread().setName("线程");
System.out.println(Thread.currentThread().getName() + " " + finalI);
}
});
}
}

}

  • 后续实践和分析可以看此文章

总结

  • 看了以上四种分析,我们可以清晰的发现了java中其实创建线程的方式就只有一种就是利用Thread+Runnable来实现多线程。其他多有方式都是对这个实现方式的变种。
  • 当然操作线程还有更多的知识,比如设置线程的一些状态、设置线程同步等,更加详细知识参考这里
1
2
3
4
5
6
setPriority  # 设置线程优先级
sleep # 让线程进入阻塞状态
join # 当一个线程调用join方法时,该线程会强制抢占cpu资源,直到该线程执行完毕其他线程才会继续执行
yield # 释放cpu资源,让当前线程进入就绪状态,礼让操作,重新和其他线程竞争cpu资源,可能不一定成功
interrupt # 中断线程
setDaemon # 将线程设置为守护线程,用来守护用户线程的,虚拟机只会保证用户线程的执行完毕,守护线程会随着虚拟机的关闭而关闭。