线程

# Java多线程完整笔记

程序、进程、线程

程序 (program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程 (process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

  • 例子:运行中的 QQ,运行中的 MP3 播放器;
  • 程序是静态的,进程是动态的;
  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

线程 (thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的;
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器 (pc),线程切换的开销小;
  • 一个进程中的多个线程共享相同的内存单元 / 内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患;
  • 每个 Java 程序都有一个隐含的主线程:main 方法。

进程和线程之间的关系

1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。

2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。

3、线程在执行过程中,需要协作同步(生产者消费者)。不同进程的线程间要利用消息通信的办法实现同步。

4、CPU是分给线程,即真正在CPU上运行的是线程。

总的来说

进程作为拥有资源的基本单位,线程是作为CPU调度和分配的基本单位。

进程是拥有资源的独立单位,线程是不拥有系统资源,但是可以访问隶属于进程的资源。


并发和并行

单核 CPU,其实是一种假的多线程,因为在一个时间单元内,只能执行一个线程的任务,实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 CPU 的时间片分给不同的程序使用,只是由于 CPU 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。

总结:微观串行,宏观并行。

如果是多核的话,才能更好的发挥多线程的效率,现在的服务器都是多核的。多核 CPU 每个核都可以调度运行线程,这时候线程可以是并行的。

  • 并发(concurrent):同一时间应对多件事情的能力;交替执行
  • 并行(parallel):同一时间动手做多件事情的能力;同时执行

举例:

  • 家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发;
  • 家庭主妇雇了个保姆,她们一起做这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待);
  • 雇了 3 个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行。

同步和异步asynchronize

需要等待结果返回,才能继续运行就是同步

不需要等待结果返回,就能继续运行就是异步


线程创建的四种方式

方式1:继承Thread类

  1. 自定义类继承Thread类
  2. 重写run()方法 ——run()中的内容就是线程体
  3. 创建自定义线程类的对象
  4. 调用start()方法启动线程

⚠注意:启动线程必须使用start(),不要直接调用run()

– start():启动线程,由JVM自动调用run(),只能调用一次
– run():只是普通方法封装业务逻辑,可以反复调用,不会开启新线程

public class ChatThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<11;i++)
        {
            System.out.println("聊天");
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        ChatThread t1 = new ChatThread();
        // 其他线程对象...
        t1.start();
    }
}

方式2:实现Runnable接口(推荐)

优势:

  1. 避免Java单继承的局限性
  2. 多个线程可以共享同一个实现类对象,适合多个线程处理同一份资源

步骤:

  1. 自定义类,实现Runnable接口
  2. 实现run()方法
  3. 创建任务对象,传入Thread构造器得到线程对象
  4. 调用start()启动
public class ChatThread1 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<11;i++)
        {
            System.out.println("聊天");
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

// 测试
public static void main(String[] args) {
  ChatThread1 chatThread1 = new ChatThread1();
  Thread t1 = new Thread(chatThread1);

  // 匿名内部类写法(常用)
  Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程运行");
    }
  });
  t1.start();
}

Thread 与 Runnable对比

对比项 继承Thread 实现Runnable
继承限制 Java单继承,无法再继承其他类,扩展性差 接口实现,可以继承其他类,灵活
数据共享 多个线程不方便共享任务数据 多个线程共用同一个任务对象,适合卖票等共享资源场景
耦合度 线程和任务耦合在一起 任务与线程分离,符合单一职责
适用场景 简单测试,正式项目不推荐 线程池、Lambda、生产主流方案

方式3:实现Callable接口

相比Runnable功能更强:

  1. call()方法拥有返回值
  2. 方法可以抛出异常
  3. 支持泛型返回值
  4. 需要借助FutureTask获取结果

Future接口:可以对任务执行结果取消、查询是否完成、获取返回值 FutureTask:Future接口唯一实现类;同时实现Runnable、Future接口,既可以交给Thread执行,又能拿到返回结果

public class SumThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

//测试
public static void main(String[] args) {
    SumThread s1 = new SumThread();
    FutureTask<Integer> futureTask = new FutureTask<>(s1);
    Thread thread = new Thread(futureTask);
    thread.start();

    try {
        Integer sum = futureTask.get();
        System.out.println(sum);
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }
}

Runnable 和 Callable区别

  1. run()无返回值;call()拥有泛型返回值,配合FutureTask获取异步结果
  2. futureTask.get()会阻塞主线程,不调用则不会阻塞
  3. run()内部异常只能捕获消化;call()允许向上抛出异常

