多线程基础篇(二)

本文详细讲解了线程的生命周期阶段,包括新建、就绪、运行、阻塞和死亡状态,还剖析了线程安全问题的成因、同步机制和synchronized与Lock的区别。此外,讨论了线程死锁的概念、产生条件与处理策略,以及如何使用线程池提高效率。
摘要由CSDN通过智能技术生成

一、线程生命周期

在这里插入图片描述

1.1 新建

  • new关键字创建了一个线程之后,该线程就处于新建状态
  • JVM为线程分配内存,初始化成员变量值

1.2 就绪

  • 当线程对象调用了start()方法之后,该线程处于就绪状态
  • JVM为了创建线程方法栈和程序计数器,等待线程调度器调度

1.3 运行

  • 就绪状态的线程获得CPU资源,开始运行run()方法,该线程进入运行状态

1.4 阻塞

  • 线程调用sleep()方法主动放弃所占用的处理器资源
  • 线程调用了一个阻塞式IO方法,在该方法返回之前,该程序被阻塞
  • 线程试图获得一个同步锁,但该同步锁正被其他线程所持有
  • 线程在等待某个同事notify
  • 程序调用了线程的suspend()方法将该线程挂起,但这个方法容易导致死锁,所以应该尽量避免使用该方法

1.5 死亡

线程三种结束方式,结束后就处于死亡状态

  • run()或call()方法执行完成,线程正常结束
  • 线程抛出一个未捕获的Exception或Error
  • 调用该线程stop方法来结束该线程,该方法容易导致死锁,不推荐使用

二、线程安全问题

2.1 什么是线程安全

如果多个线程同时运行同一个实现了Runnable接口的类,程序每次运行结果和单线程运行的结果一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的,反之则线程不安全。

2.2 问题分析

线程安全问题都是有全局变量及静态变量引起的。若每个线程对全局变量、静态变量只读,不写一般来说,这个变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

  • 多个线程在操作共享的数据
  • 操作共享数据的线程代码有多条
  • 多个线程对共享数据有写操作

2.3 线程同步

要解决线程问题,只有在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能抢占CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的线程。

七种线程同步机制

  • 同步代码块 synchronized

    public class Ticket implements Runnable{
    
        //先指定票数有100张
        private int ticketNum = 100;
    
        private Object obj = new Object();
    
        @Override
        public void run() {
            while (true){
                synchronized (obj){
                    if(ticketNum>0){
                        //线程睡眠100毫秒
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //打印当前售出的票数字和线程名
                        String name = Thread.currentThread().getName();
                        System.out.println("线程"+name+"销售电影票:"+ticketNum--);
                        //票数减一
                    }
                }
    
            }
    
        }
    }
    
  • 同步方法 synchronized

    /**
    * 对于非static方法,同步锁就是this
    * 对于static方法,同步锁是当前方法所在类的字节码对象(类名.class)
    */
    public class Ticket implements Runnable{
    
        //先指定票数有100张
        private int ticketNum = 100;
    
        private Object obj = new Object();
    
        @Override
        public void run() {
            while (true){
                saleTcikt();
            }
    
        }
    
        private synchronized void saleTcikt(){
            if(ticketNum>0){
                //线程睡眠100毫秒
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //打印当前售出的票数字和线程名
                String name = Thread.currentThread().getName();
                System.out.println("线程"+name+"销售电影票:"+ticketNum--);
                //票数减一
            }
        }
    }
    
  • 同步锁 ReenreantLock

    public class Ticket implements Runnable{
    
        //先指定票数有100张
        private int ticketNum = 100;
    
        //参数是否是公平锁 true:公平锁,多个线程都公平拥有执行权 false:非公平独占锁
        private Lock lock = new ReentrantLock(true);
    
        @Override
        public void run() {
            while (true){
                lock.lock();//加锁
                try {
                    if(ticketNum>0){
                        //线程睡眠100毫秒
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //打印当前售出的票数字和线程名
                        String name = Thread.currentThread().getName();
                        System.out.println("线程"+name+"销售电影票:"+ticketNum--);
                        //票数减一
                    }
                }finally {
                    lock.unlock();//释放锁
                }
            }
        }
    }
    
  • 特殊域变量 volatile

  • 局部变量 ThreadLocal

  • 阻塞队列 LinkedBlockingQueue

  • 原子变量 Atomic*

