java多线程

 

目录

线程与进程

区别

协程

线程的工作原理 内存模型

线程安全:

多线程的优点: 

同步 异步 阻塞 非阻塞

如何创建线程

启动线程时,Start与run的区别是什么

ThreadLocal的实现  

Spring使用ThreadLocal解决线程安全问题:

java中的线程有几种状态?  新建状态  就绪状态 运行状态 阻塞状态 终止状态

看哪些线程处于阻塞状态 jstack  ps -ef   ps -aux

java中的线程池实现 4种 FixedThreadPool底层使用的是什么任务队列? 

ThreadPoolExecutor参数含义: 7 个

线程池的状态 5 种

向线程池提交任务(2种)

execute()内部实现

 线程池容量的动态调整

Java线程池使用有界队列实现时,饱和策略有哪些?

解决的问题


线程与进程

  • 线程是CPU调度和分配的基本单位,进程是资源分配和调度的基本单位
  • 一个进程中可以包括多个线程,线程共享整个进程的资源(寄存器、堆栈、上下文),线程间要进行同步和互斥

区别

  • 进程结束后它拥有的所有线程都将销毁,线程的结束不会影响同个进程中的其他线程结束
  • 线程是轻两级的进程,创建和销毁所需要的时间比进程多,操作系统中的功能执行需要创建线程

协程

  1. 协程协程是一种用户态的轻量级线程,由用户程序控制调度、切换开销更小,操作系统完全感知不到
  2. 最大限度地利用cpu

线程的工作原理 内存模型

jvm有一个主存main memory,而每个线程有自己的(工作内存)working memory,一个线程对一个变量所有操作,都要在工作内存里建立一个副本,操作完之后再写入主存;

(1) 从主存复制变量到工作内存 (read and load) 

(2) 执行代码,操作变量 (use and assign) 

(3) 工作内存数据刷新到主存 (store and write)

线程安全:

  1. 当多个线程访问某个类,其始终能表现出正确的行为
  2. 采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,限制其他线程访问,直到锁释放

多线程的优点: 

1、提高应用程序响应:当一个操作耗时很长时,整个系统都会等待这个操作,使用多线程技术,将耗时长的操作置于一个新的线程,可以避免这种情况。 

2、使多CPU系统有效,充分利用:操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 

3、改善程序结构:复杂的进程可以分为多个线程,成为几个独立的运行部分,程序利于理解和修改

同步 异步 阻塞 非阻塞

进程调用,IO,方法:

同步:功能调用后,没有得到结果前,调用不返回

异步:通过状态、回调函数处理调用,无需立刻返回

线程:

阻塞:结果返回之前,当前线程会被挂起,让出cpu时间,仅在得到结果之后才继续执行

非阻塞:即使不能立刻得到结果,也不会挂起线程

多线程同步方式

  • synchronized关键字修饰【类、静态变量、方法、代码块】 synchronized (this) {  }
  • volatile关键字 可见性,免锁
  • 重入锁 ReentrantLock

 

如何创建线程

Java使用Thread类代表线程,所有的线程对象都Thread类或其子类的实例

3种方式来创建线程:

1)继承Thread类创建线程

  • 并重写该类的run()方法---线程执行体
  • 调用线程的start()方法

2)实现Runnable接口创建线程 适合多继承

  • 创建Runnable实现类的实例
  • 这个实例作为Thread的参数来创建Thread对象
  • start()方法来启动线程
  • 若自定义类需要继承其他类,实现Runnable接口

3)使用Callable和Future创建线程

  1. call()方法可以有返回值,任务执行完毕之后得到任务的执行结果
  2. call()方法可以声明抛出异常

java5提供了Future接口来代表Callable接口里的call()方法的返回值,提供FutureTask实现类,实现类实现了Future接口,并实现了Runnable接口

既能当做一个Runnable直接被Thread执行,也能作为Future用来得到Callable的计算结果

启动线程时,Start与run的区别是什么

1)start方法

  start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

2)run方法

  run()方法是不需要用户来调用的,当start方法启动线程后,线程获得CPU执行时间,便进入run方法体去执行具体的任务。继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

ThreadLocal的实现  

  1. ThreadLocal维护变量时,为每个使用该变量的线程提供独立的变量副本
  2. 每一个线程可以独立地改变自己的副本,不影响其它线程的副本

应用:数据库连接的独立建立与关闭

每个线程中都有一个connect变量

