基本使用
最基本的使用多线程的方法是: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;}@Overridepublic 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()方法,这个线程会释放锁,进行对象的等待池。
