Bootstrap

线程的基本操作

1. 线程的基本概念

线程是什么?

  • 线程是程序执行的最小单位,是操作系统能够进行调度的基本单元。
  • 一个程序至少包含一个线程(主线程),它由main方法启动。
  • 线程共享同一个进程的资源,例如内存空间,但线程本身有独立的运行栈和程序计数器。

线程的生命周期

Java线程的生命周期可以分为以下几个状态:

  1. New(新建)

    • 当线程对象被创建,但还没有调用start方法时,线程处于新建状态。
      示例:
    Thread thread = new Thread();
    
  2. Runnable(可运行)

    • 调用了start方法后,线程进入可运行状态。注意,它不一定立即运行,而是等待CPU调度。
      示例:
    thread.start();
    
  3. Running(运行中)

    • 当CPU开始执行该线程的代码时,线程进入运行状态。
  4. Blocked(阻塞)

    • 当线程尝试获取一个被其他线程占用的锁,或者等待资源释放时,线程进入阻塞状态。
  5. Waiting/Timed Waiting(等待/超时等待)

    • Waiting:线程主动调用wait方法,等待其他线程唤醒。
    • Timed Waiting:线程调用sleep或带超时参数的wait方法,指定等待时间后自动唤醒。
  6. Terminated(终止)

    • 当线程的run方法执行完毕或被显式中止时,线程进入终止状态。
      示例:
    System.out.println("Thread finished.");
    

2. 线程的创建与启动

创建线程的两种基础方式

  1. 继承Thread
    创建一个子类继承Thread类,并重写其run方法。

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread is running...");
        }
    }
    public class Main {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start();  // 启动线程
        }
    }
    
  2. 实现Runnable接口
    创建一个实现Runnable接口的类,并将其传递给Thread对象。

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread is running...");
        }
    }
    public class Main {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();  // 启动线程
        }
    }
    

start方法和run方法的区别

  1. start方法

    • 用于启动线程,将线程加入到就绪队列,等待CPU调度。
    • 它会调用线程的run方法,但在一个独立的线程中执行。
    • 每个线程只能调用一次start方法,重复调用会抛出IllegalThreadStateException
    • 示例:
      Thread thread = new Thread(() -> System.out.println("Running in a new thread!"));
      thread.start(); // 启动线程
      
  2. run方法

    • Thread类中的普通方法,定义了线程的任务逻辑。
    • 如果直接调用run方法,它会在当前线程中执行,而不是创建一个新线程。
    • 示例:
      Thread thread = new Thread(() -> System.out.println("Running in current thread!"));
      thread.run(); // 没有启动新线程
      

区别总结

特性start 方法run 方法
是否创建新线程
线程状态切换到 Runnable 状态,等待调度直接在调用线程中执行
调用次数只能调用一次,重复调用抛异常可多次调用

3. 线程的控制方法

1. wait 方法

  • 作用:使当前线程进入等待状态,并释放锁,直到其他线程调用notifynotifyAll唤醒它。

  • 关键点

    • 必须在同步块或同步方法中使用,否则会抛出IllegalMonitorStateException
    • 调用wait后,线程进入WAITING状态,等待被唤醒。
  • 示例代码

    class WaitExample {
        private static final Object lock = new Object();
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                synchronized (lock) {
                    try {
                        System.out.println("Thread1: Waiting...");
                        lock.wait();
                        System.out.println("Thread1: Resumed!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("Thread2: Notifying...");
                    lock.notify();
                }
            });
    
            thread1.start();
            try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
            thread2.start();
        }
    }
    
  • 输出

    Thread1: Waiting...
    Thread2: Notifying...
    Thread1: Resumed!
    

2. sleep 方法

  • 作用:让当前线程暂停指定时间,不释放锁,进入TIMED_WAITING状态。

  • 关键点

    • 它是静态方法,作用于当前线程。
    • 不影响锁的持有状态,其他线程仍然无法访问被锁住的资源。
  • 示例代码

    public class SleepExample {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                System.out.println("Thread: Sleeping...");
                try {
                    Thread.sleep(2000); // 暂停2秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread: Awake!");
            });
            thread.start();
        }
    }
    
  • 输出

    Thread: Sleeping...
    (等待2秒)
    Thread: Awake!
    

