【笔记】Java的多线程

前言

Java的多线程学习笔记
Java中,所有非守护线程(主线程和子线程全部)执行结束后,JVM才会自动退出,程序才会终止
平台线程由操作系统调度,虚拟线程由JVM调度,Java8的线程都是平台线程,本篇介绍的线程都是Java8的平台线程

创建线程对象

通过继承Thread类创建线程对象

  • 定义一个类,继承Thread类,重写run()方法,在run()方法内定义线程执行的代码
  • 这种方式创建的线程对象没有返回值
1
2
3
4
5
6
7
8
9
10
11
12
class Cls extends Thread {
@Override
public void run() {
...
}
}

class Main {
public static void main(String[] args) {
Thread thread = new Cls();
}
}

通过实现Runnable接口创建线程对象

  • 定义一个类,实现Runnable接口,实现run()方法,在run()方法内定义线程执行的代码
  • 这种方式创建的线程对象没有返回值
1
2
3
4
5
6
7
8
9
10
11
12
class Cls implements Runnable {
@Override
public void run() {
...
}
}

class Main {
public static void main(String[] args) {
Thread thread = new Thread(new Cls());
}
}

Lambda表达式

1
2
3
4
5
6
7
class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
...
});
}
}

通过Callable接口创建线程对象

  • 创建一个类,实现Callable接口,实现call()方法,在call()方法内定义线程执行的代码
  • 这种方式创建的线程对象有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
class Cls implements Callable<String> {
@Override
public String run() {
...
}
}

class Main {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<>(new Cls());
Thread thread = new Thread(futureTask);
}
}

类方法

获取当前线程对象

1
Thread thread = Thread.currentThread();

当前线程让位

  • 主动放弃CPU分配的时间片
1
Thread.yield();

当前线程延迟

<num>:毫秒值

1
Thread.sleep(<num>);

获取线程组中存活的线程总数

  • 获取当前线程所在的线程组及其子组的所有存活线程总数
1
int count = Thread.activeCount();

实例方法

启动线程

异步执行

  • 由JVM执行,需要等待CPU分配时间片
1
thread.start();

同步执行

  • 由宿主执行,立即执行
1
thread.run();

修改线程名

1
thread.setName("");

获取线程名

1
thread.getName();

获取线程组

1
ThreadGroup group = thread.getThreadGroup();

设置线程优先级

  • 优先级范围为[1,10],缺省值为5

Thread.MIN_PRIORITY:1
Thread.NORM_PRIORITY:缺省值,5
Thread.MAX_PRIORITY:10

1
thread.setPriority(<num>);

指定线程加入到当前线程

  • 当前线程等待指定线程执行结束后才会继续执行
1
thread.join();

设置是否为守护线程

  • 守护线程会在JVM退出时自动退出
  • 守护线程中创建的子线程也是守护线程
  • 守护线程得到CPU时间片的概率会降低

false:缺省值,非守护线程
true:守护线程

1
thread.setDaemon(true);

获取线程编号

1
thread.getId();

获取线程状态

1
thread.getState();

线程中断

  • 通过中断信号提醒线程需要终止线程了,但是实际终止行为由线程自身决定

为指定线程设置中断信号为true

  • 为已经处于阻塞状态的线程设置中断信号为true时,会抛InterruptedException异常
1
thread.interrupt();

重置当前线程中断信号为false

1
boolean interrupted = Thread.interrupted();

判断当前线程的中断信号

1
boolean interrupted = Thread.currentThread().isInterrupted();

线程同步

加锁

  • 锁只有一个,多个线程抢锁只有一个线程能得到锁,得到锁的线程执行逻辑,执行完逻辑释放锁,其他线程等待锁释放后继续抢锁

synchronized修饰代码块

  • 抢类对象锁
1
2
3
synchronized(Main.class) {
...
}
  • 抢对象锁
1
2
3
4
5
Object obj = new Object();

synchronized(obj) {
...
}

synchronized修饰方法

  • 所有调用这个方法的线程都需要先抢锁

  • 包含static关键字的方法需要抢类对象锁

1
2
3
static synchronized void method() {
...
}
  • 不包含static关键字的方法需要抢对象锁
1
2
3
synchronized void method() {
...
}

死锁判定

命令行工具

  • 通过jps获取进程id,通过jstack查看指定进程是否死锁(Found 1 deedlock.
1
jstack <pid>

GUI工具

  • 通过Java监视和管理控制台查看是否死锁
1
jconsole

设置线程为等待状态

  • 必须在synchronized代码块中使用wait()方法
  • 通过wait()让当前线程变为等待状态(Thread.State.WAITING),并释放锁
1
2
3
4
5
Object obj = new Object();

synchronized(obj) {
obj.wait();
}

设置超时时间

  • 等待超时时间结束会自动唤醒

<timestamp>:毫秒时间戳

1
2
3
4
5
Object obj = new Object();

synchronized(obj) {
obj.wait(<timestamp>);
}

唤醒等待状态的线程

  • 必须在synchronized代码块中使用notify()方法
  • 通过notify()方法随机唤醒一个需要抢相同锁的等待状态(Thread.State.WAITING)的线程,或者通过notifyAll()方法唤醒所有需要抢相同锁的等待状态(Thread.State.WAITING)的线程,被唤醒的线程从等待状态(Thread.State.WAITING)变为就绪状态(Thread.State.RUNNABLE),仍然需要重新抢锁
1
2
3
4
5
Object obj = new Object();

synchronized(obj) {
obj.notify();
}
1
2
3
4
5
Object obj = new Object();

synchronized(obj) {
obj.notifyAll();
}

ThreadLocal

  • 将数据绑定到指定线程,线程只能获取自己设置的数据

创建对象

1
ThreadLocal<String> threadLocal = new ThreadLocal<>();

设置数据

1
threadLocal.set("");

获取数据

1
String value = threadLocal.get();

InherableThreadLocal

  • 将数据绑定到指定线程,线程只能获取自己设置的数据
  • 在父线程创建子线程时,子线程会继承父线程定义的数据作为初始值,但是子线程与父线程的数据是隔离的

创建对象

1
InherableThreadLocal<String> inherableThreadLocal = new InherableThreadLocal<>();

设置数据

1
inherableThreadLocal.set("");

获取数据

1
String value = inherableThreadLocal.get();

线程同步

  • 同步:多个线程,步调一致地执行
  • 让多个线程,争夺同一个对象的“同步锁”,谁抢到谁执行,抢不到要等待
  • 同步锁
    • 任何对象,都有唯一的同步锁
    • 遇到同步关键字(synchronized),要先抢到锁才能执行,抢不到要等待

同步代码块

  • 抢指定对象的锁

<obj>:指定对象

1
2
3
synchronized(<obj>) {
...
}

在方法上加锁

  • 抢当前对象的锁
1
2
3
synchronized void func() {
...
}

静态同步代码块

  • 抢类的锁
1
2
3
static synchronized void func() {
...
}

完成