java中的线程

一个应用程序一般就是一个进程,一个进程中至少包含一个线程。线程比进程更轻量级,切换代价更小。合理时用多线程能显著提升系统性能。

新建线程的方式

java中新建一个线的方式有4种,本质上只有一种核心方式:构造 Thread 对象,其余都是实现 Runnable / Callable 传给 Thread。

1、继承Thread类重写run方法

java 复制代码
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("线程执行");
    }
}

// 使用
MyThread t = new MyThread();
t.start(); // 启动线程,不是直接调用run()

2、实现Runnable接口,重写run方法,交给Thread构造器

java 复制代码
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("runnable线程");
    }
}

// 使用
Thread t = new Thread(new MyRunnable());
t.start();

也可以匿名内部类 / Lambda 简写:

java 复制代码
//匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("匿名");
    }
}).start();

// Lambda(Runnable是函数式接口)
new Thread(() -> System.out.println("lambda线程")).start();

3、实现Callable+FutureTask(可以带返回值,抛出异常)

java 复制代码
Callable<Integer> callable = () -> {
    //业务逻辑
    return 100;
};
FutureTask<Integer> task = new FutureTask<>(callable);
Thread t = new Thread(task);
t.start();

Integer res = task.get(); //阻塞等待获取返回结果

4、线程池

通过线程池创建线程,不手动 new Thread,由池管理复用线程。

java 复制代码
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(()-> System.out.println("线程池任务")); //执行Runnable

//提交Callable任务获取返回
Future<Integer> future = pool.submit(()-> 200);
pool.shutdown();

线程的状态

java中线程一共有6种状态:

新建(NEW):当程序中执行了new Thread();

可运行(RUNNABLE):当线程执行了start()方法,包含两种情况:CPU 正在执行该线程(运行中)、等待 CPU 分配时间片(就绪)。JVM 把运行 + 就绪统一叫 RUNNABLE。IO 阻塞时线程依然是 RUNNABLE,不是 BLOCKED。

等待(WAITING):当线程执行了Object.wait()方法,注意该该方法是Object的方法,执行前需要先拿到对象锁,否则将抛出异常。进入等待状态后,只有等其他线程调用notify() / notifyAll()唤醒。

超时等待(TIMED_WAITING):当线程执行了wait(long)或sleep(long),线程可以被打断唤醒也可以等待超时后自动唤醒。

阻塞(BLOCKED):只针对 synchronized 锁,多个线程同时竞争锁时,同时只能有一个线程抢到锁,其余没抢到锁的线程将被阻塞。

终止(TERMINATED):线程执行完毕,或者异常退出。线程生命周期结束,不能再次 start ()。

Java 线程相关核心方法

分为:Thread类方法、Object监视器方法、LockSupport工具方法

1、Thread类方法

  • start():作用为向 JVM 申请创建操作系统线程,自动调用run()。一个线程只能调用一次start()。
  • run():线程的业务逻辑方法,由 JVM 回调执行。直接调用run():就是普通方法,不会开启新线程。
  • sleep(long millis) :静态方法,作用为让当前线程休眠指定毫秒,交出 CPU 时间片。不会释放持有的 synchronized 锁、可被interrupt()打断,抛出InterruptedException。
  • yield(): 静态方法,作用为提示 CPU 让出时间片,回到就绪状态,不一定生效,取决于操作系统调度。
  • join() / join(long millis):作用为让当前线程等待目标线程执行完毕。无参:进入WAITING;带超时:TIMED_WAITING。可被 interrupt 打断抛异常。底层调用 wait (),会释放锁。
  • interrupt(): 实例方法,作用为给线程打上中断标记,不是直接终止线程!如果线程处于sleep/wait/join阻塞中,会直接抛出InterruptedException,同时清除中断标记。
  • isInterrupted(): 实例方法,判断线程的中断标记,不清除标记。
  • Thread.interrupted():静态方法,判断当前线程中断标记,调用后清除中断标记。
  • setDaemon(true) / isDaemon():设置守护线程,必须在start()之前调用。用户线程全部结束,JVM 直接退出,守护线程会被直接终止。通过 new Thread() 创建出来的线程,继承创建它的父线程的守护标记。main 线程是非守护线程,所以 main 里面 new 出来的线程默认就是非守护。
  • setPriority(int) :设置优先级,范围 1‑10,默认 5。只是给操作系统提示,不保证一定生效。
    2、Object 类监视器方法(必须在 synchronized 同步块内调用)
  • wait() / wait(long timeout):当前线程释放锁,进入对象等待集。会释放 synchronized 锁。被notify/notifyAll唤醒,或者超时;唤醒后重新去竞争锁,拿到锁之后才继续执行。
  • notify():随机唤醒该对象等待集里一个等待线程。不会立刻释放锁,执行完同步块才释放。
  • notifyAll():唤醒对象等待集全部等待线程,全部去竞争锁。
    3、LockSupport(J.U.C 底层工具,AQS 依赖)
  • park():阻塞当前线程,进入WAITING
  • parkNanos():限时阻塞,TIMED_WAITING
  • unpark(thread):给指定线程发放许可,唤醒。

