Java多线程基本知识整理

基本概念

多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

并发:在操作系统中指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。

并行:在操作系统中指一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的。

进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程:有时被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID,当前指令,寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。

死锁:指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

创建线程

在Java中创建新线程,一般有以下几种方法:继承Thread类、实现Runnable接口、实现Callable接口。

继承Thread类

继承Thread类并重写run方法,在run方法中定义需要执行的逻辑。

线程类
1
2
3
4
5
6
7
8
9
10
11
class Seller extends Thread {
private Integer ticketNum;
public Seller(Integer ticketNum) {
this.ticketNum = ticketNum;
}
public void run() {
for (; ticketNum > 0; --ticketNum) {
System.out.println(Thread.currentThread().getId() + " sold one ticket, " + ticketNum);
}
}
}
测试类
1
2
3
4
5
6
7
public class ThreadTest {
public static void main(String[] args) {
int ticketTotalNum = 3;
new Seller(ticketTotalNum).start();
new Seller(ticketTotalNum).start();
}
}
测试结果
1
2
3
4
5
6
7
8
11 sold one ticket, 3
12 sold one ticket, 3
11 sold one ticket, 2
11 sold one ticket, 1
12 sold one ticket, 2
11 sold one ticket, 0
12 sold one ticket, 1
12 sold one ticket, 0
分析
  • 继承Thread类的方式创建新线程时最大的局限就是不支持多继承,因为Java语言的特点就是单根继承。
  • 使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。

实现Runnable接口

实现Runnable接口并实现run方法,在run方法中定义需要执行的逻辑。

线程类
1
2
3
4
5
6
7
8
9
10
11
class Seller implements Runnable {
private int ticketNum;
public Seller(Integer ticketNum) {
this.ticketNum = ticketNum;
}
public void run() {
for (; ticketNum > 0; ) {
System.out.println(Thread.currentThread().getId() + " sold one ticket, " + (ticketNum--));
}
}
}
测试类
1
2
3
4
5
6
7
8
9
public class RunnableTest {
public static void main(String[] args){
int count=6;
Seller seller=new Seller(count);
new Thread(seller).start();
new Thread(seller).start();
new Thread(seller).start();
}
}
测试结果
1
2
3
4
5
6
11 sold one ticket, 6
11 sold one ticket, 3
13 sold one ticket, 4
12 sold one ticket, 5
13 sold one ticket, 1
11 sold one ticket, 2
分析
  • 如果待创建的线程类已经有一个父类了,这时就不能再继承自Thread类,因为Java不支持多继承,此时就可以实现Runnable接口来创建新线程。

实现Callable接口

实现Callable接口并实现call方法,在call方法中定义需要执行的逻辑。

线程类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Seller implements Callable {
private Integer ticketNum;
public Seller(Integer ticketNum) {
this.ticketNum = ticketNum;
}
public String call() throws Exception {
if (this.ticketNum <= 0) {
System.out.println(Thread.currentThread().getId()+ " error");
throw new Exception("num error");
}
for (; ticketNum > 0; ) {
System.out.println(Thread.currentThread().getId() + " selled one ticket, " + (ticketNum--));
}
return "done";
}
}
测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CallableTest {
public static void main(String[] args) {
// 创建一个执行任务的服务
ExecutorService es = Executors.newFixedThreadPool(3);
try {
int count = 3;
Future featureOne = es.submit(new Seller(count));
// 调用get方法,当前线程会等待任务执行完毕后才往下执行
System.out.println(featureOne.get());
Future featureTwo = es.submit(new Seller(0));
System.out.println(featureTwo.get());
} catch (Exception e) {
System.out.println(e.getMessage());
}
// 停止任务执行服务
es.shutdownNow();
}
}
测试结果
1
2
3
4
5
6
11 selled one ticket, 3
11 selled one ticket, 2
11 selled one ticket, 1
done
12 error
java.lang.Exception: num error
分析

Callable接口类似于Runnable,但Callable接口有更多的新特性:

  • call方法可以有返回值
  • call方法可以抛出异常并被捕获

线程基本操作

currentThread()方法

currentThread()方法可返回代码段正在被哪个线程调用的信息。

例:Thread.currentThread().getName() 获取当前线程名称

isAlive()方法

isAlive()方法的功能是判断当前的线程是否处于活动状态。

活动状态是指线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。

sleep()方法

sleep()方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠。

例:Thread.sleep(2000);当前线程休眠2秒

getId()方法

getId()方法的作用是获取线程的唯一标识。

例:Thread.currentThread.getId(); 获取当前线程的唯一标识

停止线程

在Java中有以下三种方法可以终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend、resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。
  • 使用interrupt方法中断线程。
判断线程是否是停止状态

在Java中,Thread类提供了两种方法判断线程是否停止

  • this.interrupted(): 测试当前线程是否已经是中断状态,执行后具有将状态标志置为false的功能。
  • this.isInterrupted(): 测试线程Thread对象是否已经是中断状态,但不清除状态标志。

参考文献

《Java多线程编程核心技术》