2.4 Synchronized和Lock区别

  • synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁
  • synchronized会自动释放锁,Lock需要在finally中手动释放锁
  • 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁 ,线程2等待,通过线程1阻塞,线程2会一直等待下去;Lock锁就不会一直等待下午,如果尝试获取不到锁,线程可以不用一直等待就结束了
  • synchronized的锁可重入,不可中断,非公平,Lock锁可重入,可判断、可公平
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题

三、线程死锁

3.1 什么是死锁

多线程以及多进程改善了系统资源的利用率并提高了系统的处理能力。然而并发执行也带来了新的问题-死锁。

所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

3.2 死锁产生的必要条件

  • 互斥条件
    • 若有其他进程请求该资源,则请求进程只能等待
  • 不可剥夺条件
    • 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程主动释放资源
  • 请求与保持条件
    • 进程已经保持了至少一个资源,但是又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
  • 循环等待条件
    • 存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。

3.3 死锁处理

  • 预防死锁:通过设置某些限制条件,去破坏产生死活的四个必要条件中的一个或几个条件,来预防死锁的产生
  • 避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生
    • 有序资源分配法
    • 银行家算法
    • 顺序加锁
    • 限时加锁
  • 检测死锁:允许系统在运行过程中发生死锁,但可设置检测机构及时检测死锁的发生,并才去适当措施加以清除
  • 解除死锁:当检测出死锁后,便采用适当措施将进程从死锁状态中解脱出来

四、线程池

4.1 为什么使用线程池

多线程的缺点:处理任务的线程创建和销毁都非常耗时并消耗资源,多线程之间的切换也会非常耗时并消耗资源

解决办法:采用线程池,使用时线程已存在,消除了线程创建的时耗,通过设置线程数目,防止资源不足

4.2 ThreadPoolExecutor的全参构造函数参数介绍

在java中创建线程池最核心的类是ThreadPoolExecutor,参数最全的一个构造函数

public ThreadPoolExecutor(	int corePoolSize,
                      int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,                  							ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) {
    
}

函数的参数含义如下:

  • corePoolSize: 线程池核心线程数
  • maximumPoolSize:线程池最大数
  • keepAliveTime: 空闲线程存活时间
  • unit: 时间单位
  • workQueue: 线程池所使用的缓冲队列
  • threadFactory:线程池创建线程使用的工厂
  • handler: 线程池对拒绝任务的处理策略

4.3 四种常用线程池

ThreadPoolExecutor 参数多一些,java就给提供了一个Executors接口,Executors类提供了四个创建线程池的方法

  • newCachedThreadPool
  • newFixedThreadPool
  • newSingleThreadExecutor
  • newScheduleThreadPool
import java.util.List;
import java.util.concurrent.*;

public class MyThread {

    public static  void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception {
        // 测试: 提交15个执行时间需要3秒的任务,看超过大小的2个,对应的处理情况
        for (int i = 0; i < 15; i++) {
            int n = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("开始执行:" + n);
                        Thread.sleep(3000L);
                        System.err.println("执行结束:" + n);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            System.out.println("任务提交成功 :" + i);
        }
        // 查看线程数量,查看队列等待数量
        Thread.sleep(500L);
        System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
        System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
        // 等待15秒,查看线程数量和队列数量(理论上,会被超出核心线程数量的线程自动销毁)
        Thread.sleep(15000L);
        System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
        System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
    }

