java多线程第一讲(基础知识)

不懂多线程就等同于不懂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阶段。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值