Spring使用ThreadLocal解决线程安全问题:

同一线程贯通MVC三层,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

java中的线程有几种状态?  新建状态  就绪状态 运行状态 阻塞状态 终止状态

线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。

所谓阻塞状态是:正在运行的线程没有运行结束,暂时让出CPU

1.新建状态(New): 

当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态

2.就绪状态(Runnable)

        线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态

       于就绪状态的线程必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。多个处于就绪状态的线程由线程调度程序(thread scheduler)调度

3.运行状态(Running)

        当线程获得CPU时间后,进入运行状态,真正开始执行run()方法.

4. 阻塞状态(Blocked) **********

所谓阻塞状态是:正在运行的线程被挂起,暂时让出CPU,其他处于就绪状态的线程就可以获得CPU时间,进入运行状态

        线程运行过程中,可能由于各种原因进入阻塞状态:

        1>sleep方法进入睡眠状态;

        2>I/O上阻塞的操作,即该操作在输入输出操作完成之前不会返回

        3>试图得到一个锁,而该锁正被其他线程持有;

        4>等待触发条件

TIMED_WAITING,对应"阻塞"状态,代表此线程正处于有限期的主动等待中,要么有人唤醒它,要么等待够了一定时间之后,才会再次进入就绪状态  

5. 终止状态(Dead)

        有两个原因会导致线程死亡:

        1) run方法正常退出而自然死亡,

        2) 一个未捕获的异常终止了run方法,线程猝死。

        为了确定线程在当前是否存活着(运行、阻塞),使用isAlive方法:如果是可运行或被阻塞,返回true; new状态或不可运行, 或死亡,返回false

看哪些线程处于阻塞状态 jstack  ps -ef   ps -aux

jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息

什么是阻塞队列

阻塞添加

当阻塞队列元素已满时,队列会阻塞加入元素的线程,直队列元素不满时才重新唤醒线程执行加入操作。

阻塞删除

队列元素为空时,删除队列元素的线程将被阻塞,直到队列不为空再执行删除操作

java中的线程池实现 4种 FixedThreadPool底层使用的是什么任务队列? 

线程池的接口:Executors接口;实现类ThreadPoolExecutor类

ThreadPoolExecutor通过构造方法的参数,构造不同配置的线程池,除了ScheduledThreadPool

1.FixedThreadPool( 3 ) 线程数

说明:指定线程数的线程池,使用LinkedBlockingQuene作为阻塞队列

特点:当线程池没有可执行任务时,也不会释放线程。

2.CachedThreadPool( ) 无参数

说明:可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,SynchronousQueue作为阻塞队列(不存储元素);

特点:在没有任务执行时,线程的空闲时间超过keepAliveTime 60s,会自动释放线程资源;

当提交新任务时,如果没有空闲线程,则创建新线程执行任务;否则复用未超过60s的空闲线程

使用时注意控制并发的任务数,防止因创建大量的线程导致而降低性能

3.SingleThreadExecutor( )

说明:初始化只有一个线程的线程池,内部使用LinkedBlockingQueue作为阻塞队列。

特点:保证所提交任务的顺序执行,如果该线程异常结束,会重新创建一个新的线程继续执行任务,

4.ScheduledThreadPool() }, 1, 3, TimeUnit.SECONDS); 定义在ExecutorService接口中

特定:延迟(定时)执行、周期执行;延迟1秒后,每3秒执行一次

ThreadPoolExecutor参数含义: 7 个

<1>corePoolSize:线程池中核心线程的数量,预创建线程; 作用:频繁创建线程和销毁线程需要时间,

  1. 初始线程池中没有线程,等待有任务到来才创建线程
  2. 少于corePoolSize的一直创建线程,即使有线程空闲
  3. 任务数多于corePoolSize的任务放入阻塞队列;
  4. 阻塞队列满,继续创建线程直到maximumPoolSize

<2>maximumPoolSize:线程池允许创建的最大线程数;

  1. 阻塞队列满,已创建的线程数小于maximumPoolSize,创建新的线程来处理阻塞队列中的任务,不超过maximumPoolSize;

<3>keepAliveTime:工作线程空闲之后继续存活的时间,默认一直存活

  1. 默认线程数大于corePoolSize时起作用,线程数目小于corePoolSize,一直存活;

<4>unit:参数keepAliveTime的时间单位;如:TimeUnit.SECONDS

