• JAVA线程和线程池


    线程的创建

    • 继承Thread类(Thread类其实也是实现的Runnable接口)
    // 自定义个线程类,继承thread类
    public class TestThread extends Thread{
    	
        public static void main(String[] args) {
    		// 实例化线程对象,调用start方法启动
            TestThread th =  new TestThread();
            th.start();
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程"+i);
            }
        }
    	// 重写run方法(IDEA中按住ALT+INSERT键,点击Override Method,快速生成run方法)
        @Override
        public void run() {
            for (int i = 0; i < 200; i++) {
                System.out.println("子线程"+i);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 实现Runnable接口(推荐使用Runnable)
    public class TestRunnable implements Runnable {
        
        public static void main(String[] args) {
            TestRunnable tr = new TestRunnable();
            new Thread(tr).start();
            
            for (int i = 0; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName()+"主线程"+i);
            }
        }
    
        public void run() {
            for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"子线程"+i);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • Callable(用的不多)

    线程状态

    • 创建:new Thread(),线程一旦创建的状态
    • 就绪:start()方法,进入就绪状态,等待cpu调度
    • 运行:cpu调度后执行
    • 阻塞:调用sleep、wait、同步锁定时,线程进入阻塞状态,代码不再往下执行,待阻塞解除后,重新进入就绪状态,等待cpu调度
    • 结束:线程正常执行或中断

    线程相关常用方法

    • setPriority 设置优先级
    • sleep 设置睡眠时间
    • join 线程插队
      • 线程A在执行的过程中,如果执行join(B),那么A线程暂时阻塞,待线程B执行完成后再执行A
    • yield 礼让
      • 让正在执行的线程暂停,但是不阻塞,并将线程由运行状态转化为就绪状态。
      • 在线程A执行的过程中,执行yield,可以礼让其他线程同时执行,但礼让成不成功还需要CPU调度,即执行礼让并不一定会礼让成功。
    • interrupt 中段
    • isAlive 检测
    • 锁池和等待池
    • sleep和wait:
      • sleep是Thread提供的方法,wait是object提供的方法。
      • sleep会cpu的执行资格和权限释放,不再运行线程,定时时间结束后再取回CPU资源,参与cpu调度,如果sleep时该线程有锁,那么sleep期间不会释放锁,而是把锁带到冻结状态,其他线程不可能获取到这个锁。
      • sleep不依赖同步器Synchronized,但是wait依赖Synchronized
      • sleep不需要被唤醒,是当前线程的休眠,或者轮询暂停操作,wait需要唤醒,是多线程之间的通讯。
    • yield和join:
      • yield执行后线程进入就绪状态(不是阻塞状态),马上释放了cpu的执行权,但是依然保留cpu执行资格,所以cpu下次还有可能调度这个线程进行执行。也就是说线程礼让并不一定会成功,还要看cpu怎么调度。
      • join会把线程置入阻塞状态,比如在A线程中调用B线程的join,那么A线程会进入阻塞队列,直到B执行结束后,再执行A。

    ThreadLocal

    • 每个Thread对象中,都有ThreadLocalMap类型的变量ThreadLocals,用来存储本线程中所有ThreadLocal对象和对应的值。
    • 应用场景:
      • 进行线程数据隔离
      • 事务操作,存储线程中的事务信息
      • 数据库连接,session管理等。

    ThreadLocal内存泄漏的原因

    • 内存泄漏:不会被使用的对象或者变量不能被回收而占据资源。过多的内存泄漏会造成OOM。
    • 强引用和弱引用

    线程优先级

    • setPriority设置优先级
    • getPriority查询优先级
    • priority在1和10之间取值,整数
    • priority越大并不代表越先执行,而是执行的优先权重更高,即优先权高的先执行的概率更高

    守护线程

    • 线程分为守护线程和用户线程,守护线程并不是某一个线程的守护线程,而是为jvm中所有非守护线程的提供服务的线程。
    • 虚拟机必须确保用户线程执行完毕,而不用保证守护线程执行完毕
    • thread.setDaemon(true) // 设置守护线程
    • 如果不设置,默认都是用户线程
    • 守护线程多用在内存回收(GC线程)、操作日志、内存监控等,守护线程是依赖整个进程而运行,其他线程都结束了,程序结束了,守护线程也就结束了。
    • 所以守护线程的终止是自身无法控制的,不要把复杂重要的逻辑写在守护线程中,例如IO、文件操作等。
    • Java自带的多线程框架会把守护线程转换为用户线程,比如ExecutorService,所以如果想用守护线程,就不能用java的自带的多线程框架。
    • 应用场景:
      • 用来为其他线程提供服务支持的情况。
      • 在任何情况下,程序结束时,这个线程必须正常立刻关闭。

    线程安全问题

    • 什么是线程安全:线程不安全的本质是每个线程会把变量信息读取到自己的工作内存中,容易造成数据不一致的现象:
      1. 抢票负数问题,容易出现-1张票,最后一张票,如果各个线程拿到最后一张票的信息,扣减后很容易出现-1。
      2. 取钱重复问题,例如余额10元,两个人同时取钱,如果线程不安全,很容易出现两个人都取出了10元。
      3. 数组覆盖问题,同时操作同一个数组(集合),如果多个线程同时操作同一下标的数据,很容易出现覆盖问题。
    • 怎么实现线程安全:增加sychronized修饰,如果修饰加在方法上,那么锁的对象是方法所在的类this,可能会无法实现同步锁,这时就需要使用同步块synchronized(object)写法
      // 可以直接加载方法上,此时锁的对象是方法指向的this
      public synchronized void xxx(){
      }
      // 也可以写同步块
      synchronized(object){
      	xxx
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 实现线程安全的原理:队列+锁的机制

    volatile关键字

    • 被volatile关键字修饰的变量,会被多个线程共享,保证对所有线程的“可见性”,即任何一个线程更改了变量后,其他线程都可以获取最新的数据
    • 被volatile修饰的变量,不会被缓存在寄存器、线程缓存或者其他线程无法访问到的地方,而是会放在各个线程都可以访问的公共区域,例如内存。
    • 当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
    • volitile无法保证操作的原子性,例如多线程同时操作一个数字,实现自增+1,如果只使用volatile关键字,无法保证操作的原子性,自增的原子操作包括:读取数字原始值、进行+1操作、写入工作内存。如果不保证原子操作,那么这三个子操作可能被分割开来。(具体可以参考:链接)volatile无法保证原子性的解决方法:
      1. 采用synchronized修饰自增的方法,即synchronized是可以保证原子性的
      2. 采用lock手动加锁解锁
      3. 使用juc中的atomic.AtomicInteger进行自增,AtomicInteger.getAndIncrement();

    保证多个线程永远获取变量的最新值,让变量成为各个线程之间的共享变量,即线程1修改变量后,其他变量可以感知到变量变化。和synchronized关键字的区别:(参考博客:点击链接

    1. volatile只能修饰变量,而synchronized可以修改变量,方法以及代码块
    2. volatile在多线程中不会存在阻塞问题,synchronized会存在阻塞问题
    3. volatile能保证数据的可见性,但不能完全保证数据的原子性,synchronized即保证了数据的可见性也保证了原子性
    4. volatile解决的是变量在多个线程之间的可见性,而sychroized解决的是多个线程之间访问资源的同步性

    线程死锁

    • 多个线程共占一些共享资源,并且等待其他线程占据的资源,从而导致多个线程互相等待对方资源,形成死锁。死锁的四个条件:
      1. 互斥:一个资源只能被一个进程使用
      2. 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的锁保持不放。
      3. 不剥夺条件:进程已获得的资源,在未结束之前,不可被剥夺。
      4. 循环等待条件:多个进程之间形成一个首尾相接循环等待条件。
    • 如果出现死锁,上述四个条件,只要破解其中一个即可解除死锁。
    • java程序死锁,可以通过jps、jstack、jconsole 3种方式快速找到死锁代码:点击跳转

    Lock(java.util.concurrent.lock | juc)

    • 为了防止线程死锁现象,需要在资源进行加锁。
    • Synchronized隐式锁
    synchronized(this){
    	xxx
    }
    
    • 1
    • 2
    • 3
    • Reentrantlock显式锁,可重入锁(re+entrant),是lock接口的实现类
    ReentrantLock rtl = new ReentrantLock();
    try{
       rtl.lock();
       xxx
    }finally	{
       // 一般有异常,在finally中解锁
       rtl.unlock();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    两个锁的方式进行对比:

    • 显式和隐式的区别主要在于隐式锁是自动加锁和解锁,显式锁需要手动处理
    • lock只有代码块锁,sychronized有代码块锁和方法锁

    线程通信(线程协作)

    JAVA中线程间的通信模型大致分为两类:共享内存和消息传递。

    • 共享内存的思路是多个线程同时监听内存中的某个变量,变量发生变化并且满足线程中业务条件后,执行对应的业务逻辑。
    • 消息传递即通过线程或对象的内置方法进行线程通知。

    JAVA线程之间通信的几种方法,只能在同步方法或者同步代码块中调用,否则会抛出异常:

    • 线程的控制方法详细介绍,参考 文章2
    • wait(long timeout):参数非必须,表示线程进入等待的状态,直到收到其他线程的通知,wait和sleep不同,wait会释放掉锁,而sleep会一直占据资源。
    • notify():唤醒一个在wait状态的线程
    • notifyAll():唤醒同一个对象中,所有处于wait状态的线程,优先级越高越优先调度。
    notify notifyAll wait suspend resume park unpark使用
    • 参考文章点击这里
    • wait会让当前线程挂起,而且当线程调用wait之后,会自动释放锁,notify,notifyAll会唤醒线程,wait和notify,notifyAll只能用在synchronized关键字中,而且必须是同一个对象锁,否则会报java.lang.IllegalMonitorStateException异常。
    • suspend/resume 这组 API 是被弃用的,原因是很容易死锁。具体造成死锁有两种形式:
      • 在同步关键字内使用不释放锁资源
      • suspend 和 resume 执行顺序反了
    协作方式加synchronized关键字先唤醒后挂起
    suspend/resume死锁死锁
    wait/notify,notifyAll不会死锁死锁
    park/unpark死锁不会死锁
    线程通信的几种方式:
    1. 线程间的通信方式,包括volatile、object的wait/notify、CountDownLatch(闭锁用法)、ReentrantLock结合Condition、LockSupport等方法,仔细阅读 文章1
      • CountDownLatch也是用的共享内存的设计思路,大概用法:初始化一个值N,使用CountDownLatch.await()进行线程等待,直到N变为0,线程就会被唤起自动执行,而其他线程就可以进行业务判断并用CountDownLatch.countDown()把N进行线程安全自减,直到触发await的线程。
      • latch的英文意思很形象:门闩、锁门器。
    2. 生产者/消费者模式(管程法),消费者不可以从生产者内部获取数据,他们之间设置个缓冲区,用生产者和消费者的代码演示该方式:
    import sun.misc.Cache;
    import java.beans.Customizer;
    
    public class ThreadCommumucate {
        public static void main(String[] args) {
            CacheContainer cacheContainer = new CacheContainer();
    
            new Cusomter(cacheContainer).start();
            new Provider(cacheContainer).start();
        }
    }
    
    // 消费者
    class Cusomter extends Thread{
        CacheContainer cacheContainer;
    
        public Cusomter(CacheContainer cacheContainer) {
            this.cacheContainer = cacheContainer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("购买到的产品:"+cacheContainer.buy().id);
            }
        }
    }
    
    // 生产者
    class Provider extends Thread{
        CacheContainer cacheContainer;
    
        public Provider(CacheContainer cacheContainer) {
            this.cacheContainer = cacheContainer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                cacheContainer.build(new Product(i));
                System.out.println("生产的产品:"+i);
            }
        }
    }
    
    // 产品
    class Product{
        int id;
        public Product(int id){
            this.id = id;
        }
    }
    
    // 缓冲池
    class CacheContainer{
        Product[] products = new Product[10];
        int count = 0;
    
        // 生产产品
        public synchronized void build(Product p){
            // 如果产品池达到了设置的最大值10,就让线程进行等待
            if(count==products.length){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 生产产品并且放进产品池
            products[count] = p;
            count++;
    
            // 通知所有消费者消费
            this.notifyAll();
        }
    
        // 消费产品
        public synchronized Product buy(){
            // 如果没有产品就等待
            if(count == 0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 从池子中拿走产品
            count--;
            Product p = products[count];
    
            // 通知生产者继续生产
            this.notifyAll();
    
            return p;
        }
    }
    
    • 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
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    1. 信号灯法, 使用标志位来控制线程之间的执行,模拟十字路口的红绿灯:
    public class SignoLight {
        public static void main(String[] args) {
            Crossroad crossroad = new Crossroad();
    
            new NorthRoad(crossroad).start();
            new EastRoad(crossroad).start();
    
        }
    }
    // 南北方向道路
    class NorthRoad extends Thread{
        Crossroad crossroad;
        public NorthRoad(Crossroad crossroad){
            this.crossroad = crossroad;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                this.crossroad.northRun();
            }
        }
    }
    // 东西方向道路
    class EastRoad extends Thread{
        Crossroad crossroad;
        public EastRoad(Crossroad crossroad){
            this.crossroad = crossroad;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                this.crossroad.eastRun();
            }
        }
    }
    
    // 十字路口
    class Crossroad{
    
        // 标志位
        // 为true时,南北放行,为false时,东西放行
        boolean flag = true;
    
        public synchronized void northRun(){
    
            // 如果为false,等待
            if(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 否则放行300ms,并通知其他线程执行
            System.out.println("南北放行300ms");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = !flag;
            this.notifyAll();
        }
    
        public synchronized void eastRun(){
    
            // 如果为true,等待
            if(flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            // 否则放行300ms,并通知其他线程执行
            System.out.println("东西放行300ms");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = !flag;
            this.notifyAll();
        }
    }
    
    
    // 输出结果为(南北和东西交替):
    南北放行300ms
    东西放行300ms
    南北放行300ms
    东西放行300ms
    南北放行300ms
    东西放行300ms
    ...
    
    • 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
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    线程池

    线程经常会被创建、销毁,在这个过程中会使用较多的资源,如果在并发量很高的情况下,会对系统性能造成很大的影响。基于性能考虑和资源利用的考虑,建立线程池对线程进行统一的管理,使用线程池的好处:

    • 提前创建好线程,使用时候直接拿来使用,减少了线程创建的时间,提升效率。
    • 系统使用完线程后,把线程归还给线程池,而不是进行销毁,以供后续功能继续使用,实现了重复利用,降低了资源消耗。
    • 便于线程的统一管理。

    JAVA线程池核心概念

    • 核心线程:线程池中常存的线程,即线程空闲时不会被销毁的线程
    • 非核心线程:线程空闲时即会被销毁的线程
    • 队列:如果在核心线程和非核心线程的工作都被占满的,新加入的请求会被暂存在队列中
    • 提交优先级:任务被装填至线程池的优先级顺序,核心线程>队列>非核心线程
    • 执行优先级:任务被装填后执行的优先级顺序,核心线程>非核心线程>队列

    JAVA线程池的7个核心参数

    • corePoolSize 核心线程数
    • maximumPoolSize 最大的线程数量
    • keepAliveTime 非核心线程在空闲状态下的存活时间
    • unit 存活时间的单位(指定秒、分钟、小时等)
    • workQueue 队列,当核心的线程满了,新加入的请求会被存放在改队列中
    • threadFactory 创建线程的工厂
    • handler 拒绝策略,当线程池中的线程达到最大线程数,根据拒绝策略进行回绝新的请求

    api

    • threadPoolExecuter
    • ScheduledExecuterService
    • Executer静态方法
      • newSingleThreadExecutor
      • newFixedThreadExecutor
      • newCachedThreadExecutor
      • newScheuledThreadExecutor
  • 相关阅读:
    LNMP架构:搭建Discuz论坛
    Rust之构建命令行程序(六):信息写入
    云上亚运:所使用的高新技术,你知道吗?
    el-table滚动加载、懒加载(自定义指令)
    Sonarqube与Angular集成
    【C++】gnustl_static 与 c++_shared 的区别
    多频电磁法概述 - 1. 简介
    C++入门知识
    持续集成与部署之CI/CD简介
    【Java八股文总结】之SpringBoot
  • 原文地址:https://blog.csdn.net/z591391960/article/details/126705176