    /**
     * 1、线程池信息: 核心线程数量5,最大数量10,无界队列,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest1() throws Exception {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5,
                                                    TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

        testCommon(threadPoolExecutor);
        // 预计结果:线程池线程数量为:5,超出数量的任务,其他的进入队列中等待被执行
    }

    /**
     * 2、 线程池信息: 核心线程数量5,最大数量10,队列大小3,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest2() throws Exception {
        // 创建一个 核心线程数量为5,最大数量为10,等待队列最大是3 的线程池,也就是最大容纳13个任务。
        // 默认的策略是抛出RejectedExecutionException异常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.err.println("有任务被拒绝执行了");
            }
        });
        testCommon(threadPoolExecutor);
        // 预计结果:
        // 1、 5个任务直接分配线程开始执行
        // 2、 3个任务进入等待队列
        // 3、 队列不够用,临时加开5个线程来执行任务(5秒没活干就销毁)
        // 4、 队列和线程池都满了,剩下2个任务,没资源了,被拒绝执行。
        // 5、 任务执行,5秒后,如果无任务可执行,销毁临时创建的5个线程
    }

    /**
     * 3、 线程池信息: 核心线程数量5,最大数量5,无界队列,超出核心线程数量的线程存活时间:5秒
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest3() throws Exception {
        // 和Executors.newFixedThreadPool(int nThreads)一样的
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        testCommon(threadPoolExecutor);
        // 预计结:线程池线程数量为:5,超出数量的任务,其他的进入队列中等待被执行
    }

    /**
     * 4、 线程池信息:
     * 核心线程数量0,最大数量Integer.MAX_VALUE,SynchronousQueue队列,超出核心线程数量的线程存活时间:60秒
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest4() throws Exception {

        // SynchronousQueue,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列。
        // 在使用SynchronousQueue作为工作队列的前提下,客户端代码向线程池提交任务时,
        // 而线程池中又没有空闲的线程能够从SynchronousQueue队列实例中取一个任务,
        // 那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。
        // 此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到其最大线程池大小maximumPoolSize)。

        // 和Executors.newCachedThreadPool()一样的
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
        testCommon(threadPoolExecutor);
        // 预计结果:
        // 1、 线程池线程数量为:15,超出数量的任务,其他的进入队列中等待被执行
        // 2、 所有任务执行结束,60秒后,如果无任务可执行,所有线程全部被销毁,池的大小恢复为0
        Thread.sleep(60000L);
        System.out.println("60秒后,再看线程池中的数量:" + threadPoolExecutor.getPoolSize());
    }

    /**
     * 5、 定时执行线程池信息:3秒后执行,一次性任务,到点就执行 <br/>
     * 核心线程数量5,最大数量Integer.MAX_VALUE,DelayedWorkQueue延时队列,超出核心线程数量的线程存活时间:0秒
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest5() throws Exception {
        // 和Executors.newScheduledThreadPool()一样的
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
        threadPoolExecutor.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务被执行,现在时间:" + System.currentTimeMillis());
            }
        }, 3000, TimeUnit.MILLISECONDS);
        System.out.println(
                "定时任务,提交成功,时间是:" + System.currentTimeMillis() + ", 当前线程池中线程数量:" + threadPoolExecutor.getPoolSize());
        // 预计结果:任务在3秒后被执行一次
    }


    /**
     * 6、 定时执行线程池信息:线程固定数量5 ,<br/>
     * 核心线程数量5,最大数量Integer.MAX_VALUE,DelayedWorkQueue延时队列,超出核心线程数量的线程存活时间:0秒
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest6() throws Exception {
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
        // 周期性执行某一个任务,线程池提供了两种调度方式,这里单独演示一下。测试场景一样。
        // 测试场景:提交的任务需要3秒才能执行完毕。看两种不同调度方式的区别
        // 效果1: 提交后,2秒后开始第一次执行,之后每间隔1秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,完毕后立刻执行)。
        // 也就是说这个代码中是,3秒钟执行一次(计算方式:每次执行三秒,间隔时间1秒,执行结束后马上开始下一次执行,无需等待)
        threadPoolExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务-1 被执行,现在时间:" + System.currentTimeMillis());
            }
        }, 2000, 1000, TimeUnit.MILLISECONDS);

        // 效果2:提交后,2秒后开始第一次执行,之后每间隔1秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,等上一次执行完毕后再开始计时,等待1秒)。
        // 也就是说这个代码钟的效果看到的是:4秒执行一次。 (计算方式:每次执行3秒,间隔时间1秒,执行完以后再等待1秒,所以是 3+1)
        threadPoolExecutor.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务-2 被执行,现在时间:" + System.currentTimeMillis());
            }
        }, 2000, 1000, TimeUnit.MILLISECONDS);
    }

    /**
     * 7、 终止线程:线程池信息: 核心线程数量5,最大数量10,队列大小3,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的
     *
     * @throws Exception
     */
    public static void  threadPoolExecutorTest7() throws Exception {
        // 创建一个 核心线程数量为5,最大数量为10,等待队列最大是3 的线程池,也就是最大容纳13个任务。
        // 默认的策略是抛出RejectedExecutionException异常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.err.println("有任务被拒绝执行了");
            }
        });
        // 测试: 提交15个执行时间需要3秒的任务,看超过大小的2个,对应的处理情况
        for (int i = 0; i < 15; i++) {
            int n = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("开始执行:" + n);
                        Thread.sleep(3000L);
                        System.err.println("执行结束:" + n);
                    } catch (InterruptedException e) {
                        System.out.println("异常:" + e.getMessage());
                    }
                }
            });
            System.out.println("任务提交成功 :" + i);
        }
        // 1秒后终止线程池
        Thread.sleep(1000L);
        threadPoolExecutor.shutdown();
        // 再次提交提示失败
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("追加一个任务");
            }
        });
        // 结果分析
        // 1、 10个任务被执行,3个任务进入队列等待,2个任务被拒绝执行
        // 2、调用shutdown后,不接收新的任务,等待13任务执行结束
        // 3、 追加的任务在线程池关闭后,无法再提交,会被拒绝执行
    }


    /**
     * 8、 立刻终止线程:线程池信息: 核心线程数量5,最大数量10,队列大小3,超出核心线程数量的线程存活时间:5秒, 指定拒绝策略的
     *
     * @throws Exception
     */
    public static void threadPoolExecutorTest8() throws Exception {
        // 创建一个 核心线程数量为5,最大数量为10,等待队列最大是3 的线程池,也就是最大容纳13个任务。
        // 默认的策略是抛出RejectedExecutionException异常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.err.println("有任务被拒绝执行了");
            }
        });
        // 测试: 提交15个执行时间需要3秒的任务,看超过大小的2个,对应的处理情况
        for (int i = 0; i < 15; i++) {
            int n = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println("开始执行:" + n);
                        Thread.sleep(3000L);
                        System.err.println("执行结束:" + n);
                    } catch (InterruptedException e) {
                        System.out.println("异常:" + e.getMessage());
                    }
                }
            });
            System.out.println("任务提交成功 :" + i);
        }
        // 1秒后终止线程池
        Thread.sleep(1000L);
        List<Runnable> shutdownNow = threadPoolExecutor.shutdownNow();
        // 再次提交提示失败
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("追加一个任务");
            }
        });
        System.out.println("未结束的任务有:" + shutdownNow.size());

        // 结果分析
        // 1、 10个任务被执行,3个任务进入队列等待,2个任务被拒绝执行
        // 2、调用shutdownnow后,队列中的3个线程不再执行,10个线程被终止
        // 3、 追加的任务在线程池关闭后,无法再提交,会被拒绝执行
    }



}


class Test{
    public static void main(String[] args) {
        try {
            MyThread.threadPoolExecutorTest2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值