> 文档中心 > 多线程与高并发

多线程与高并发


基本概念

1、进程、线程

进程:类似于一个简单的程序

线程:进程里面最小的执行单元,简单来说,线程就是一个程序里不同的执行路径

2、创建线程的方式

2.1、继承Thread类来创建

a.定义一个类继承Thread类,并重写run()方法,run()方法的方法体就是线程具体的实现逻辑,因此把run()方法称为线程的执行体

b.创建该类的实例对象

c.调用线程对象的start()方法启动线程

2.2、实现Runnable接口创建线程

a.定义一个类实现Runnable接口

b.创建该类的实例对象obj

c.将实例对象obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象

d.调用线程对象的start()方法启动线程

继承Thread和实现Runnable接口的区别:

a.实现Runnable接口避免多继承局限(Java只支出单继承,继承Thread类,就不能在继承其它类)b.实现Runnable接口可以更好的体现共享的概念(多个线程执行同一个线程执行类实例,实现数据的共享)

2.3、通过Callable和Future接口创建线程

Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法的功能的强大体现在:

a.call()方法可以有返回值

b.call()方法可以声明抛出异常

通过Callable和Future接口创建并启动线程的步骤:

a.创建Callable接口实现类,并实现call()方法,该方法作为线程执行体,且该方法有返回值,再创建Callable实现类的实例

b.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值

c.使用FutureTask对象作为Thread对象的target创建并启动线程

d.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

2.4、创建匿名线程

2.5、通过线程池启动多线程

a)newFixedThreadPool(int n); 启动一个固定大小的线程池

使用于为了满足资源管理需求而需要限制当前线程数量的场合。使用于负载比较重的服务器

b)newSingleThreadExecutor(): 启动一个单线程的线程池

需要保证顺序执行各个任务的场景

c)newCachedThreadPool(); 启动一个缓存的线程池

当提交任务速度高于线程池中任务处理速度时,缓存线程池会不断的创建线程 适用于提交短期的异步小程序,以及负载较轻的服务器

3、创建线程的几种方式对比

通过继承Thread类实现多线程:

优点: a.实现起来比较简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程;

缺点:

a.线程类已经继承Thread类,就不能再继承其他类

b.多个线程不能共享同一份资源

通过实现Runnable接口或者Callable接口实现多线程:

优点:

a.线程类只是实现了接口,还可以继承其他类

b.多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况

缺点:

a.通过这种方式实现多线程,相比与第一类方式,编程较复杂

b.要访问当前线程,必须调用Thread.currentThread()方法

注意:多数情况下使用线程池来创建线程

4、常见的线程状态

4.1、Ready就绪状

4.2、Running运行状态

4.3、Teminated结束状态

4.4、TimedWaiting等待

4.5、Waiting等待

4.6、Blocked阻塞

5、synchronized、volatile与CAS

5.1、synchronized(同步锁)

jdk早期,synchronized的底层实现是重量级的,需要到操作系统中去申请锁,效率非常低。

锁升级的概念: 在操作系统中,向内核申请锁,到后期进行了一些改进,HotSpot中的实现是这样的:第一个去访问某把锁的线程,比如sync(Object),先在这个Object的头上面markword记录这个线程。(如果只有第一个线程访问的时候实际上是没有给这个Object加锁的,在内部实现的时候,只是记录这个线程的ID(偏向锁))。

偏向锁如果有线程竞争,就升级为自旋锁。

当自旋锁转圈十次之后,将升级为重量级锁,重量级锁就是去操作系统那里去申请资源,这是一个锁升级的过程。

执行时间短(加锁代码),线程数少,用自旋

执行时间长,线程数多,用系统锁(重量级锁)

锁升级的四种状态:无锁、偏向锁、轻量级锁、重量级锁

5.2、volatile

作用:

1、保证线程的可见性

2、禁止指令重排序

synchronized和volatile知识回顾:synchronized锁的是对象而不是代码,锁方法锁的是this,锁static方法锁的是class,锁定方法和非锁定方法是可以同时执行的,锁升级从偏向锁到自旋锁到重量级锁volatile保证线程的可见性,同时防止指令重排序。线程可见性在CPU的级别是用缓存一致性来保证的;禁止指令重排序CPU级别是禁止不了的,是内部运行的过程,提高效率的。添加volatile之后,指令重排序就可以禁止。

5.3、CAS

CAS(compareAndSet) 无锁优化,或者叫自旋。

CAS会产生ABA问题

JUC同步工具

1、synchronized和ReentrantLock的不同?

synchronized:系统自带、系统自动加锁,自动解锁,不可以出现多个不同的等待队列,默认进行四种锁状态的升级

ReentrantLock:需要手动加锁,手动解锁,可以出现多个不同的等待队列,CAS的实现

线程池

线程池7大参数:

1、corePoolSize 核心线程数

2、maxinumPoolSize 最大线程数

3、keepAliveTime 生存时间

4、TimeUnit.SECOUNDS 生存时间的单位

5、BlockingQueue任务队列

6、defaultThreadFactory 线程工厂

7、拒绝策略,指的是线程池忙,并且在任务队列满的情况下,执行设置的拒绝策略,JDK默认提供了4种拒绝策略,也可以自定义

JDK默认提供的拒绝策略:

Abort:新建的任务抛异常

Discard:扔掉新建的任务,不抛异常

DiscardOldSet:扔掉排队时间最久的旧任务,尝试提交新的任务

CallerRuns:不抛弃新任务,也不抛异常,将新建的任务退回到调用者,由调用者自己执行

线程池的大小=CPU的数目*期望的CPU *(1+等待时间/计算时间)