程序、进程、线程

程序

概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态代码

进程

概念:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有其自身的产生、存在、消亡的过程。——生命周期
说明:进程作为资源分配单位,系统在运行时会为每个进程分配不同的内存区域

线程

概念:进程可以进一步细分为线程,是程序内部的一条执行路径
说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
内存结构:
多线程 - 图1
进程可以细化为多个线程
每个线程拥有自己独立的:栈、程序计数器
多个线程,共享同一个进程中的机构:方法区和堆

线程的生命周期

多线程 - 图2


多线程

优势

  • 提高程序的运行性能
  • 充分利用系统的处理能力,提高系统资源利用率
  • 提高系统响应性,同时处理多个任务

    问题

  1. 线程安全问题:对共享数据的操作可能得到不同的结果
  • 如++i操作,先去i再加1再写回,并发线程可能会同时写回造成数据错误
  • if-then-do 竞争条件使得多个线程同时进入条件进行数据的重复添加
  1. 活跃性问题:加锁解锁可能导致线程死锁
  • 不同的线程分别占用对方需要的共享资源不放弃,都在等对方释放自己需要的共享资源,就形成了死锁
  • 出现死锁后,不会出现异常、提示,只是所有线程都在阻塞状态,无法继续
  • 预防死锁的原则:所有的线程都按照相同的顺序获得资源的锁
  1. 性能问题:多个线程并发执行,线程切换会有一定的损耗

    Thread

    Java中只有这么⼀种东⻄代表线程
    start⽅法才能并发执⾏!
    每多开⼀个线程,就多⼀个执⾏流
    ⽅法栈(局部变量)是线程私有的
    静态变量/类变量是被所有线程共享的

    解决线程安全问题:

    在java中通过同步机制,解决线程安全问题
    同步解决了线程安全问题,操作同步代码时,其他线程等待,相当于一个单线程的过程,效率低

    实现线程安全的基本手段

    1.不可变类

    Integer
    String

    2.同步块、同步方法

  2. 同步代码块

    1. private static final Object lock =new Object();
    2. synchronized(一个对象){
    3. //操作共享数据的代码
    4. }
  3. 同步方法 ``` public synchronized void threadTest(){ //操作共享数据 } //等价于 public void threadTest(){ synchronized(this){ //操作共享数据 }

``` 实例的synchronnized⽅法把该实例当成锁
Static synchronized⽅法 把Class对象当成锁

  1. Collections.synchronized

    将集合中的方法用synchronized方法包起来,使这个方法成为线程安全的,但调用不同的方法还是会出问题,就需要使用同步方法的方式

  2. JUC包

  • AtomicInteger
  • ConcurrentHashMap

    任何使⽤HashMap有线程安全问题的地⽅ 都⽆脑地使⽤ConcurrentHashMap替换即可。

  • ReentrantLock 可重入锁

在不同的地方加锁解锁

ThreadLocal

Java 里一种特殊变量
是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。 可以通过 ThreadLocal value = new ThreadLocal (); 来使用。

线程池

为什么要使用线程池?

果要执行的任务很多,每个任务都需要一个线程的话,那么频繁的创建、销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用,另外,如果无限制的创建大量的线程,大量的线程会占用内存资源并且可能会导致Out of Memory。

线程池类ThreadPoolExecutor的参数

原文链接:https://blog.csdn.net/ye17186/article/details/89467919

1.corePoolSize:核心线程数

  • 核心线程会一直存活,即使没有任务需要执行
  • 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
  • 设置allowCoreThreadTimeout=true,核心线程会超时关闭,默认是false

    2.queueCapacity:阻塞队列workQueue的大小

  • 当核心线程数达到最大时,新任务会放在队列中排队等待执行

    3.maxPoolSize:最大线程数

    当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

    4.keepAliveTime 空闲线程存活时间

    一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

    5.unit 空闲线程存活时间单位

    keepAliveTime的计量单位

    6.workQueue 工作队列

    新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

  • ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

  • LinkedBlockingQuene

基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

  • SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

  • PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

应用场景

  1. CPU密集型 不适合使用,多线程带来的提升有限
  2. IO密集型
  • 网络IO
  • 文件IO