单例模式
单例的核心目标是:确保一个类在整个应用中只有一个实例,并提供一个全局访问点。虽然概念简单,但不同场景下的实现方式差异很大,面试官往往通过这个问题考察你对线程安全、性能和类加载机制的掌握。
最基础的实现是饿汉式,在类加载时就创建实例,天然线程安全:
1 2 3 4 5 6 7 8 9
| public class Singleton { private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; } }
|
这种方式简单可靠,但缺点是无论是否使用都会创建对象,可能造成资源浪费。于是引出了懒汉式——延迟加载:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
这里通过synchronized保证线程安全,但每次调用getInstance()都要加锁,性能较差。为了优化,我们引入双重检查锁定(Double-Checked Locking):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Singleton { private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
注意这里的volatile关键字至关重要,它防止了指令重排序,确保多线程环境下实例的正确发布。这是目前推荐的懒加载线程安全实现。
还有一种优雅的方式是静态内部类:
1 2 3 4 5 6 7 8 9 10 11
| public class Singleton { private Singleton() {}
private static class Holder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return Holder.INSTANCE; } }
|
利用类加载机制保证线程安全,同时实现懒加载,代码简洁且高效,是实际开发中的优选方案。
交替打印ABC
这类题目的核心在于线程间的协调与通信,常用于考察对wait()、notify()、Lock、Condition或Semaphore等并发工具的理解。
我们以使用wait/notify为例,实现三个线程轮流打印A、B、C各10次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| public class PrintABC { private int flag = 1; private final Object lock = new Object();
public static void main(String[] args) { PrintABC printer = new PrintABC();
Thread t1 = new Thread(() -> { for (int i = 0; i < 10; i++) { printer.printA(); } }, "Thread-A");
Thread t2 = new Thread(() -> { for (int i = 0; i < 10; i++) { printer.printB(); } }, "Thread-B");
Thread t3 = new Thread(() -> { for (int i = 0; i < 10; i++) { printer.printC(); } }, "Thread-C");
t1.start(); t2.start(); t3.start(); }
public void printA() { synchronized (lock) { while (flag != 1) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.print("A"); flag = 2; lock.notifyAll(); } }
public void printB() { synchronized (lock) { while (flag != 2) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.print("B"); flag = 3; lock.notifyAll(); } }
public void printC() { synchronized (lock) { while (flag != 3) { try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.print("C"); flag = 1; lock.notifyAll(); } } }
|
输出结果将是:ABCABCABC...,共10组。关键点在于使用一个共享变量flag控制执行顺序,每个线程在不符合条件时wait(),符合条件时打印并更新flag,然后唤醒其他线程。注意必须使用while判断而非if,防止虚假唤醒。