方式4:线程池创建线程

问题背景:频繁创建销毁大量线程,消耗性能。 思路:提前创建一批线程放入池中,复用线程,避免频繁创建销毁。

✅线程池好处:

  • 提高响应速度(省去创建线程耗时)
  • 降低资源消耗(线程复用)
  • 方便统一管理线程

核心API:

–

  ExecutorService
  

:线程池核心接口,实现类

  ThreadPoolExecutor
  
  • execute(Runnable):执行无返回值任务
  • submit(Callable):执行带返回值任务
  • shutdown():关闭线程池

–

  Executors
  

:工具类,用于快速创建各类线程池

  • newCachedThreadPool():按需创建线程
  • newFixedThreadPool(n):固定数量线程池
  • newSingleThreadExecutor():单一线程池
  • newScheduledThreadPool(n):支持延迟、定时任务
public static void main(String[] args) {
    ChatThread1 chatThread1 = new ChatThread1();
    // 创建固定线程池
    ExecutorService executorService = Executors.newFixedThreadPool(100);

    // 提交任务
    executorService.execute(chatThread1);
    // 关闭线程池
    executorService.shutdown();
}

池化技术:线程池、数据库连接池、内存池。目的:复用资源,减少创建销毁带来的性能损耗。

线程停止

我们举个栗子:假设从网络下载一个100M的文件,如果网速很慢,用户等得不耐烦,就可能在下载过程中点“取消”,这时,程序就需要中断下载线程的执行。

使用interrupt()方法

中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

1.thread.interrupt():通知线程中断(设置中断标志位)。
2.Thread.currentThread().isInterrupted():检查当前线程是否被中断。

public class InterruptExample {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println("线程正在运行...");
                    Thread.sleep(500); // 可能抛出 InterruptedException
                } catch (InterruptedException e) {
                    System.out.println("线程在阻塞时被中断,准备退出");
                    Thread.currentThread().interrupt(); // 重新设置中断标志
                }
            }
            System.out.println("线程结束");
        });

        worker.start();
        Thread.sleep(2000);
        worker.interrupt(); // 请求中断
    }
}

使用共享变量(volatile 标志位)

创建一个布尔类型的共享变量(标志位),线程在run()方法的循环中持续检查这个标志位。当外部需要停止线程时,将这个标志位设置为true,线程检测到标志位变为true后,自行完成当前任务并退出循环,从而结束线程。 如果标志位是多个线程共享的,应该使用volatile关键字确保其可见性。

   static void main() throws InterruptedException {
   DownloadThread thread = new DownloadThread();
    thread.start();

    Thread.sleep(2000);
    thread.stopThread();
}

public class DownloadThread extends Thread{
    private volatile boolean isRunning = true;

    @Override
    public void run() {
        boolean isDownloading = true;
        while (isRunning && isDownloading) {
            System.out.println("DownloadThread.run");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("线程结束");
    }

    public void stopThread() {
        // 在需要时设置标识=false,停止线程
        isRunning = false;
    }
}

自定义线程常用API

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 11; i++) {
            System.out.println(Thread.currentThread().getName() + ':' + i);
        }
    }
}

public static void main(String[] args) {
    MyThread myThread = new MyThread();
    myThread.setName("线程一");
    Thread.currentThread().setName("主线程");

    myThread.start();
}
  • getName():获取线程名称
  • setName():设置线程名称
  • Thread.currentThread():获取当前正在执行的线程对象

线程的调度

调度策略

  1. 时间片轮转
  2. 抢占式:高优先级线程抢占CPU

优先级范围

Java线程优先级:1~10,默认优先级5

  • MAX_PRIORITY(10) 最高
  • MIN_PRIORITY(1) 最低
  • NORM_PRIORITY(5) 默认

常用调度方法

  1. setPriority() / getPriority():设置、获取线程优先级
  2. yield():线程让步,暂停当前线程;让出CPU给同优先级或更高优先级线程
  3. join():等待其他线程执行完毕,当前线程阻塞
