不懂多线程就等同于不懂java。强烈建议看下序章。https://blog.csdn.net/define_us/article/details/84860541
JVM线程分类
jvm线程分为守护线程和用户线程。守护线程的定位是为用户线程提供服务。当所有用户线程结束后,jvm随机退出。你可以调用setDeamon将线程指定为守护线程。
JVM启动时第一个进行的用户线程是主线程。一般认为主线程应该最后一个结束。
JVM中的线程状态
- NEW 新建的线程对象。创建后尚没有启动的线程处于这种状态。
- RUNNABLE (包含了操作系统线程状态的RUNNING和READY)可运行,获取了一切线程运行的条件,正在等待被调度,或者正在运行。
- WIATING 无限期等待。必须被其他线程唤醒。没有设置timeout的Object.wait()和Thread.join(),LockSupport.park()会让线程陷入此状态
一个典型的堆栈如下
flink kafka connector 代码 一个典型的生产者消费者模式
at java.lang.Object.wait(Native Method)
- waiting on java.lang.Object@7eaa2be4
at java.lang.Object.wait(Object.java:502)
at org.apache.flink.streaming.connectors.kafka.internal.Handover.pollNext(Handover.java:53)
at org.apache.flink.streaming.connectors.kafka.internal.Kafka010Fetcher.runFetchLoop(Kafka010Fetcher.java:164)
at org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase.runWithPartitionDiscovery(FlinkKafkaConsumerBase.java:798)
at org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumerBase.run(FlinkKafkaConsumerBase.java:790)
at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:110)
at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:66)
实际对应代码如下
public ConsumerRecords<byte[], byte[]> pollNext() throws Exception {
synchronized (lock) {
while (next == null && error == null) {
lock.wait();
}
...
- TIMED WIATING 有限期等待,设置timeout的Object.wait()和Thread.join(),LockSupport.parkNanos(),LockSupport.parkUtil()
Thread state for a waiting thread with a specified waiting time. A thread is in the timed waiting state due to calling one of the following methods with a specified positive waiting time:
Thread.sleep
Object.wait with timeout
Thread.join with timeout
LockSupport.parkNanos
LockSupport.parkUntil
比如一个常见的线程堆栈(ScheduledThreadPoolExecutor的底层)
"jedis-renew-thread-thread-1" Id=979 TIMED_WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@752ca0eb
at sun.misc.Unsafe.park(Native Method)
- waiting on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject@752ca0eb
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
...
对应的代码就是
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
可以看到线程被park了。只能等待别人unpark它或者超时或者自己被中断。AbstractQueuedSynchronizer$ConditionObject 这个记录这个现成为什么被park。(理论上你可以传任意对象,但是最好遵循这个约定方便排查问题)
@HotSpotIntrinsicCandidate
public native void unpark(Object thread);
- BLOCKED 阻塞状态。被排他锁阻塞。
- DEAD 死亡 死亡的线程没有重生的机会。
线程的中断
- 如果一个线程处于了BLOCKED,TIMED WIATING, WIATING(如线程调用了LockSupport.park(),thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
});
thread1.start();
thread1.interrupt();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().interrupt();
try{
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
});
thread1.start();
上述两种代码将打印出InterruptedException。
注意,并不是所有导致程序进入这三种状态的方法都有响应InterruptedException的能力。比如说下面这个epoll中用到的API。就并不支持抛出InterruptedException
private native int kevent0(int var1, long var2, int var4, long var5);
- 对于NEW和TERMINATED状态的线程,中断对他们毫无意义。
- 对于RUNNABLE状态的线程当我们调用t.interrput()的时候,线程t的中断状态(interrupted status) 会被置位。我们可以通过Thread.currentThread().isInterrupted() 来检查这个布尔型的中断状态。也就是说,如果你不手动检查,则中断对他们毫无意义。
JVM多线程框架
原始
Thread类的相关使用
Executor
JAVA中锁的分类
公平锁,非公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。
-
可重入锁,不可重入锁
可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
在JAVA环境下 ReentrantLock 和synchronized 都是可重入锁 -
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。如ReentrantLock,ReadWriteLock的写锁。
共享锁是指该锁可被多个线程所持有。如ReadWriteLock中的读锁。 -
自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁 -
无状态锁,偏向锁,轻量级锁和重量级锁状态
java 6中引入的synchronized锁的四种状态。
多线程的底层实现
JAVA的多线程实际上对应着操作系统的多线程(1:1模型)。对于linux这种伪多线程操作系统而言,线程就是操作系统中的进程。
JVM支持的最大线程数为
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory 指的是一个进程的最大内存
JVMMemory JVM内存
ReservedOsMemory 保留的操作系统内存
ThreadStackSize 线程栈的大小
在java语言里, 当你创建一个线程的时候,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存(MaxProcessMemory - JVMMemory - ReservedOsMemory)。
对于这种模型,单台服务器往往有着100万的线程的上限。对于实现百万并发以上的场景,可以使用协程(据说腾讯微信使用C++的协程技术实现了千万级别的并发)
如何阅读堆栈
"redisson-netty-1-1" #12 prio=5 os_prio=31 tid=0x00007fb92e9bd800 nid=0x4f03 runnable [0x000070000b676000]//线程名是重要的提示
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.KQueueArrayWrapper.kevent0(Native Method)
at sun.nio.ch.KQueueArrayWrapper.poll(KQueueArrayWrapper.java:198)
at sun.nio.ch.KQueueSelectorImpl.doSelect(KQueueSelectorImpl.java:117)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000006c000b1a0> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x00000006c000b1b8> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000006c000b150> (a sun.nio.ch.KQueueSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:752)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:408)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)//知道该了属于一个SingleThreadEventExecutor
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)##知道该线程属于netty,由DefaultThreadFactory创建
at java.lang.Thread.run(Thread.java:748)##最后一行一定是Thread.java
nio对应操作系统的线程编号。以16进制数字显示。
我们来看下几种典型的线程
被park的线程
"Thread-1" #29 daemon prio=5 os_prio=31 tid=0x00007f903501f800 nid=0x9603 waiting on condition [0x0000700007555000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c0d38248> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
线程状态TIMED_WAITING,是因为调用了LockSupport.parkNanos。而0x00000006c0d38248地址的对象是调用UNSAFE.park(false, nanos) 前所调用UNSAFE.putObject(t, parkBlockerOffset, arg) 所set的blocker。这个broker的作用只有调试和定位问题。并没有实际意义。同理waiting on condition [0x0000700007555000]这个值是底层linux的pthread_cond_wait命令参数,在java中并没有实际意义。 https://linux.die.net/man/3/pthread_cond_wait
sleep的线程
"JVM INFO" #60 daemon prio=5 os_prio=31 tid=0x00007f9036e76000 nid=0x8503 waiting on condition [0x000070000888e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.alipay.sofa.runtime.ce.integration.monitor.JvmMonitor$1.run(JvmMonitor.java:54)
Locked ownable synchronizers:
- None
wait的线程
"com.google.inject.internal.util.$Finalizer" #79 daemon prio=5 os_prio=31 tid=0x00007f9036fac000 nid=0xa50b in Object.wait() [0x0000700006b37000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000782d80770> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x0000000782d80770> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at com.google.inject.internal.util.$Finalizer.run(Finalizer.java:114)
这种就是对用的经典的先锁后等的代码
synchronized (lock) {
Reference<? extends T> r = reallyPoll();
if (r != null) return r;
long start = (timeout == 0) ? 0 : System.nanoTime();
for (;;) {
lock.wait(timeout);
r = reallyPoll();
if (r != null) return r;
if (timeout != 0) {
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
start = end;
}
}
}
注意堆栈虽然是lock了0x0000000782d80770这个对象。但是wait后实际上该对象已经被释放了。所以完全有可能多个线程locked了同一个对象。
抢锁失败的线程
"t2" #14 prio=5 os_prio=31 tid=0x00007fe3cf0a6800 nid=0xa603 waiting for monitor entry [0x0000700006c92000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.ZhimaCertVerifyDaoImpl$2.run(ZhimaCertVerifyDaoImpl.java:144)
- waiting to lock <0x000000076b17cf98> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
"t1" #13 prio=5 os_prio=31 tid=0x00007fe3cc416800 nid=0xa803 waiting on condition [0x0000700006b8f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.ZhimaCertVerifyDaoImpl$1.run(ZhimaCertVerifyDaoImpl.java:127)
- locked <0x000000076b17cf98> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
可以看到,上述t2线程无法lock对象。出现了block阶段。