美文网首页
学习笔记1-线程基础

学习笔记1-线程基础

作者: 在知识的海洋里遨游 | 来源:发表于2020-05-20 18:53 被阅读0次

    线程状态:更详细说明参考 https://www.jianshu.com/p/ec94ed32895f

    1创建.NEW 例如:Thread thread = new Thread(new Runable(){});

    2.可运行RUNNABLE 调用start方法后处于RUNNABLE状态 thread.start();

    3.带超时时间的等待TIMED_WAITING 例如sleep(1000),Object.wait(1000) ,Thread.join(1000),时间结束会自动唤醒线程

    4.不带超时时间的等待 例如Object.wait() ,Thread.join(), LockSupport.park()

    5.阻塞BLOCKED 两个线程在争夺同一把锁的时候,进入阻塞状态,可使用synchronized关键字实现

    6.死亡TERMINATED 线程执行结束,进入此状态

    CPU为了优化性能

    1.有三级缓存,缓存中数据在同一时间有可能和主内存中数据不一致

    2.cpu为了优化执行速度,可能会对程序进行重排序,前提是不影响运行结果,多核cpu之间如果要写,则会通知其他cpu自己正在做的事,同时关注其他cpu的通知,防止线程不安全的情况发生

    线程中止:

    stop方法会使等待状态的线程强行中止,破坏原子性,故此方法被弃用

    正确的中止应使用inerrupt,线程不会强行中止等待线程,而是抛出异常,我们可以在异常中捕获处理

    线程之间的通讯方式

    1.文件2.变量3.网络4.jdk提供的方式

    前三种都是将一些状态写入存储中介,其他线程去读取,获得该线程的状态

    jdk提供了1.suspend,resmer.(容易出现死锁,1.因为suspend方法不会自动释放锁,如果挂起和唤醒操作均加同一把锁,则会死锁,2.二者有先后顺序,如果唤醒在挂起之前执行,也会死锁.故而此API被弃用)

    2.推荐wati,notify/notifyAll : 会自动释放锁,但仍然要注意先后顺序,类似于坐火车场景,当车已经开过去了,你才来等车,是永远等不到车的

    3.推荐方案:park和unpark机制,是一种令牌机制,park等待令牌,unpark发放令牌,如果unpark先执行,park后执行,只要看到令牌已经发放了,就会执行,类似于等绿灯,只要绿灯存在,车先来或者后来,都会往前开,但是却无法自动释放锁,所以在同步代码块会死锁,

    总结:2,3两种方法互补,各自有不能解决的情景

    伪唤醒:

    底层cpu调度会产生非unpark/notifyAll /notify的唤醒,所以判断是否唤醒的条件不要用if应该用while来判断

    线程封闭ThreadLocal

    ThreadLocal<Object> threadLocal = new ThreadLocal<>();相当于一个Map,键是线程,每个线程可以设置一个仅自己可见的值,避免了多线程的资源争抢,从而保证线程安全

    线程池:

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10,5,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>()new RejectedExecutionHandler() {//拒绝策略
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            System.out.println("有任务被我拒绝了!");
                        }
                    });
    

    参数依次为:核心线程数量为5,最大线程数量为10,线程空闲的最大时间是5秒,超过这个时间就会销毁,销毁时间单位是秒,缓冲队列是无界队列.最后一个参数是拒绝策略的实现
    线程池工作流程:当任务来临,先判断线程池中核心线程数量是否<5,是则创建线程,否则将任务放入缓存队列,若缓存队列已满,则创建新线程,允许创建的线程最大数量为:最大线程数量-核心线程数量,即10-5 = 5,若仍然有新任务,则会采取拒绝策略.如果空闲线程超过5秒,则销毁,但会保证线程池中核心线程数量最小为5个.
    举例:核心线程相当于公司的正式员工,最大线程代表公司最多能放多少个工位,缓冲队列代表公司的仓库,那么公司接到订单,就会招正式员工,正式员工招满之后,再来新的订单,就会把订单先放到仓库,正式员工做完手上的工作,就去仓库取新任务做,如果仓库堆满了,就需要扩招,这个时候就可以招一些临时工,最多可以招多少个员工,取决于公司有多少个工位,等公司所有工位坐满了,且仓库也堆满了,再来订单就只能拒绝了,如果到了淡季没有订单且员工空闲的时间超过了设定的超时时间,这个时候,就会裁掉这个临时工,但不会把正式员工也裁掉,人家有合同呢,没法随便裁

    2.特殊线程使用方式,缓存线程池:

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0,15,5,TimeUnit.SECONDS,new SynchronousQueue<>());
    

    核心线程数量为0,最大线程数量为15,线程空闲的最大时间是5秒,超过这个时间就会销毁,销毁时间单位是秒,缓冲队列是同步队列,相当于大小为0的队列
    这个队列有新任务来时,由于核心数量为0,会加入缓存队列,那么加入会被拒绝,然后就会创建线程,过了空闲时间就销毁,伸缩性较好,适用于新任务时多时少的场景

    3.定时任务线程池:

    1)3秒后执行任务,仅执行一次

    new ScheduledThreadPoolExecutor(5).schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("现在时间是 : " + System.currentTimeMillis());
                }
            },3000,TimeUnit.MILLISECONDS);
    

    2)周期性任务:2秒后开始第一次执行,每隔1秒执行一次,任务每次执行时间为3秒,线程数量为5,
    如果是scheduleAtFixedRate这种调度方式,如果任务执行时间大于间隔时间,则会在上次任务结束,立马开始下一次任务,不会出现同一个任务重叠执行
    如果是scheduleWithFixedDelay这种调度方式,如果任务执行时间大于间隔时间,则会在上次任务结束,等待1秒后开始下一次任务,不会出现同一个任务重叠执行

    new ScheduledThreadPoolExecutor(5).scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },2000,1000,TimeUnit.MILLISECONDS);
    

    线程池的中止方式:shutdown和shutdownNow

    前者会继续执行完当前所有任务,并且拒绝新任务,等所有任务执行结束之后关闭线程池
    后者会强行关闭,正在执行的任务不会继续执行

    如何定义线程池的大小

    1.计算型的业务多,数量=cpu核数*1~2 例如8核的计算器,可以开16个线程数
    2.io类型比较多,因为耗时,所以可以多开点线程
    3.具体而言,可以观察服务器的cpu占用率,在80左右是比较高性能的,少则加开,多则减少
    4.也可以考虑使用缓存线程池,动态变化线程池大小

    参考此公式:最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

    相关文章

      网友评论

          本文标题:学习笔记1-线程基础

          本文链接:https://www.haomeiwen.com/subject/jvcvyhtx.html