闲碎记事本 闲碎记事本
首页
  • JAVA
  • Cloudflare
  • 学完再改一遍UI
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

YAN

我要偷偷记录...
首页
  • JAVA
  • Cloudflare
  • 学完再改一遍UI
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • java

    • SpringBoot

    • SpringSecurity

    • MybatisPlus

    • Netty

    • sip

    • 其他

      • MDC 使用
      • 位运算
      • RedisMQ实现
      • 自定义枚举序列化
      • Mybatis使用自定义枚举
      • Jackson反序列化泛型注意点
      • 敏感词过滤算法
      • 线程
        • 并发学习
        • jni使用
        • 关于注释
        • 为什么一个Byte用两个16进制表示
        • JAVA获取系统信息
        • 对extends和super的理解
        • JAVA系统API
        • java探针初探
        • JAVA获取USB信息
        • HashMap初探
        • JAVA远程调试
        • 初探webflux
        • SSE示例
    • linux

    • docker

    • redis

    • nginx

    • mysql

    • 其他

    • 环境搭建

    • 知识库
    • java
    • 其他
    YAN
    2023-07-17
    目录

    线程

    # 线程状态

    • New:新创建的线程,尚未执行;
    • Runnable:运行中的线程,正在执行run()方法的Java代码;
    • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
    • Waiting:运行中的线程,因为某些操作在等待中;
    • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
    • Terminated:线程已终止,因为run()方法执行完毕。
    示例

    join() 等待该线程结束,然后才继续往下执行自身线程

    @Test
    public  void  case1() throws Exception {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        System.out.println("start() 前,Thread T 状态:"+t.getState());
        t.start();
        System.out.println("start() 后,Thread T 线程状态:"+t.getState());
        //join()等待该线程结束,然后才继续往下执行自身线程
        t.join();
        System.out.println("join() 后,Thread T 状态:"+t.getState());
        System.out.println("程序结束");
    }
    

    log

    start() 前,Thread T 状态:NEW
    start() 后,Thread T 线程状态:RUNNABLE
    join() 后,Thread T 状态:TERMINATED
    

    如果上述示例不加 join, Thread Main 将不会等待 Thread t 执行,从而导致程序直接退出。

    # 中断线程

    interrupt(); 需要注意的是,他并不会直接中断线程,需要线程内部监听 isInterrupted() 返回值

    示例
    Thread t = new Thread(() -> {
        System.out.println("Thread t:"+Thread.currentThread().getState());;
        //监听是否中断
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("正在运行中。。。"+ false);
        }
    });
    
    t.start();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    //线程中断
    t.interrupt();
    try {
        //等待检查执行完毕
        t.join();
    } catch (InterruptedException e) {
        System.out.println("程序已经 interrupt()");
    }
    
    System.out.println("程序结束");
    

    # 守护线程

    java程序的所有线程都是由jvm创建的,也就是说,只要有任意一个jvm创建的线程没有退出,那么jvm就不会退出。

    所以线程需要有负责人对他进行关闭,但是一些线程,例如定时任务通常是无线循环,且无人它负责的关闭。

    这类线程需要随着系统的退出而关闭,对于这类线程通常会用到守护线程。

    需要注意的是:

    守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。

    示例
    Thread thread = new Thread(() -> {
        while (true){
        }
    });
    //设置守护线程
    thread.setDaemon(true);
    thread.start();
    
    System.out.println("程序结束"));
    

    # 线程同步

    多线程的模式下,对同一个模型进行操作,结果往往不尽如意,例如一以下示例:

    示例
    
    static int n = 0;
    
    
    public static void main(String[] args) {
    
        Thread a  = new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                 n = n++;
            }
        });
    
        Thread b = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                    n = n--;
            }
        });
    
        b.start();
        a.start();
        try {
            a.join();
            b.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(n);
    }
    

    上述程序中: A,B两个线程分别对n进行了1000次加,减操作。按照预期最终结果应该为:0。

    实际上并不是这样,n = n++; 这行代码实际上分为3个步骤

    1. ILOAD
    2. IADD
    3. ISTORE

    例如: n=10 ,n=n++; 对应的步骤是:

    1. ILOAD -> n=10
    2. IADD -> +1操作
    3. ISTORE -> n=11

    但是有这样一种场景: A线程对N进行+1操作, 在没有到 ISTORE 阶段时, B线程对N进行-1操作,ILOAD 得到的是为变更结果

    代入上面推断

    1. A -> 10 +1 -> 11
    2. B -> 10 -1 -> 9
    3. 最终结果:n=9

    所以上述程序有时候的得不到预期的结果:0

    为了解决上述问题,我们需要保证,在进行一次变更操作的时候,别的线程不能干扰它,也就是阻断别的线程。

    可通过 synchronized 关键字对一个对象进行加锁,保证了代码块在任意时刻最多只有一个线程能执行。

    改动代码如下

    示例
    static int n = 0;
    
    //锁对象
    static final Object LOCK = new Object();
    public static void main(String[] args) {
    
    
        Thread a  = new Thread(()->{
            for (int i = 0; i < 1000; i++) {
                synchronized (LOCK){
                    n = n++;
                }
            }
        });
    
        Thread b = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (LOCK){
                    n = n--;
                }
            }
        });
    
        b.start();
        a.start();
        try {
            a.join();
            b.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(n);
    }
    

    synchronized 保证了代码块中数据的原子性,同样JVM规范定义了几种原子操作 :

    • 基本类型赋值 (long和double除外)
      • 例如:int n = m
    • 引用类型赋值:
      • 例如:List<String> list = anotherList
    • 不可变对象无需同步

    同样使用 synchronized 需要注意粒度,对于一个锁对象,应该应用于同类事务。不同类事务的操作,应该定义与之对应的锁对象。

    例如:分别统计男女性别数量

    int boyCount=0;
    int girlCount=0
    static final Object LOCK = new Object();
    
    addBoy(){
      synchronized (LOCK){
        boyCount= boyCount++;
      }
    }
    
    addGirl(){
      synchronized (LOCK){
        girlCount= girlCount++;
      }
    }
    

    都是进行+1操作。如果同一把锁进行保证同步,那么将会损失一部分性能。因为这两个统计是可以分开进行的,这类场景就可以男女分别设置一把锁。

    int boyCount=0;
    int girlCount=0
    static final Object BOY_LOCK = new Object();
    static final Object GIRL_LOCK = new Object();
    
    void addBoy(){
      synchronized (BOY_LOCK){
        boyCount= boyCount++;
      }
    }
    
    void addGirl(){
      synchronized (GIRL_LOCK){
        girlCount= girlCount++;
      }
    }
    

    # 同步方法

    在使用synchronized对线程进行同步,使用synchronized的时候,锁住的是哪个对象非常重要。

    但是在方法内定于过多的锁对象,会使代码变得凌乱且不易于维护。

    对于大部分方法而言,我们想要的通常是:使用一个实例对象调用方法时,不被别的线程调用同一个实例对象所影响,因此我们需要锁住的其实是这个实例本身。

    锁住实例本身有两种方式,他们效果相同:

    int boyCount=0;
    
    void addBoy(){
      synchronized (this){
        boyCount= boyCount++;
      }
    }
    
    
    int boyCount=0;
    
    synchronized void addBoy(){
       (this){
        boyCount= boyCount++;
      }
    }
    
    

    因此,用 synchronized 修饰的方法就是同步方法,它表示整个方法都必须用this实例加锁。

    当调用的是静态方法时:

    public class Test {
        public synchronized void test(int n) {
        
        }
    }
    

    由于静态方法没有 this 对象,它锁住的是当前的Class实例,由jvm创建 ,因此静态方法的同步也可以这样写

    public class Test {
        public static void test(int n) {
            synchronized(Test.class) {
                
            }
        }
    }
    

    # 可重入锁

    synchronized 代码块所加的锁为可重入锁,也就是说,在获取到当前对象的锁后,可以继续获取同一个锁。

    如下

    public class Test {
        public synchronized void test(int n) { //得到锁
            test2(n);
        }//释放锁
        
        public synchronized void test2(int n) {//得到锁 
    
        }//释放锁
    }
    

    可重入锁在获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁。

    # 死锁

    死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待。

    由于 synchronized 为可重入锁,那么在如下程序

    public  class Test {
        static  final Object LOCK_1 = new Object();
        static  final Object LOCK_2 = new Object();
        
        public  void test1(int n) { //得到锁
            System.out.println("得到方法test1的锁");
            synchronized (LOCK_1){
                test2(n);
                synchronized (LOCK_2){
                    System.out.println("test1");
                }
            }
        }//释放锁
    
        public  void test2(int n) {//得到锁
            System.out.println("得到方法test1的锁");
            synchronized (LOCK_2){
                test1(n);
                synchronized (LOCK_1){
                    System.out.println("test2");
                }
            }
        }//释放锁
    
    }
    
    

    此时如果有两个线程A,B。

    A 调用了test1 拿到了锁 LOCK_1 ,准备获取LOCK_2。

    于此同时,B 调用了test2 拿到了锁 LOCK_2 ,准备获取LOCK_1。

    这时候 test2 无法获取 LOCK_1 ,test1 无法获取 LOCK_2 , 双方都在等待对方释放锁。导致形成死锁。

    所以多线程获取锁的顺序要一致,以避免出现死锁。

    上次更新: 2025/05/14, 01:34:05
    敏感词过滤算法
    并发学习

    ← 敏感词过滤算法 并发学习→

    最近更新
    01
    Caddy操作指南
    04-25
    02
    Swap空间
    04-22
    03
    Alist使用
    04-21
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025 YAN | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式