多线程
线程基本概念
线程概念
- 作为进程里面最小的执行单元它就叫做线程,一个进程的不同执行路径就叫做线程。是CPU时间片调度的最小单位。
启动方式
- new Thread().start();(继承Thread)
- new Thread(Runnable).start();(实现Runnable接口)
- new Thread(new FutureTask<>(Callable)).start();(使用Callable和Future创建线程)
常用方法
-
sleep(long millis)
- 线程休眠,当前线程暂停一段时间让给别的线程去执行
-
yield()
- 当前线程正在执行的时候停止下来进入等待队列
-
join()
- t1、t2两个线程,t1中执行t2.join(),t2则会被切换到执行,然后t1就等待t2运行完后再运行t1
线程状态
-
New新创建状态
- 线程被创建,还没有被调用start方法,在线程运行之前还有一些基础工作要做
-
Runable可运行状态
- 一旦调用start方法,线程就处于Runnable状态,一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间
-
Blocked阻塞状态
- 表示线程被锁阻塞,它暂时不参与活动
-
Waiting等待状态
- 线程暂时不参与活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器重新激活它
-
Timed waiting超时等待状态
- 和等待状态不同的是,它是可以在指定的时间自行返回的
-
Terminated终止状态
- 表示当前线程已经执行完毕。导致线程终止有两种情况:1.run方法执行完毕正常退出;2.因为一个没有捕获的异常而终止了run方法。
-
状态间的转换(生命周期)
synchronized
synchronized(Object)不能使用String常量、Integer、Long等
-
线程同步
锁的是对象不是代码
-
锁升级
- 偏向锁(记录这个线程的ID)
- 自旋锁(如果线程争用,就升级为自旋锁<线程数量少>)
- 重量级锁10次(线程数量多)
-
同步方法
-
一个方法使用synchronized关键字声明
public synchronized void method(){}
-
-
同步代码块
-
代码块使用synchronized关键字修饰
synchronized(Object){}
-
volatile
-
保证线程的可见性
- 线程的可见性在CPU的级别是用缓存一致性来保证的
-
保证指令的有序性(禁止指令重新排序)
- 禁止指令重排序CPU级别是禁止不了的,那是内部运行的过程,提高效率的。在虚拟机级别加了volatile后,这个指令重排续可以禁止。
- volatile关键自禁止指令重排的两个含义:
1.当程序执行到volatile变量操作时,在其前面的操作已经全部执行完毕,并且结果会对后面的操作可见,在其后面的操作还没有进行
2.在进行指令优化时,在volatile变量之前的语句不能在volatile变量后面执行
-
java中的原子性、可见性、有序性
-
原子性:对于基本数据类型变量的读取和赋值操作是原子性操作,即这些操作是不可被中断。(操作已经是最小单位了,不可再拆分)(volatile不保证原子性)
-
上面3个语句中只有语句1是原子性操作。x = 3 ; //语句1(将3写入工作内存) y = x ; //语句2(先读取x的值,再将x的值写入工作内存) x++; //语句3(先读取x的值,对x的值进行加1,再向工作内存写入新值)
- java.util.concurrent.atomic包中有很多类使用了高效的机器级指令(不是锁)来保证其他操作的原子性。如AtomicInteger类提供了以原子方式将一个证书自增或自减的方法incrementAndGet和decrementAndGet
-
可见性:指线程之间的可见性,一个线程修改的状态对另外一个线程是可见的。被volatile修饰的共享变量在修改后会立马更新到主存,而普通的共享变量则不能保证立马被更新到主存。
有序性:java内存模型中允许编译器和处理器对指令进行重排序,重拍序不会影响到单线程执行的正确性,会影响到多线程并发执行的正确性。(除了volatile外,synchronized和Lock都可以保证有序性;不过后面两个是通过同时只有一个线程执行同步代码来保证的)
-
-
volatile的使用
通常来说使用volatile必须具备以下两个条件(说白了就是需要具备原子性):
1.对变量的写操作不会依赖当前值
2.该变量没有包含在具有其他变量的不变式中-
常用场景一:状态标志
volatile boolean statusTag;
-
常用场景二:双重检查模式(DCL)
public class Singleton { private Singleton() { } private volatile static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
可重入锁
- 可重入锁就是在同一线程中,外层函数获取了锁之后,内层函数依然可以获得相同的锁。synchronized、ReentrantLock都是可重入锁
重入锁与条件对象(ReentrantLock、Condition)
- ReentrantLock是Java SE5.0引入的,支持重进入的锁,它表示该锁能够支持一个线程资源的重复加锁。
Lock mLock = new ReentrantLock(); mLock.lock(); try{ ... }finally{ mLock.unlock; } //需要注意这里是在finally里进行锁的释放,因为防止在临界区内发生异常,后没有释放导致其他线程的永远堵塞。
- 锁的条件对象Condition,一个锁对象拥有多个相关的条件对象,可以用newCondition方法获取一个条件对象,得到条件对象后调用await方法,当前线程就被阻塞了并放弃了锁。一旦调用了await方法,它就会进入该条件的等待集并处于阻塞状态,直到另一个线程调用了同一个条件的signalAll/signal方法时为止,就会解除了因为这一条件而等待的所有线程/某一线程的阻塞,之后这些线程能够在当前线程退出同步方法后,通过竞争实现对对象的访问。(Condition condition = new ReentrantLock().newCondition();//获取Condition对象)
java内存模型
-
java内存模型的抽象示意图
java中的堆内存采用存储对象实例,堆内存是被所有线程共享的运行时的内存区域
本地内存是java内存模型的一个抽象概念,并不是真是存在,它涵盖了缓存、写缓冲区、寄存器等区域
-
java内存模型控制线程之间的通信,它决定一个线程对主存共享变量的写入合适对另外一个线程可见。线程A与线程B之间若要通信,则必须经历下面两个步骤:
1.线程A把线程A本地内存中更新过的共享变量刷新到主存中去。
2.线程B到主存中去读取线程A之前已更新过的共享变量。
-
线程执行互斥代码的过程
- 获得互斥锁
- 清空工作内存
- 从主内存拷贝变量的最新副本到工作内存
- 执行代码
- 将更改后的共享变量的值刷新到主内存
- 释放互斥锁
-
volatile可见性
-
写操作
- 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
- 改变线程工作内存中的volatile变量副本的值
- 将改变后的副本的值从工作内存刷新到主内存
-
读操作
- 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
- 从主内存中读取volatile变量的最新值到线程的工作内存中
- 从工作内存中读取volatile变量的副本
-
线程池
在java1.5中提供了Executor框架用于把任务的提交和执行解藕,任务的提交交给Runnable或者Callable,而Executor框架用来处理任务。Executor中最核心的成员就是ThreadPoolExecutor,它是线程池的核心是实现类。
ThreadPoolExecutor构造方法中的参数
- corePoolSize:核心线程数。默认情况下线程池是空的,只有任务提交是才会创建线程。如果当前线程数少于corePoolSize,则创建新的线程来处理任务;如果当前运行的线程数等于或多于corePoolSize则不在创建信的线程。如果调用线程池的prestartAllcoreThread方法,则线程池会提前创建并启动所有的核心线程来等待任务
- maxmunPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数少于maxmunPoolSize时,则线程池仍旧会创建信的线程来处理任务。
- keepAliveTime:非核心线程闲置的超时时间。超过这个时间,非核心线程就会被回收。如果任务很多,并且每个任务的执行时间很短,则可以调大keepAliveTime来提高线程的利用率。另外,如果设置allowCoreThreadTimeOut属性为true,keepAliveTime也会应用到核心线程
- TimeUnit:keepAliveTime参数额时间单位。可选择的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、秒(SECONDS)、毫秒(MILLISECONDS)等
- workQueue:任务队列。如果当前线程数大于corePoolSize,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,就是阻塞队列。
- ThreadFactory:线程工厂。可以用线程工厂给每个创建出来的线程设置名字。一般情况下无须设置该参数。
- RejectedExecutionHandler:饱和策略。这是当任务队列和线程池都满了时所采取的应对策略,默认是AbortPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。另外还有三种异常:
1.CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
2.DiscardPolicy:不能执行的任务,并将该任务删除。
3.DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。
线程池的处理流程图
线程池的执行示意图
线程池的种类
同过直接或间接地配置ThreadPoolExecutor的参数可以创建不同类型的ThreadPoolExecutor,其中有4种比较常用。
-
FixedThreadPool:可重用固定线程池
- Executor类种提供了创建FixedThreadPool的方法:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- FixedThreadPool只有核心线程,并且数量固定,没有非核心线程,任务队列采用了无界阻塞队列LinkedBlockingQueue。
- Executor类种提供了创建FixedThreadPool的方法:
-
CachedThreadPool:是一个根据需要创建线程的线程池
- 创建方法:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
- corePoolSize为0,maximumPoolSize设置为integer.MAX_VALUE,CacheThreadPool没有核心线程,非核心线程是无界的,空闲等待为60s,使用了SynchronousQueue阻塞队列(不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,任何一个移除操作都等待另一个线程的插入操作)。CacheThreadPool比较适合于大量的需要立即处理并且耗时比较少的任务。
- 创建方法:
-
SingleThreadExecutor:是使用单个工作线程的线程池
- 创建方法:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
- SingleThreadExecutor能确保所有的任务在一个线程中按照顺序逐一执行
- 创建方法:
-
ScheduledThreadPool:是一个能实现定时和周期性任务的线程池
- 创建方法:
//class Executors public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
//class ScheduledThreadPoolExecutor public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
- 主要用于在给定延时之后运行任务或者定期处理任务
- 创建方法: