基本使用
最基本的使用多线程的方法是:new Thread(方法).start();传过去的方法相当于一份使用让多线程执行的说明书。
方法可以通过lambda表达式或者类::方法等等方式写入进去。
线程不安全问题
多线程会会乱序执行,即是会造成线程不安全问题。常用的Collection体系的东西和Map体系的东西都是线程不安全的,以HashMap举例,多线程可能会造成HashMap死循环。
需要解决这个问题可以使用锁,private final static Object a = new Object;一般用这种方式声明一把锁,一般很少new Object;
另一种使用方式
类也可以通过继承Thread
再重写run(),使得这个类符合多线程的条件;然后调用时,new这个类的实例Thread1,通过Thread1.start()启动线程。
实现 Callable 接口
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
实现java.lang.Runnable接口
package com.multithread.runnable;
class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "运行 : " + i);
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Thread2("C")).start();
new Thread(new Thread2("D")).start();
}
}
run()方法是多线程程序的一个约定,所有的多线程代码都在run()方法里面。
为什么推荐使用Runable接口?
- 可以避免Java单继承的限制。
- 线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread类线程。
- 适合多个程序代码相同的线程去处理同一个资源
- 代码可被多个线程共享。代码和数据独立。
synchronized关键字
通过synchronized设置竞争条件,使得某一部分线程安全。可以synchronized(lock){内容};(这个lock一般为Object lock = new lock());也可以把这个关键字加到类的签名(把Class对象当锁)或者方法签名中(把该实例当锁)。(同一把锁在同一时刻只能被一个地方拿到)。
ReentrantLock
ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 确保释放锁,从而避免发生死锁。
}
}
}
public static void main(String[] args) {
LockExample lockExample = new LockExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> lockExample.func());
executorService.execute(() -> lockExample.func());
}
Output:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
排查死锁
我们可以通过terminal命令jsp打印正在运行的进程和jstack + 进程号打印该进程的所有栈信息(可导出)。来分析死锁。
预防死锁
所有线程都按相同顺序获得资源的锁,可以预防死锁的产生。按照相同顺序指的是,如果第一次是按照lock1、lock2的顺序拿的锁,我们在第二个地方也应该按照这个顺序拿到锁。
线程安全相关的包—JUC包
juc:java.util.concurrent
Object类里的线程方法
- void notify() - Used to wake up a single thread that is waiting on this object’s monitor.
- void notifyAll() - Used to wake up all threads that are waiting on this object’s monitor.
- void wait() - marks the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
- void notify() - 用来唤醒一个线程
- void notifyAll() - 用来唤醒全部线程
- void wait() -让这个线程睡觉,直到被别的线程唤醒
使用:
lock.wait(); 暂时释放这个锁,让这个线程进入等待池,直到这把锁被notify.
lock.notify(); 释放锁。
示例
https://github.com/gakei/MutiThread
涉及新接触的容器类:Optional
线程什么时候会释放锁
在以下情况下,持有锁的线程会释放锁:
1. 执行完同步代码块。
2. 在执行同步代码块的过程中,遇到异常而导致线程终止。
3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。