一个最简单的HelloWorld程序,背后也有多个线程。
Java代码
-
public static void main(String[] args) {
-
System.out.println("Hello World");
-
-
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
-
long[] threadIds = threadMXBean.getAllThreadIds();
-
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds);
-
for (ThreadInfo threadInfo : threadInfos) {
-
System.out.println(threadInfo.getThreadId() + ": " + threadInfo.getThreadName());
-
}
-
}
输出:
Hello World
5: Attach Listener
4: Signal Dispatcher
3: Finalizer
2: Reference Handler
1: main
java线程的本质
Java 线程 一对一 映射 操作系统内核线程。
java.lang.Thread 中有很多关键的方法是本地方法(Native):
Java代码
-
public class Thread {
-
private native void start0();
-
private native void interrupt0();
-
private native void setPriority0();
-
...
-
}
与内核线程一对一的机制的
优点:提供了 精细强大的线程管理能力。
缺点:其高复杂性使得 并发编程非常困难。
未来Java也会提供类似 Python协程 的轻量级用户线程(Fiber)(Loom)。
Python协程机制可以让一个线程执行多个协程。与多线程模式相比,它 避免了线程切换的开销,甚至 避免线程间的锁机制。
还可用 “多进程 + 协程” 的模式充分利用多核CPU。
Python 生成者-消费者 协程示例:(生产一个,消费一个;单个线程轮换执行生产和消费的过程)
Python代码
-
def consumer():
-
r = ''
-
while True:
-
n = yield r
-
if not n:
-
return
-
print('[消费者] 正在消费 %s...' % n)
-
r = '200-OK'
-
-
def produce(c):
-
c.send(None)
-
n = 0
-
while n < 5:
-
n = n+1
-
print('[生产者] 正在生产 %s...' % n)
-
r = c.send(n)
-
print('[生产者] 消费者返回:%s' % r)
-
c.close()
-
-
c = consumer()
-
produce(c)
线程状态与方法图
Thread 和 Object 中的 wait、notify 等方法过于晦涩难用。
推荐使用java并发包中的工具来达到同步控制的目的。如,CountDownLatch、CyclicBarrier、Semaphore
创建线程
直接创建 Thread 实例
一般可以通过 Runnable 简单直接的创建线程:
Java代码
-
Runnable task = () -> System.out.println("Hello World");
-
Thread thread = new Thread(task);
-
thread.start();
-
thread.join();
使用线程池
一般推荐使用线程池来处理稍复杂的多线程场景,以减少创建和销毁线程的开销(包括内存和CPU),降低大量线程“过度切换”的风险。
推荐手动创建线程池,以明确指定运行规则,规避资源耗尽的风险。
-
Executors.newFixedThreadPool 和 newSingleThreadExecutor 可能会耗费非常大的内存,甚至OOM
-
Executors.newCachedThreadPool 和 newScheduleThreadPool允许最大线程数是 Integer.MAX_VALUE。这可能会导致创建非常的线程,甚至OOM
线程池(ExecutorService)创建示例:
Java代码
-
// org.apache.commons.lang3.concurrent.BasicThreadFactory
-
ThreadFactory threadFactory = new BasicThreadFactory.Builder()
-
.namingPattern("example-schedule-pool-%d")
-
.daemon(true)
-
.build();
-
-
ScheduledExecutorService executorService
-
= new ScheduledThreadPoolExecutor(1, threadFactory);
Java代码
-
ThreadFactory threadFactory = new ThreadFactoryBuilder()
-
.setNameFormat("demo-pool-%d")
-
.build();
-
-
ExecutorService executorService = new ThreadPoolExecutor(
-
5, 200,
-
0L, TimeUnit.MILLISECONDS,
-
new LinkedBlockingQueue<Runnable>(1024),
-
threadFactory,
-
new ThreadPoolExecutor.AbortPolicy());
守护线程
如果需要一个长期驻留的线程,但是不希望它影响应用的退出,那么可将该线程设置为 守护线程。
JVM 发现只有守护线程存在时,会结束进程。
具体方法:在启动线程前,调用 Thread.setDaemon(true) 方法:
Java代码
-
Thread daemonThread = new Thread();
-
daemonThread.setDaemon(true);
-
daemonThread.start();
Thread.onSpinWait()
该方法的意思就是“自旋等待”。即,线程在等待期间一直占用着CPU。
这是一直性能优化的技术,用于避免线程切换的开销。
但 Thread.onSpinWait() 方法只是对 JVM 的一个暗示,并没有任何行为上的保障。
JVM 可能会利用 CPU 的 pause 指令。
ThreadLocal
谨慎使用 ThreadLocal ! 注意预防 OOM。
ThreadLocal 提供了一种保存 线程私有 信息的机制。
ThreadLocal 保存的信息在线程的整个生命周期内都有效。
所以可以通过 ThreadLocal 在一个线程关联的 不同业务模块 之间传递信息,如 事务ID、Cookie 等上下文信息。
ThreadLocalMap
ThreadLocal 以 弱引用 的形式被保存。
每个线程(Thread)内部都有一个 ThreadLocalMap 字段来存储属于线程自己的 ThreadLocal 对象。
ThreadLocalMap 内部有一个数组保存这些信息;
数组中的每个元素(Entry),也就是 键值对,都是 弱引用形式的ThreadLocal对象:
Java代码
-
class Entry extends WeakReference<ThreadLocal<?>> {
-
Object value;
-
Entry(ThreadLocal<?> k, Object v) {
-
super(k);
-
value = v;
-
}
-
}
通常,弱引用 和 引用队列(ReferenceQueue) 配合垃圾回收机制使用:
创建弱引用对象的同时,将对象注册到一个引用队列中;
当对象的 可达性 成为弱引用时,对象会被添加到该引用队列,用于后续的自定义操作。
Java代码
-
public WeakReference(T referent, ReferenceQueue<? super T> q);
但是 ThreadLocal 并没有结合 ReferenceQueue 使用。
也就是说,ThreadLocalMap 中 废弃项的回收依赖于显式的触发;
否则就要等到线程结束,ThreadLocalMap 被回收后,废弃项才会被回收。
ThreadLocalMap 中的几个关键方法,set、remove、rehash,会触发废弃项的回收。
所以在使用ThreadLocal时,为了预防OOM:
-
应用须自己负责删除废弃项:ThreadLocal.remove()
-
不要和线程池配合使用。因为 worker 线程往往不会退出