多线程-实战优化

一. 回顾

关于线程相关的一些基础知识,本篇不再过多阐述,首先我们通过几个简单的问题,复习一下线程相关的一些基础知识。

1.进程和线程分别是什么?
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
2.进程、线程的例子?
进程:运行的QQ、迅雷、Word等应用进程,进程是系统级别的
线程:QQ多个聊天窗口、迅雷下载多个文件等
3.线程的状态(生命周期)
线程的状态枚举:java.lang.Thread.State 中状态值:
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED
线程状态
4.wait()和sleep()区别
sleep()是Thread类的方法;wait()是Object类的方法
sleep()方法使程序暂停执行指定的时间,让出cpu给其它线程,但是它的监控状态依然保持,也就说是不会释放对象锁,当指定的时间到了又会自动恢复运行状态;wait()方法使线程释放对象锁,进入此对象的等待锁定池,只有针对此对象调用notify()方法后,本线程才会重新进入准备获取对象锁的状态。

  1. 原理不同。sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,他会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动苏醒。例如,当线程执行报时功能时,每一秒钟打印出一个时间,那么此时就需要在打印方法前面加一个sleep()方法,以便让自己每隔一秒执行一次,该过程如同闹钟一样。而wait()方法是object类的方法,用于线程间通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法或者notifyAll()时才醒来,不过开发人员也可以给他指定一个时间,自动醒来。
  2. 对锁的 处理机制不同。由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,当调用wait()方法后,线程会释放掉他所占用的锁,从而使线程所在对象中的其他synchronized数据可以被其他线程使用。
  3. 使用区域不同。wait()方法必须放在同步控制方法和同步代码块中使用,sleep()方法则可以放在任何地方使用。sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用他的interrupt(),产生InterruptedException。由于sleep不会释放锁标志,容易导致死锁问题的发生,因此一般情况下,推荐使用wait()方法。

通过上面几个问题复习了一下基本概念,如果对这些概念有些模糊,小伙伴可以通过专栏简单看前几篇回忆一下。
Java多线程

在学习多线程的时候,我们都知道wait()、notfiy()、notifyAll()一些操作线程的方法,但是工作中我们其实很少直接使用这些方法进行多线的操作。我们使用多线程的时候要注意几点:
1.在高内聚低耦合的前提下,通过线程操作资源类
竞争资源+操作(对外暴露的调用方法)=资源类
2.操作方法实现
判断->业务逻辑->通知唤醒
判断时,防止虚假唤醒,应使用while()循环判断
3.使用JDK8 lock
synchronized-> wait\notify 替换==> lock->await\signal

在实际工作中更加关心业务逻辑的实现,直接使用这些基础方法很难写出高效且安全的多线程代码,一般都是通过线程池进行多线程实现,那么有没有什么规范可以参考吶?
当然,在《阿里巴巴Java开发手册》并发处理章节中,对于并发中的规约如下:
阿里巴巴开发手册
阿里巴巴开发手册中明确指出线程资源必须通过线程池提供,也就说我们在编码中应该通过线程池而不是new Thead()的方式。使用线程池的时候也不能通过Executors去创建。ok,进入正题–>线程池

二. 线程池

线程池的优势:

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。

这些都是套话,死记硬背当然是不可能。其实提到池,池化思想是很普遍的,如线程池、数据库连接池等,这些池化的优点也都是基本相通的.
1.通过资源的重用降低系统的资源消耗;
2.通过提前创建资源提高系统的响应速度;
3.提供更加高效安全的管理方法等;

线程池的使用

可以通过Executors工厂方法创建

方法 特点
newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程;
newFixedThreadPool(int) 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。池中的线程将一直存在,直到它显式出现shutdown
newSingleThreadPool() 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newScheduledThreadPool() 创建一个定长线程池,支持定时及周期性任务执行
通过上面四种方法基本可以创建平时所需的线程池,在配合常见如下api,基本可以完成任务了。
方法 说明
execute(Runnable) 执行任务
shutdown() 不在接受新的线程,并且等待之前提交的线程都执行完在关闭
shutdownNow() 直接关闭活跃状态的所有的线程 , 并返回等待中的线程
getActiveCount() 获取线程池活动线程数量

线程池api很多,只列举几个。通过Executors创建所需线程池,搭配基本api,貌似已经可以完成业务逻辑代码了,万事大吉了。
我们当然不能停下思考的脚本,为什么开发手册强制不能使用Executors创建线程池吶?

Executors 返回的线程池对象的弊端如下
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

原来这样会造成OOM。为什么吶?我们以CachedThreadPool为例看一下创建线程池方法的源码。

    //1.java.util.concurrent.Executors#newCachedThreadPool()
    public static ExecutorService newCachedThreadPool() {
   
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aotulive

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值