01. 线程的创建方式
创建线程有三种方式:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口(有返回值)。推荐用 Runnable 方式,因为 Java 单继承,Thread 方式会浪费唯一一次继承机会。Callable 可以返回结果和抛异常,配合 Future 使用。
java
// 方式1: 实现 Runnable(推荐)
Thread t1 = new Thread(() -> {
System.out.println("线程运行中");
});
t1.start();
// 方式2: Callable + Future
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "结果";
});
String result = future.get(); // 阻塞等待结果02. 线程的生命周期
线程有六个状态:NEW(创建了但没 start)、RUNNABLE(运行中或就绪)、BLOCKED(等锁)、WAITING(无限等待如 wait)、TIMED_WAITING(限时等待如 sleep)、TERMINATED(执行完毕)。start 从 NEW 到 RUNNABLE,sleep 进入 TIMED_WAITING,synchronized 拿不到锁进入 BLOCKED。
java
Thread t = new Thread(() -> {
System.out.println("状态: " + Thread.currentThread().getState());
});
System.out.println(t.getState()); // NEW
t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TERMINATED
t.join(); // 等待线程执行完毕
Thread.sleep(1000); // 当前线程睡1秒join 方法让当前线程等待调用 join 的那个线程执行完毕。
03. synchronized 同步锁
多个线程同时改同一个数据会出问题,synchronized 保证同一时刻只有一个线程执行同步代码块。可以修饰方法(锁的是 this 对象)或代码块(锁指定对象)。静态方法上的 synchronized 锁的是 Class 对象。锁的范围越小越好,只锁关键代码,不要锁整个方法。
java
public class Counter {
private int count = 0;
public synchronized void increment() { count++; }
// 等价于
public void increment2() {
synchronized (this) {
count++;
}
}
// 静态方法锁 Class 对象
public static synchronized void staticMethod() { }
}synchronized 是可重入锁,同一个线程可以多次获取同一把锁不会死锁。
04. 线程池 ExecutorService
频繁创建销毁线程开销大,线程池就是预先创建一批线程等着干活,用完不销毁放回池子。Executors 工具类可以快速创建各种线程池:newFixedThreadPool 定长线程池、newCachedThreadPool 无限扩大的缓存池、newScheduledThreadPool 定时任务池。生产环境不推荐用 Executors,用 ThreadPoolExecutor 手动调参。
java
ExecutorService pool = Executors.newFixedThreadPool(4);
// 提交任务
pool.execute(() -> System.out.println("无返回值的任务"));
Future<Integer> future = pool.submit(() -> { return 42; });
// 关闭线程池
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
// 推荐的生产环境写法
new ThreadPoolExecutor(4, 8, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100));生产环境不要用无界队列,请求多时会 OOM。newFixedThreadPool 用 LinkedBlockingQueue 无界队列,很危险。
05. volatile 和原子类
volatile 保证多线程间变量的可见性,一个线程改了值另一个线程马上能看到,但不保证原子性。count++ 这种非原子操作 volatile 也救不了。如果想用无锁的方案做计数,用 AtomicInteger 之类的原子类,底层 CAS 实现,性能比 synchronized 好很多。
java
// volatile 只保证可见性
volatile boolean running = true;
// 原子类保证线程安全
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet(); // 原子的 count++
int value = atomicCount.get();
// CAS 原理简述
atomicCount.compareAndSet(5, 10); // 如果当前值=5,改为10简单计数场景用 AtomicInteger,不需要 synchronized,性能好很多。
知识测验
第 1/4 题正确 0
start() 和 run() 的区别?
下一节
下一节 数据库操作