public static void main(String[] args) {
    Thread t1 = new Thread(() -> System.out.println("t1"));
    Thread t2 = new Thread(() -> {
        try {
            t1.join();
            System.out.println("t2");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    t1.start();
    t2.start();
}
  1. sleep(long millis):休眠指定时间,放弃CPU,时间到重新排队

2.

   isAlive()
   

:判断线程是否存活

⚠stop()已经废弃,不推荐使用


线程的生命周期

image-20260728205312616

image-20260820092637314

Java线程六大状态(Thread.State枚举)

  1. NEW 新建:new Thread()创建对象,未调用start()
  2. RUNNABLE 可运行:调用start(),等待CPU调度 / 正在运行
  3. BLOCKED 阻塞:等待获取synchronized同步锁
  4. WAITING 无限等待:调用wait()、join(),等待其他线程唤醒
  5. TIMED_WAITING 计时等待:sleep、wait(时间),超时自动唤醒
  6. TERMINATED 终止:线程执行完毕 / 异常退出,生命周期结束

状态流转:NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → RUNNABLE → TERMINATED


线程同步机制

多个线程操作共享资源会产生线程安全问题,需要同步锁。

1. synchronized 同步锁

synchronized方法是使用this锁去做线程的共享互斥。this 指的是当前对象实例本身,所以,所有使用 synchronized(this) 方式的方法都共享同一把锁。

(1)同步代码块

synchronized(锁对象) {
    // 需要保证原子性的代码
}

要点:

  1. ()内必须是唯一对象(锁)
  2. 同一时刻只能有一个线程持有该锁
  3. 持有锁的线程执行完毕,自动释放锁;其他线程阻塞等待

(2)同步方法

  • 非静态同步方法:锁对象默认是this
  • 静态同步方法:锁对象是当前类的Class对象

卖票示例:

public class Window extends Thread{
    private Integer ticket =100;

    //同步方法
    synchronized public void sale(){
        if (ticket <= 0) {
            return;
        }
        System.out.println(Thread.currentThread().getName() + "销售了第" + ticket + "张票");
        ticket--;
    }

    @Override
    public void run() {
        while(true){
            sale();
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2. Lock显式锁(JDK5新增)

ReentrantLock实现Lock接口,手动加锁、手动释放锁,灵活性更高

规范:unlock()必须写在finally中,保证锁一定释放

public class Window extends Thread{
    private Integer ticket =100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock(); // 加锁
                if (ticket <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "销售了第" + ticket + "张票");
                ticket--;
            } finally {
                lock.unlock(); //释放锁
            }
        }
    }
}

synchronized VS Lock synchronized:隐式锁,自动获取释放;Lock:显式锁,手动控制,支持公平锁/非公平锁、可中断等待

Synchronized类方法和Synchronized阻挡

假设现在有一个类型如下的synchronized的类方法,synchronized类方法有限制同时只能让一个线程执行。这部分和synchronized实例方法一样,但是两者是有不同的。

class Something{   
static synchronized void method1(){           
}    

static synchronized void method2() {  
} 
}


在功能上和下面以synchronized阻挡为主的方法有异曲同工之妙。

public class Something{   
public static void method(){      
synchronized(Something.class){             
}    
}
}

换句话说synchronized的类方法是使用该类的类对象的锁去做线程的共享互斥。Something.class是对应Something类的java.lang.Class类的实例。


线程通信

配套方法:wait() / notify() / notifyAll() ⚠限制:只能在synchronized代码块/同步方法内部调用

  1. wait():当前线程挂起,释放锁,进入等待队列,等待被唤醒
  2. notify():随机唤醒一个等待该锁的线程
  3. notifyAll():唤醒所有等待该锁的线程

交替打印数字示例:

public class PrintNum implements Runnable{
    private Integer i =1;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                if(i>20){
                    break;
                }
                this.notify();
                System.out.println(Thread.currentThread().getName()+":"+i);
                i++;
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

wait() 和 sleep() 核心区别

  1. 所属类
  • sleep():Thread静态方法
  • wait():Object成员方法,所有对象都拥有
  1. 锁机制(重中之重)
  • sleep():不会释放锁资源
  • wait():执行后主动释放锁
  1. 唤醒条件
  • sleep():时间到自动唤醒;无法被notify唤醒
  • wait():可以等待超时自动唤醒,也可以被notify/notifyAll唤醒
  1. 调用前提
  • sleep():任意位置都能调用
  • wait():必须在同步代码块内调用

wait进入wait set 被唤醒的方式

  1. 有其他线程以 notify 方法唤醒该线程
  2. 有其他线程以 notifvAll 方法唤醒该线程
  3. 有其他线程以 intetrupt 方法唤醒该线程
  4. wat 方法已然到期。

Notify后的线程

被notify唤醒的线程不是在notify后立即执行,因为在notify的那一刻执行notify 的线程还握着锁定不放,所以其他线程无法获取该实例的锁定。

Notify如何选择线程

假设执行notify方法时,wait set里面正在执行的线程不止一个。规格并没有注明此时该选择哪一个线程。究竟是选择等待线程里面的第一个,随机选择或是另以其他方式选择,则以java处理系统而异。

obj.notifyAll()则会唤醒所有留在实例obj的wait set里的线程。

跟wait方法和notify方法一样,线程必须要获取要调用实例的锁定,才能调用notifyAll方法。

被唤醒的线程便开始去获取刚才wait时释放掉的锁定,那么现在这个锁定现在是在谁的手中呢?没错,锁定就是在刚才执行notifyAll方法的程序手里,因此即使所有线程都退出了wait set,但他们仍然在去获得锁定的状态下,还是有阻挡。要等到刚才执行notifyAll方法的线程释放出锁定后,其中一名幸运儿才会实际执行。

要是没有锁定synchronized呢

若没有锁定的线程去调用wait,notify或notifyAll时,便会抛出异常java.lang.IllegalMonitorStateException.

wait,notify,notifyAll是Object类的方法

obj.wait()是把现在的线程放到obj的wait set

obj.notify()是从obj的wait set里唤醒一个线程

obj.notifyAll()是唤醒所有在obj的wait set里的线程

换句话说,把wait、notify、notifyAll三者均解释为对实例对象的wait set的操作,会比说他们是对线程的操作更贴切,由于所有实例都会有wait set,所以wait、notify、notifyAll才会是Object类的方法。

虽然三者不是Thread类固有的方法,不过,因为Object类是Java所有类的祖先类,所以wait、notify、notifyAll也是Thread的方法。

yield 屈服,让步;放弃,让出

Thread.yield();,表示暂停当前线程,执行其他线程(包括执行yield这个线程), 执行谁由cpu决定 yield这个方法是让当前线程回到可执行状态,以便让具有相同优先级的线程进入执行状态(包括这个执行yield的线程,因为其也在可执行状态)

public static native void yield();

1.yield是一个静态的原生(native)方法 2.yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。 3.yield不能保证使得当前正在运行的线程迅速转换到可运行的状态 4.它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态

JNI (Java Native Interface) 是 Java 沟通 Native 语言(主要是C、C++)的桥梁。 Native 语言有着更高的性能,更重要的是还有庞大的 Native 语言所编写的库。 这些给 Java 语言提供很多性能优秀的强大功能支持。 JNI 能够提供 Java 更快速的 Native 功能移植,也能利用 C++ 的反汇编能力加强代码安全性。

join

在某些情况下,如果子线程里要进行大量的耗时的运算,主线程可能会在子线程执行完之前结束,但是如果主线程又需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()。 阻塞所在线程,等调用它的线程执行完毕,再向下执行 a.join,在API中的解释是,在B线程中调用a.join(),堵塞当前线程B,直到A执行完毕并死掉,再执行B。

public final void join() throws InterruptedException

join()方法使用示例

   public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(new Runnable() {
         public void run() {
            System.out.println("First task started");
            System.out.println("Sleeping for 2 seconds");
            try {
               Thread.sleep(2000);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
            System.out.println("First task completed");
         }
      });
      Thread t2 = new Thread(new Runnable() {
         public void run() {
            System.out.println("Second task completed");
         }
      });
      t1.start(); 
      t1.join(); // Main线程等待t1线程执行完毕,在执行下面的操作
      t2.start();
   }
}

执行结果 First task started Sleeping for 2 seconds First task completed Second task completed


线程池

什么是线程池?

线程池(Thread Pool)是一种线程复用技术:

  1. 事先创建好一定数量的线程放在池子里(像“工人”一样待命)
  2. 当有任务要执行时,不需要重新创建线程,直接复用池内线程执行任务
  3. 任务执行完毕,线程不会销毁,回到池中等待下一个任务

本质:线程池是管理线程的容器,负责线程的创建、调度、销毁。

为什么使用线程池(类比数据库连接池 Druid / HikariCP / C3P0)

  1. 减少资源消耗:复用线程,避免频繁创建、销毁线程带来的系统调用、上下文切换开销。
  2. 提升响应速度:线程预先创建,任务到来直接使用,提升响应。
  3. 便于线程管理:无限制新建线程会造成 OOM、CPU 占满,线程池可以管控线程数量。

线程池核心类

image-20260822094334041

  • Executor:线程池顶级接口,只定义 execute()
  • ExecutorService:真正的线程池接口,继承 Executor,扩展提交、关闭等能力
  • ThreadPoolExecutor:ExecutorService 的核心实现类,自定义线程池就用这个类
  • Executors:线程池工厂工具类,用来快速生成线程池(阿里巴巴规范不推荐生产直接使用)

###Executors 提供的4种线程池

  1. newFixedThreadPool(n):固定大小线程池,核心线程=最大线程,队列无界。
  2. newCachedThreadPool():按需创建线程,空闲线程会回收,最大线程无界。
  3. newSingleThreadExecutor():单线程池,保证任务顺序串行执行。
  4. newScheduledThreadPool(n):支持定时、周期性执行任务。

阿里巴巴开发规范:生产环境禁止直接使用 Executors 创建线程池,建议手动 new ThreadPoolExecutor,规避无界队列/无限线程导致 OOM。

image-20260822094240711

自定义线程池:ThreadPoolExecutor 七大核心参数

public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler
)
参数 说明
corePoolSize 核心线程数,常驻线程,默认不会回收
maximumPoolSize 最大线程数 = 核心线程 + 非核心线程
keepAliveTime 非核心线程空闲超时时间,超时就销毁
unit 时间单位:TimeUnit.SECONDS / MILLISECONDS / MINUTES
workQueue 阻塞任务队列,核心线程满了,任务进队列排队
threadFactory 线程工厂,用来创建线程,可以自定义线程名
handler 拒绝策略,队列和最大线程全部满了,新任务执行拒绝策略

拒绝策略常见实现

  1. AbortPolicy:默认,直接抛出 RejectedExecutionException
  2. CallerRunsPolicy:调用者线程自己执行任务,不丢弃不抛异常
  3. DiscardPolicy:直接丢弃任务,不抛异常
  4. DiscardOldestPolicy:丢弃队列最老任务,尝试提交当前新任务
  5. image-20260822094453586

线程池执行流程(execute())

image-20260822094355128

image-20260822094418092

刚创建线程池时,内部线程数量为0。调用 execute() 提交任务:

  1. 如果运行线程数 < corePoolSize:新建核心线程执行任务。
  2. 如果运行线程数 >= corePoolSize:任务放入阻塞队列等待。
  3. 如果队列已满,且运行线程数 < maximumPoolSize:创建非核心线程执行任务。
  4. 如果队列已满,且运行线程数 >= maximumPoolSize:触发拒绝策略。

触发拒绝策略条件:提交任务数 > workQueue容量 + maximumPoolSize

  1. 线程执行完任务,会循环从队列获取下一个任务。
  1. 线程空闲超过 keepAliveTime:
  • 如果当前线程数 > corePoolSize → 销毁该非核心线程;
  • 最终线程池收缩至 corePoolSize 大小。

代码示例

示例1:Executors工具类创建(仅学习,不建议生产)

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        //一池5线程
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        //一池一线程
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //一池可扩容线程
        //ExecutorService threadPool = Executors.newCachedThreadPool();

        try {
            //模拟10个任务
            for (int i = 1; i <=10; i++) {
                final int index = i;
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+" 为第"+index+"个客户办理业务");
                    }
                });
            }
        } finally {
            //关闭线程池
            threadPool.shutdown();
        }
    }
}