方法使用示例

1、join()/join(long millis)

在某个线程中等待另一个线程执行完或者执行一段时间。

java 复制代码
 /**
     * 在线程1中休眠5s,在线程2中调用线程1的join方法,线程2会等待线程1执行完毕再执行
     */
    public static void test1() {
        Thread thread1 = new Thread(() -> {
            try {
                System.out.println("线程1开始执行");
                Thread.sleep(5000);
                System.out.println("线程1执行完毕");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                thread1.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程2执行完毕");
        });

        thread1.start();
        thread2.start();
    }

若是线程2比线程1先执行,线程2会直接执行结束不会等线程1执行。因为join方法底层调用了object.wati()方法,且调用前先判断线程1是否活跃。一个线程如果已经启动(调用了 start())且尚未死亡,就被认为是"活跃"的。在Java的线程生命周期中,这涵盖了:RUNNABLE(就绪/运行)、BLOCKED(阻塞)、WAITING(无限期等待)、TIMED_WAITING(限期等待)这几种状态。

wati()方法执行前需要先获取锁,join()方法能直接调用是因为join()方法添加了synchronized,所以thread1.join();锁对象为thread1(成员方法上加synchronized,锁定的是调用该方法的实例,若是静态方法加synchronized锁对象为对应的Class对象)。thread2线程内部调用thread1.join(),实质是thread2获得thread1对象锁,然后执行thread1的wati()方法,thread2将释放thread1对象锁,并加入到thread1的阻塞队列中,当thread1执行完后JVM 底层自动调用thread1.notifyAll(),唤醒等待在 thread1 上的线程 2。线程2被唤醒后继续执行join内的while循环,再次判断线程1是否活跃时返回false,从而退出thread1.join()方法,继续执行线程2中剩下的代码。

2、interrupt()

实例方法,设置中断标记位为true,线程不会立即终止,需要自行判断处理。若是执行interrupt()方法前线程已经调用了sleep/wati/join 处于阻塞中,线程会抛出InterruptedException异常并重置中断标记为false。

java 复制代码
 public static void main(String[] args) {

        // sleep中的线程被打断,会抛出InterruptedException
        // 启用一个线程休眠5s 在主线程打断他
        Thread thread1=new Thread(()->{
            System.out.println("线程1开始执行");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println("线程1被打断");
            }
            System.out.println("线程1执行结束");
        });

        thread1.start();
        System.out.println(thread1.isInterrupted());
        thread1.interrupt();

        System.out.println("主线程执行结束");
    }

3、isInterrupted()

实例方法,获取当前中断标志,只获取不重置。

java 复制代码
public class IsInterruptedDemo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread workThread = new Thread(() -> {
            // 轮询判断中断标记,isInterrupted()不会清除标记
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程正在执行业务...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // sleep被打断,JVM已经清空中断标记,需要手动恢复标记
                    Thread.currentThread().interrupt();
                    System.out.println("sleep被打断,手动恢复中断标记");
                }
            }
            System.out.println("检测到中断标记为true,线程退出");
        });

        workThread.start();
        Thread.sleep(2000);
        // 发送中断信号
        workThread.interrupt();
    }
}

4、Thread.interrupted()

静态方法,获取中断标记,并重置中断标记为false。

java 复制代码
public class InterruptedDemo1 {
    public static void main(String[] args) {
        // 给当前main线程打上中断标记 true
        Thread.currentThread().interrupt();

        // isInterrupted():偷看,不清空
        System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); // true

        // Thread.interrupted():读取并且清除标记
        System.out.println("Thread.interrupted():" + Thread.interrupted()); // true

        // 标记已经被清除,现在是false
        System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted()); // false

        System.out.println("Thread.interrupted():" + Thread.interrupted()); // false
    }
}

5、 wait()

wait方法属于Object,作用是让当前线程释放锁,并进入锁对象的等待队列,等待被唤醒(其他线程执行notify/notifyAll)。调用线程必须持有该对象的 synchronized 监视器锁,必须写在 synchronized 块 /synchronized 方法内。wait 可以在没有 notify、没有超时、没有 interrupt 的情况下意外苏醒(虚假唤醒,操作系统层面的原因,jvm视为可能会发生的正常情况)。为了避免虚假唤醒导致程序执行异常,推荐使用while循环替代if。

java 复制代码
synchronized (lock) {
    // 条件不满足就一直wait;被唤醒后再次校验条件
    while (!isReady) {
        lock.wait(); 
    }
    // 条件满足执行业务
}

线程被唤醒后,还是先得到锁对象才能继续执行wait后续代码。

java 复制代码
public class WaitNotifyDemo {
    private static final Object lock = new Object();
    private static boolean hasData = false;

