基本使用

最基本的使用多线程的方法是:new Thread(方法).start();传过去的方法相当于一份使用让多线程执行的说明书。
方法可以通过lambda表达式或者类::方法等等方式写入进去。


线程不安全问题

多线程会会乱序执行,即是会造成线程不安全问题。常用的Collection体系的东西和Map体系的东西都是线程不安全的,以HashMap举例,多线程可能会造成HashMap死循环。
需要解决这个问题可以使用锁,private final static Object a = new Object;一般用这种方式声明一把锁,一般很少new Object;


另一种使用方式

类也可以通过继承Thread

再重写run(),使得这个类符合多线程的条件;然后调用时,new这个类的实例Thread1,通过Thread1.start()启动线程。

实现 Callable 接口

  1. public class MyCallable implements Callable<Integer> {
  2. public Integer call() {
  3. return 123;
  4. }
  5. }
  6. public static void main(String[] args) throws ExecutionException, InterruptedException {
  7. MyCallable mc = new MyCallable();
  8. FutureTask<Integer> ft = new FutureTask<>(mc);
  9. Thread thread = new Thread(ft);
  10. thread.start();
  11. System.out.println(ft.get());
  12. }

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

实现java.lang.Runnable接口

  1. package com.multithread.runnable;
  2. class Thread2 implements Runnable{
  3. private String name;
  4. public Thread2(String name) {
  5. this.name=name;
  6. }
  7. @Override
  8. public void run() {
  9. for (int i = 0; i < 5; i++) {
  10. System.out.println(name + "运行 : " + i);
  11. try {
  12. Thread.sleep((int) Math.random() * 10);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. }
  19. public class Main {
  20. public static void main(String[] args) {
  21. new Thread(new Thread2("C")).start();
  22. new Thread(new Thread2("D")).start();
  23. }
  24. }

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)包中的锁。

  1. public class LockExample {
  2. private Lock lock = new ReentrantLock();
  3. public void func() {
  4. lock.lock();
  5. try {
  6. for (int i = 0; i < 10; i++) {
  7. System.out.print(i + " ");
  8. }
  9. } finally {
  10. lock.unlock(); // 确保释放锁,从而避免发生死锁。
  11. }
  12. }
  13. }
  1. public static void main(String[] args) {
  2. LockExample lockExample = new LockExample();
  3. ExecutorService executorService = Executors.newCachedThreadPool();
  4. executorService.execute(() -> lockExample.func());
  5. executorService.execute(() -> lockExample.func());
  6. }

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()方法,这个线程会释放锁,进行对象的等待池。