示例2:手动自定义 ThreadPoolExecutor(生产推荐)

import java.util.concurrent.*;

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,                          //核心线程数
                5,                          //最大线程数
                2L,                         //空闲超时
                TimeUnit.SECONDS,           //时间单位
                new ArrayBlockingQueue<>(3),//有界阻塞队列容量3
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() //拒绝策略
        );

        try {
            //模拟10个任务
            for (int i = 1; i <=10; i++) {
                final int index = i;
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+" 为第"+index+"个客户办理业务");
                    }
                });
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}

补充面试小要点

  1. execute():提交 Runnable,无返回值;
  2. submit():提交任务,返回 Future 对象,可以获取返回结果;
  3. shutdown():温和关闭,已提交任务继续执行,不再接收新任务;
  4. shutdownNow():尝试中断正在执行任务,返回未执行任务列表。

银行通俗比喻:
corePoolSize:常驻窗口;maximumPoolSize:全部可打开窗口;workQueue:大厅等候座位;
窗口全开,座位坐满,再来顾客 → 执行拒绝策略。

死锁

定义:多个线程互相持有对方需要的锁,互相等待对方释放资源,所有线程永久阻塞,程序无法继续执行。

现象:无异常、无报错,程序卡住不动

死锁代码示例

public class A extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized (Lock.m) {
                synchronized (Lock.n)
                {
                    System.out.println("A..........");
                }
            }
        }
    }
}

