`

【Java核心-进阶】线程

    博客分类:
  • Java
 
阅读更多

一个最简单的HelloWorld程序,背后也有多个线程。

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):

public class Thread {
  private native void start0();
  private native void interrupt0();
  private native void setPriority0();
  ...
}

 

与内核线程一对一的机制的
优点:提供了 精细强大的线程管理能力
缺点:其高复杂性使得 并发编程非常困难

 

未来Java也会提供类似 Python协程 的轻量级用户线程(Fiber)(Loom)。

Python协程机制可以让一个线程执行多个协程。与多线程模式相比,它 避免了线程切换的开销,甚至 避免线程间的锁机制
还可用 “多进程 + 协程” 的模式充分利用多核CPU。

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 简单直接的创建线程:

Runnable task = () -> System.out.println("Hello World");
Thread thread = new Thread(task);
thread.start();
thread.join();

 

使用线程池

一般推荐使用线程池来处理稍复杂的多线程场景,以减少创建和销毁线程的开销(包括内存和CPU)降低大量线程“过度切换”的风险
推荐手动创建线程池,以明确指定运行规则,规避资源耗尽的风险。

  • Executors.newFixedThreadPoolnewSingleThreadExecutor 可能会耗费非常大的内存,甚至OOM
  • Executors.newCachedThreadPoolnewScheduleThreadPool允许最大线程数是 Integer.MAX_VALUE。这可能会导致创建非常的线程,甚至OOM

线程池(ExecutorService)创建示例:

// 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);
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) 方法:

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对象:

class Entry extends WeakReference<ThreadLocal<?>> {
  Object value;
  Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
  }
}

 

通常,弱引用 和 引用队列(ReferenceQueue) 配合垃圾回收机制使用:

创建弱引用对象的同时,将对象注册到一个引用队列中;
当对象的 可达性 成为弱引用时,对象会被添加到该引用队列,用于后续的自定义操作。

public WeakReference(T referent, ReferenceQueue<? super T> q);

 

但是 ThreadLocal 并没有结合 ReferenceQueue 使用。
也就是说,ThreadLocalMap 中 废弃项的回收依赖于显式的触发
否则就要等到线程结束,ThreadLocalMap 被回收后,废弃项才会被回收。

ThreadLocalMap 中的几个关键方法,set、remove、rehash,会触发废弃项的回收。

所以在使用ThreadLocal时,为了预防OOM:

  • 应用须自己负责删除废弃项:ThreadLocal.remove()
  • 不要和线程池配合使用。因为 worker 线程往往不会退出

 

  • 大小: 49.4 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics