• 线程基础知识


    线程基础知识

    1. 创建线程常用的三种方式

    1. 继承Trhead类
    
    /**
     * @since 2022/9/8 19:26
     * @author monk
     */
    class MyThread :Thread(){
        override fun run() {
            println("当前线程:${currentThread().toString()}")
        }
    }
    
    fun main() {
        val myThread = MyThread()
        myThread.start()
    
        println("当前线程:${Thread.currentThread().toString()}")
    }
    
    /**
    * 结果:
    * 当前线程:Thread[main,5,main]
    * 当前线程:Thread[Thread-0,5,main]
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    1. 实现Runnable接口
    class MyRunnable :Runnable{
        override fun run() {
            println("子线程:${Thread.currentThread()}")
        }
    }
    
    fun main() {
        val myRunnable = MyRunnable()
        Thread(myRunnable).start()
    
        println("主线程:${Thread.currentThread()}")
    }
    
    /**
    * 结果:
    * 主线程:Thread[main,5,main]
    * 子线程:Thread[Thread-0,5,main]
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 实现Callable接口
    class MyCallable<String> : Callable<kotlin.String> {
    
        override fun call(): kotlin.String {
            return "子线程:${Thread.currentThread()}"
        }
    }
    
    fun main() {
        val myCallable = MyCallable<String>()
        val singleThread = Executors.newSingleThreadExecutor()
        val submit = singleThread.submit(myCallable)
        println(try {
            submit.get()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            singleThread.shutdown()
        })
    
        println("主线程:${Thread.currentThread()}")
    }
    
    /**
    * 结果:
    * 子线程:Thread[pool-1-thread-1,5,main]
    * 主线程:Thread[main,5,main]
    */
    
    • 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

    2. 线程的生命周期

    参考《参考《深入理解Java虚拟机》Java与线程-状态转换》

    Java语言定义了5中进程状态,在任意一个时间点中,一个进程只能有且只有其中一种状态,这5中状态分别是:

    1. 新建(New):创建后尚未启动的线程处于这种状态

    2. 运行(Runable):包括操作系统线程状态中的Running 和 Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间

    3. 无限期等待(Waiting):处于这种状态的进程不会被分配CPU执行时间,它们要等待被其它线程显示地唤醒,以下方法会让线程陷入无限期等待状态:

      • 没有设置Timeout参数的 wait() 方法
      • 没有设置Timeout参数的 join()方法
      • LockSupport.park()方法
    4. 限期等待(Timed Waitting):处于这种状态的进程也不会被分配CPU执行时间,不过无须等待被其他线程显示地唤醒,在一定时间之后它们会由系统自动唤醒

      • Thread.sleep()方法。
      • 设置了Timeout参数的Object…wait0方法。
      • 设置了Timeout参数的Thread,.join()方法。
      • LockSupport.parkNanos()方法。
      • LockSupport…parkUntilO)方法。
    5. 阻塞(Blocked):进程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待着获取到一个排它锁,这个事件将在另外一个线程放弃这个锁的时候发生:而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

    6. 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

    img

    3. 线程的中断方式

    1. 布尔值标记的运用,以往有的时候会使用stop()方法来停止线程,但现在的JDK早就废除了stop()方法,不建议使用,现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止
    class MyRunnable : Runnable {
        var flag = true
    
        override fun run() {
            var i = 0
            while (flag) {
                Thread.sleep(500)
                println("i:${i++}")
            }
             println("终止了")
        }
    
        fun close() {
            flag = false
        }
    }
    
    
    fun main() {
        val myRunnable = MyRunnable()
        Thread(myRunnable).start()
        Thread.sleep(5000)
        myRunnable.close()
    }
    /*
    i:0
    i:1
    i:2
    i:3
    i:4
    i:5
    i:6
    i:7
    i:8
    i:9
    终止了
    */
    
    • 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
    1. 另外一个就是interrupt()方法,如果线程使用sleep()或wait()方法进入了就绪状态,那么可以使用interrupt()方法是线程离开run()方法,同时结束线程,但会抛InterruptedException异常,用户可以在处理异常时完成线程的中断业务处理,如终止while循环
    class MyRunnable : Runnable {
    
        var flag = true
        override fun run() {
            var i = 0
            while (flag) {
                try {
                    Thread.sleep(500)
                } catch (e: Exception) {
                    flag = false
                }
                println("i:${i++}")
            }
            println("终止了")
        }
    }
    
    fun main() {
        val myRunnable = MyRunnable()
        val thread = Thread(myRunnable)
        thread.start()
        Thread.sleep(5000)
        thread.interrupt()
    }
    /*
    i:0
    i:1
    i:2
    i:3
    i:4
    i:5
    i:6
    i:7
    i:8
    i:9
    终止了
    */
    
    • 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

    4. wait/notify

    在 Java 中,使用 Object.wait()/Object.wait(long) 和 Object.notify()/Object.notifyAll() 可以用于实现等待和通知。用法省略。

    原理:
    JVM 会给每个对象维护一个入口集(Entry Set)和等待集(Wait Set)。

    入口集用于存储申请该对象内部锁的线程,等待集用于存储对象上的等待线程。

    wait() 方法会将当前线程暂停,在释放内部锁时,会将当前线程存入该方法所属的对象等待集中。

    调用对象的 notify() 方法,会让该对象的等待集中的任意一个线程唤醒,被唤醒的线程会继续留在对象的等待集中,直到该线程再次持有对应的内部锁时,wait() 方法就会把当前线程从对象的等待集中移除。

    添加当前线程到等待集、暂停当前线程、释放锁以及把唤醒后的线程从对象的等待集中移除,都是在 wait() 方法中实现的。

    在 wait() 方法的 native 代码中,会判断线程是否持有当前对象的内部锁,如果没有的话,就会报非法监视器状态异常,这也就是为什么要在同步代码块中执行 wait() 方法。

    5. await/signal

    wait()/notify() 过于底层,而且还存在两个问题,一是过早唤醒、二是无法区分 Object.wait(ms) 返回是由于等待超时还是被通知线程唤醒。

    使用 await/signal 协作方式有下面几个要点。

    • Condition 接口
      在 JDK 5 中引入了 Condition(条件变量) 接口,使用 Condition 也可以实现等待/通知,而且不存在上面提到的两个问题。
      Condition 接口提供的 await()/signal()/signalAll() 相当于是 Object 提供的 wait()/notify()/notifyAll()。
      通过 Lock.newCondition() 可以获得一个 Condition 实例。
    • 持有锁
      与 wait/notify 类似,wait/notify 需要线程持有所属对象的内部锁,而 await/signal 要求线程持有 Condition 实例的显式锁。
    • 等待队列
      Condition 实例也叫条件变量或条件队列,每个 Condition 实例内部都维护了一个用于存储等待线程的等待队列,相当于是 Object 中的等待集。
    • 循环语句
      对于保护条件的判断和 await() 方法的调用,要放在循环语句中
    • 引导区内
      循环语句和执行目标动作要放在同一个显式锁引导的临界区中,这么做是为了避免欺骗性唤醒和信号丢失的问题

    基本用法:

    class AwaitSignal  {
        private val lock: Lock = ReentrantLock()
        private val condition: Condition = lock.newCondition()
    
        @Volatile
        private var conditionSatisfied = false
    
        fun startWait() {
            lock.lock()
            println("等待线程获取了锁")
            try {
                while (!conditionSatisfied) {
                    println("保护条件不成立,等待线程进入等待状态")
                    condition.await()
                }
                println("等待线程被唤醒,开始执行目标动作")
            } catch (e: InterruptedException) {
                e.printStackTrace()
            } finally {
                lock.unlock()
                println("等待线程释放了锁")
            }
        }
    
        fun startNotify() {
            lock.lock()
            println("通知线程获取了锁")
            try {
                conditionSatisfied = true
                println("通知线程即将唤醒等待线程")
                condition.signal()
            } finally {
                println("通知线程释放了锁")
                lock.unlock()
            }
        }
    }
    
    fun main() {
        val awaitSignal = AwaitSignal()
        thread {
            awaitSignal.startWait()
        }
    
        thread {
            awaitSignal.startNotify()
        }
    }
    /*
    等待线程获取了锁
    保护条件不成立,等待线程进入等待状态
    通知线程获取了锁
    通知线程即将唤醒等待线程
    通知线程释放了锁
    等待线程被唤醒,开始执行目标动作
    等待线程释放了锁
    */
    
    • 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

    6. LockSupport

    LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法,主要有两类方法:parkunpark,另外线程中断的方法也能解除park阻塞状态

    class LockSupportS  :Runnable{
        var u = Any()
    
        override fun run() {
            synchronized(u) {
                println("in ${Thread.currentThread().name}")
                LockSupport.park()
                if (Thread.currentThread().isInterrupted) {
                    println("被中断了")
                }
                println("继续执行")
            }
        }
    }
    
    fun main() {
        val lockSupport = LockSupportS()
        val t1 = Thread(lockSupport, "t1")
        t1.start()
        Thread.sleep(1000)
        val t2 = Thread(lockSupport, "t2")
        t2.start()
        Thread.sleep(1000)
        // t1.interrupt()
        LockSupport.unpark(t1)
        LockSupport.unpark(t2)
    }
    /*
    in t1
    继续执行
    in t2
    继续执行
    
    */
    
    • 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
  • 相关阅读:
    python实现提取文件名某个字符串并新建文件夹保存,判断两个矩形是否相交或重合
    huawei 华为 交换机 配置 Dot1q 终结子接口实现跨设备 VLAN 间通信示例
    基于python下django框架 实现旅游景区景点售票系统详细设计
    PyTorch与向量化计算
    基于javaweb的技能交换分享网站系统-计算机毕业设计
    为什么在高速PCB设计当中信号线不能多次换孔
    K8s部署
    腾讯高工用 4 部分就讲清楚了 Spring 全家桶 + 微服务
    分享股票下单API接口的方式和API攻略
    构建嵌入式Linux操作系统 Linux操作系统的介绍
  • 原文地址:https://blog.csdn.net/qq_37776700/article/details/126770587