    public static void main(String[] args) {
        //消费者
        new Thread(() -> {
            synchronized (lock) {
                while (!hasData) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("消费数据");
                hasData = false;
            }
        }).start();

        //生产者
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("生产数据");
                hasData = true;
                lock.notify(); //唤醒等待线程
            }
        }).start();
    }
}

生产者消费者案例中,消费者会等待生产者生产完成后再执行消费,否则消费者将等待生产者生产。

线程池

1、核心参数

  1. corePoolSize核心线程数
  2. maximumPoolSize最大线程数
  3. keepAliveTime空闲存活时间
  4. unit时间单位
  5. workQueue阻塞队列
  6. threadFactory线程工厂
  7. handler拒绝策略。
  • AbortPolicy:抛出异常(默认)
  • DiscardPolicy:直接丢弃任务,不抛异常
  • DiscardOldestPolicy:丢弃队列最老任务,执行当前
  • CallerRunsPolicy:调用者线程执行任务

2、线程池执行流程

任务提交到线程池,首先有无空闲核心线程,有则处理,没有则判断当前线程是否<核心线程数,小于则创建核心线程处理,否则将任务添加到阻塞队列,当阻塞队列放满后还有新来的任务待处理则判断当前核心线程数是否小于最大线程数,小于则创建临时线程处理,若队列已满且已到达最大线程数还有新任务待处理,则执行拒绝策略。

java 复制代码
提交任务 execute()
       ↓
① 当前工作线程数 < corePoolSize ?
    ├─✅ → 创建【核心线程】执行任务 → 结束
    └─❌ → ②任务尝试加入阻塞队列 workQueue
                ↓
            ②队列是否已满?
                ├─❌ → 任务存入队列等待 → 结束
                └─✅ → ③当前工作线程数 < maximumPoolSize ?
                            ├─✅ → 创建【非核心线程】执行任务 → 结束
                            └─❌ → ④执行拒绝策略 RejectedExecutionHandler

JDK默认的4种线程池

Executors 是工厂类,底层全部是 ThreadPoolExecutor / ScheduledThreadPoolExecutor。阿里开发手册禁止生产直接使用 Executors,因为隐藏风险:要么无界队列,要么最大线程无上限,容易 OOM / 线程耗尽。

1、newFixedThreadPool (int nThreads) 固定线程池

java 复制代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
        nThreads,        //核心线程
        nThreads,        //最大线程
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>() //无界队列,Integer.MAX_VALUE
    );
}

特点:核心 = 最大线程,线程不会回收;任务多就往无界队列放

风险:任务堆积,队列无限增大 → OOM 内存溢出

适用:测试、任务量可控场景。

2、newSingleThreadExecutor () 单线程池

java 复制代码
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>()));
}

特点:只有 1 个工作线程,任务串行执行;线程异常会自动重建新线程

风险:队列同样无界,任务大量提交 → OOM

适合:需要顺序执行任务。

3. newCachedThreadPool () 缓存线程池

java 复制代码
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0,
        Integer.MAX_VALUE, //最大线程几乎无限
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()
    );
}

特点:核心线程 0;空闲线程 60 秒回收;SynchronousQueue 队列不存任务,来任务直接新建线程

风险:任务爆发会疯狂创建大量线程,CPU、内存耗尽,系统雪崩

适合:大量短时小任务。

4. newScheduledThreadPool (int corePoolSize) 定时线程池

java 复制代码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

底层继承ThreadPoolExecutor

特点:核心线程自定义,maximumPoolSize=Integer.MAX_VALUE,线程可无限创建,支持延时执行、周期性定时任务 schedule() / scheduleAtFixedRate()

风险:线程数无上限,任务阻塞会不断新建线程。

相关推荐
盗理者3 小时前
AI Agent 技能分享|SQL 性能诊断与优化
java·sql·spring·skill
GitLqr3 小时前
Java 26 终于原生支持 HTTP/3 了:告别 Netty,直接用 QUIC
java·netty·http3
用户40966601317513 小时前
Jackson 序列化:@JsonIgnore / @JsonProperty / @JsonFormat / @JsonInclude / @JsonUnwrapped 一次讲清楚
java·后端
码路漫漫3 小时前
用了 Caffeine,消息为什么还是被处理了两次?
java
云和数据.ChenGuang3 小时前
fastapi项目拆分实战数据模型
java·服务器·数据库·人工智能·深度学习·fastapi·强化学习
小龙报4 小时前
【优选算法】1.搜索插入位置 2.x的平方根
java·c语言·数据结构·c++·python·算法·蓝桥杯
山荷枝4 小时前
04-框架--SpringBoot
java·spring boot·后端
linux修理工4 小时前
内存占用 99% 且 Java 程序较多的优化建议
java·开发语言
weixin_460443564 小时前
企业考试系统如何对接OA、钉钉和企业微信?SSO单点登录、组织同步与权限一致性设计
java·开发语言·数据库
Sylvia33.4 小时前
篮球数据API的技术架构与工程实践:基于火星数据WebSocket实时推送体系
java·python·websocket·网络协议·架构