<5>workQueue:阻塞队列;存储等待执行的任务,有四种阻塞队列类型,

  1. ArrayBlockingQueue(基于数组的有界阻塞队列)
  2. LinkedBlockingQueue(基于链表结构的阻塞队列)
  3. SynchronousQueue(不存储元素的阻塞队列) 无界队列
  4. PriorityBlockingQueue(具有优先级的阻塞队列)

<6>threadFactory:用于创建线程的线程工厂; 作用:统一在创建线程时的参数,如是否守护线程。

<7>handler:饱和策略;阻塞队列满,没有空闲线程,线程数目已达到最大数量,队列处于饱和状态,

1.Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。

2.CallerRuns策略:在调用者的线程中运行新的任务,既不抛弃任务也不抛出异常。

3.Discard策略: 新任务被抛弃。

4.DiscardOldest策略:旧任务被抛弃。(不适合工作队列为优先队列场景,否则优先级高的会被抛弃)

线程池的状态 5 种

1、RUNNING:会接收新任务、处理阻塞队列中的任务;

2、SHUTDOWN: 不会接收新任务,会处理阻塞队列中的任务;

3、STOP :不接收,不处理任务,会中断正在运行的任务;

4、TIDYING :对线程进行整理优化;

5、TERMINATED: 停止工作;

向线程池提交任务(2种)

有两种方式:

1.executor.execute (new Runnable()

重复以下代码添加任务到线程池中

ExecutorService executor=Executors.newFixedThreadPool(2);

executor.execute(new Runnable() { @Override public void run() {具体任务 }

});

2.executor.submit(new Runnable()

Future对象来判断当前的线程是否执行完毕,new Runnable() 无法返回数据信息

Future future = executor.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); //如果任务结束执行则返回 null System.out.println("future.get()=" + future.get());

 

3.executor.submit(new Callable()

Future对象,传递的参数为Callable对象,new Callable()可以返回数据信息

Future future = executor.submit(new Callable(){ public Object call() throws Exception { return "Callable Result"; } }); System.out.println("future.get() = " + future.get()); //上述样例代码会输出如下结果: //future.get() = Callable Result

execute()内部实现

1.首次通过workCountof()获知当前线程池中的线程数,如果小于corePoolSize, 就通过addWorker()创建线程并执行该任务;否则,将该任务放入阻塞队列;

2. 如果能成功将任务放入阻塞队列中,  如果当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject()处理该任务;

如果当前线程池处于RUNNING状态,则需要再次检查线程池(因为可能在上次检查后,有线程资源被释放),是否有空闲的线程;如果有则执行该任务;

3、如果不能将任务放入阻塞队列中,说明阻塞队列已满;那么将通过addWoker()尝试创建一个新的线程去执行这个任务;

如果addWoker()执行失败,说明线程池中线程数达到maxPoolSize,则执行reject()处理任务;

 sumbit()内部实现

1.将提交的Callable任务会被封装成了FutureTask对象

2.FutureTask类实现了Runnable接口,通过Executor.execute()提交FutureTask到线程池,最终执行FutureTask的run方法

比较:两个方法都可以向线程池提交任务

  • execute()方法的返回类型是void,它定义在Executor接口中
  • submit()方法可返回持有计算结果的Future对象;扩展了Executor接口,定义在ExecutorService接口中,

其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。 


线程池的关闭(2种) shutdown()和shutdownNow()

  shutdown():不会立即终止线程池,要等所有任务缓存队列中的任务都执行完后才终止,不会接受新的任务

  shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并清空任务缓存队列,返回尚未执行的任务


 线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小:setCorePoolSize() 和 setMaximumPoolSize()

Java线程池使用有界队列实现时,饱和策略有哪些?

线程池会将提交的任务先置于工作队列

无界队列不存在饱和的问题,但是其问题是当请求持续高负载的话,任务会无脑的加入工作队列,那么很可能导致内存等资源溢出或者耗尽

有界队列不会带来高负载导致的内存耗尽的问题,但是有引发工作队列已满情况下,新提交的任务如何管理的难题,这就是线程池工作队列饱和策略要

解决的问题

当工作队列满了,不同策略的处理方式为:

1.Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。

2.CallerRuns策略:在调用者的线程中运行新的任务,既不抛弃任务也不抛出异常。

3.Discard策略: 新任务被抛弃。

4.DiscardOldest策略:旧任务被抛弃。(不适合工作队列为优先队列场景,否则优先级高的会被抛弃)

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值