public class B extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Lock.n) {
                synchronized (Lock.m)
                {
                    System.out.println("B..........");
                }
            }
        }
    }
}

//锁资源
public class Lock {
    public static Object m =new Object();
    public static Object n =new Object();
}

public static void main(String[] args) {
    Thread t1 = new Thread(new A());
    Thread t2 = new Thread(new B());
    t1.start();
    t2.start();
}

死锁四个必要条件:互斥、请求保持、不可剥夺、循环等待;破坏任意一条即可避免死锁。常见方案:统一锁的获取顺序。


生产者消费者模型(经典线程通信案例)

问题描述:固定容量仓库,生产者生产商品放入仓库,消费者取出商品; 约束:仓库满 → 生产者等待;仓库空 → 消费者等待。

//仓库
public class Store {
    public static ArrayList<Object> list =new ArrayList<>(100);
    public static final int MAX_NUM = 100;
}

//生产者
public class Producer extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Store.list){
                if (Store.list.size() < Store.MAX_NUM) {
                    Store.list.notifyAll();
                    Store.list.add(new Object());
                    System.out.println(Thread.currentThread().getName()+"-生产商品,仓库目前数量:"+Store.list.size());
                }else {
                    System.out.println(Thread.currentThread().getName()+"-仓库已满,停止生产");
                    try {
                        Store.list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

//消费者
public class Consumer extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Store.list) {
                if (Store.list.size() >0) {
                    Store.list.notifyAll();
                    Store.list.remove(0);
                    System.out.println(Thread.currentThread().getName()+"-消费商品,仓库目前数量:"+Store.list.size());
                }else {
                    System.out.println(Thread.currentThread().getName()+"-仓库已空,停止消费");
                    try {
                        Store.list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

//测试
public class Mytest {
    public static void main(String[] args) {
        Producer p1 = new Producer();
        Consumer p2 = new Consumer();
        p1.start();
        p2.start();
    }
}

集合的线程安全、

集合类型 类名 线程安全 底层实现 简要特点与适用场景
List ArrayList ❌不安全 动态数组 单线程使用;查询快,增删慢,多线程读写会数据异常
List LinkedList ❌不安全 双向链表 单线程;插入删除快,查询慢,并发下不安全
List Vector ✅安全 动态数组 所有方法synchronized锁对象,性能差,已过时
List Collections.synchronizedList ✅安全 包装原有 List,加 synchronized 锁整个集合,并发性能差;遍历需要手动加锁
List CopyOnWriteArrayList ✅安全 写时复制数组 读不加锁,写加锁;适合读多写少;写操作拷贝数组,内存开销大
Set HashSet ❌不安全 HashMap(value 固定占位对象) 单线程;无序,去重;并发读写数据错乱
Set TreeSet ❌不安全 TreeMap 单线程;元素自然排序;并发不安全
Set Collections.synchronizedSet ✅安全 包装原有 Set,synchronized 加锁 性能低,遍历需手动加锁
Set CopyOnWriteArraySet ✅安全 CopyOnWriteArrayList 读多写少;底层写时复制,不适合写多场景
Set ConcurrentSkipListSet ✅安全 跳表 线程安全,元素有序;高并发场景使用
Map HashMap ❌不安全 数组 + 链表 + 红黑树 (JDK1.8) 单线程;key 允许 1 个 null;并发会丢数据、死循环
Map TreeMap ❌不安全 红黑树 单线程;key 有序;并发不安全
Map Hashtable ✅安全 数组 + 链表 方法全部synchronized锁整个对象;key、value 不能为 null,性能差,淘汰
Map Collections.synchronizedMap ✅安全 包装 Map,synchronized 加锁 锁整个对象,并发性能差
Map ConcurrentHashMap ✅安全 JDK1.7:Segment 分段锁JDK1.8:CAS + synchronized 锁桶头 高并发首选;只保证单个方法原子性,复合操作仍要注意;key、value 不能为 null
Map ConcurrentSkipListMap ✅安全 跳表 线程安全,key 有序;并发有序 Map
上一篇
下一篇