3. join 方法

  • 作用:让一个线程等待另一个线程的完成。

  • 关键点

    • join可以指定等待时间,超时后会继续执行。
    • 调用线程进入WAITINGTIMED_WAITING状态,直到目标线程完成或时间耗尽。
  • 示例代码

    public class JoinExample {
        public static void main(String[] args) {
            Thread thread = new Thread(() -> {
                System.out.println("Thread: Working...");
                try {
                    Thread.sleep(3000); // 模拟耗时任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread: Done!");
            });
    
            thread.start();
            System.out.println("Main thread: Waiting...");
            try {
                thread.join(); // 等待thread完成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Main thread: Resumed!");
        }
    }
    
  • 输出

    Thread: Working...
    Main thread: Waiting...
    (等待3秒)
    Thread: Done!
    Main thread: Resumed!
    

4. yield 方法

  • 作用:提示线程调度器当前线程愿意让出CPU时间,但不一定会立即切换到其他线程。

  • 关键点

    • 调用yield后,线程可能再次被调度。
    • 用于降低线程优先级,提高CPU资源利用率。
  • 示例代码

    public class YieldExample {
        public static void main(String[] args) {
            Runnable task = () -> {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                    Thread.yield(); // 提示让出CPU
                }
            };
    
            Thread thread1 = new Thread(task, "Thread1");
            Thread thread2 = new Thread(task, "Thread2");
    
            thread1.start();
            thread2.start();
        }
    }
    
  • 输出

    Thread1: 0
    Thread2: 0
    Thread1: 1
    Thread2: 1
    ...
    

5. Blocked状态

  • 当线程试图进入一个已被其他线程锁住的同步代码块或方法时,线程进入**Blocked(阻塞)**状态。
  • 示例:
    class BlockedExample {
        private static final Object lock = new Object();
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("Thread1: Holding lock...");
                    try {
                        Thread.sleep(5000); // 模拟任务
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread1: Released lock.");
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("Thread2: Acquired lock.");
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }
    
  • 输出
    Thread1: Holding lock...
    (Thread2进入Blocked状态,等待lock释放)
    Thread1: Released lock.
    Thread2: Acquired lock.
    

4. 使用jstack监控线程状态

1. jstack简介

  • jstack 是 JDK 提供的一个命令行工具,用于生成 Java 虚拟机当前线程的堆栈快照(Thread Dump)。
  • 主要功能
    • 查看每个线程的状态(如 RUNNABLEBLOCKEDWAITING 等)。
    • 调试死锁问题。
    • 诊断线程长时间运行或无响应的问题。

2. 常见线程状态

  • NEW:线程刚创建,尚未调用start
  • RUNNABLE:线程运行中,或正在等待 CPU 时间片。
  • BLOCKED:线程在等待进入同步代码块,锁未释放。
  • WAITING:线程调用wait()join(),等待被唤醒。
  • TIMED_WAITING:线程调用sleep()或带时间参数的wait()join()
  • TERMINATED:线程运行结束。

3. 使用jstack的基本命令

  • 获取目标 JVM 进程的线程堆栈:
    jstack <pid>
    
    • <pid> 是目标进程的 ID。可以通过jps命令获取。
  • 将线程堆栈保存到文件:
    jstack <pid> > thread_dump.txt
    

4. 示例:分析死锁问题

  • 问题场景:两个线程互相等待对方的锁,导致死锁。

  • 示例代码:

    public class DeadlockExample {
        private static final Object lock1 = new Object();
        private static final Object lock2 = new Object();
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(() -> {
                synchronized (lock1) {
                    System.out.println("Thread1: Holding lock1...");
                    try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
                    System.out.println("Thread1: Waiting for lock2...");
                    synchronized (lock2) {
                        System.out.println("Thread1: Acquired lock2.");
                    }
                }
            });
    
            Thread thread2 = new Thread(() -> {
                synchronized (lock2) {
                    System.out.println("Thread2: Holding lock2...");
                    try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
                    System.out.println("Thread2: Waiting for lock1...");
                    synchronized (lock1) {
                        System.out.println("Thread2: Acquired lock1.");
                    }
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }
    
  • 运行后,使用jstack查看线程状态

    jstack <pid>
    
  • 线程堆栈示例输出

    Found one Java-level deadlock:
    =============================
    "Thread-0":
        waiting to lock <0x000000071860b408> (a java.lang.Object),
        which is held by "Thread-1"
    "Thread-1":
        waiting to lock <0x000000071860b3d8> (a java.lang.Object),
        which is held by "Thread-0"
    

5. 解决方案

  • 使用jstack定位问题后,可以在代码中:
    • 避免嵌套锁:减少锁的使用范围。
    • 按固定顺序加锁:确保锁获取顺序一致。
    • 尝试tryLock:使用ReentrantLocktryLock方法,避免死锁。

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;