多线程:程序中包含多个执行流,同时运行多个不同的线程执行不同的任务。
好处:提高CPU利用率,一个线程必须等待时,CPU可以运行其他线程,允许创建多个并行执行的线程完成各自任务。
劣势:线程需要占用更多的内存,CPU上下文切换(多线程协调和管理)需要时间跟踪线程,各线程对共享资源的访问相互影响(竞争资源问题)。
内存泄漏、上下文切换、线程安全、死锁
原子性:一个或多个操作要么全部成功要么全部失败。
可见性:一个线程对共享变量的修改,其他县城也能立刻看见。
有序性:程序执行的顺序按照代买的先后顺序执行。
并发:多个任务在同一个CPU核上,按细分的时间片轮流(交替执行/上下文切换),逻辑上是同时执行
并行:单位时间内,多个处理器或多核处理器同时执行多个任务,真正的同时执行。
串行:n个任务,一个线程按顺序执行。
进程:一个在内存中运行的程序。每个进程都有独立的内存空间,一个进程可以有多个线程
操作系统资源分配的基本单位。
进程与进程之间独立,访问其他进程资源开销较大,保护模式下进程崩溃不会影响其他进程。
线程:进程中的一个执行任务(控制单元),负责进程中程序的执行。多个线程可共享数据。
处理器任务调度和执行的基本单位。
线程切换开销较小,一个线程崩溃整个进程撕掉。
Windows上可以使用任务管理器,Linux上可以用top这个工具看。
也可以使用arthas,通过线程日志,直接找到对应的代码块。
创建Thread类的子类,重写run方法,方法中是线程具体要执行的业务,通过start方法启动线程
public class One extends Thread {
private String name;
public One(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
for (int i = 0; i < 10; i++) {
System.out.println("i:"+i+";"+name);
try {
sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
One one1 = new One("A");
One one2 = new One("B");
one1.start();
one2.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
}
public class Two implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
}
public static void main(String[] args) {
Two two = new Two();
Thread thread = new Thread(two);
thread.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
}
}
相当于实例化了了Runnable接口中的run方法,然后放到Thread对象中执行生成一个线程
public static void main(String[] args) {
//匿名内部类创建线程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i+";线程A");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i+";线程B");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
});
thread1.start();
thread2.start();
}
相当于匿名内部类的简化写法
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i+";线程A");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i+";线程B");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
配合FutureTask使用,用于需要有返回结果的执行方法同步非阻塞
public class Five implements Callable {
private String name;
public Five(String name) {
this.name = name;
}
@Override
public Object call() throws Exception {
int i;
for (i = 0; i < 10; i++) {
System.out.println(i + ";线程A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask1 = new FutureTask(new Five("A"));
FutureTask futureTask2 = new FutureTask(new Five("B"));
Thread thread1 = new Thread(futureTask1);
Thread thread2 = new Thread(futureTask2);
thread1.start();
thread2.start();
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
}
}
Future接口表示异步任务,用于获取结果。
FutureTask表示一个异步运算的任务,传入一个Callable的实现类,可以对这个异步运算的任务结果进行获取、判断是否已完成、取消任务等操作。
使用 Executors 工具类创建线程池
Executors提供了一系列工厂方法创建线程池,返回的线程池都实现了ExecutorService接口。
run:线程体,可以重复调用,只是线程中的一个函数,直接调用run()必须等待run()方法中的内容执行完毕。
start:启动线程,并使线程进入就绪状态。只能调用一次,无序等待run方法执行完毕,可以直接开启新的线程。
1、创建线程(new):新创建了一个线程对象。
Thread t = new Thread();
2、可运行(runnable):对象创建后,调用start方法,此时处于就绪状态,等待被线程调度选中,获取CPU的使用权。
3、运行(running):可运行的线程获取到了cpu时间片,执行程序。
4、阻塞(block):运行状态中,暂时放弃对CPU的使用权,直到其再次进入就绪状态,才有机会被CPU调用。
5、死亡(dead):线程run()、main()方法执行结束,或者因为异常退出了run方法,线程生命周期结束。
分时调度模型:所有线程轮流获得CPU使用权,平均分配每个线程占用的时间片。
抢占式调度模型:优先让可运行池中优先级高的线程占用CPU,优先级相同就随机选择。处于运行状态的线程会一直执行,直到其自己放弃CPU。
wait():使线程处于等待(阻塞)状态,释放所持有的对象锁。属于Object类,一般用于线程间通讯,需要notify()或notifyAll()方法唤醒。一般在循环中使用,在循环中检查等待条件。
synchronized (monitor) {
// 判断条件谓词是否得到满足
while(!locked) {
// 等待唤醒
monitor.wait();
}
// 处理其他的业务逻辑
}
sleep():使线程休眠,静态方法。不会释放锁。属于Three类,一般用于暂停执行,时间结束会自己恢复成就绪状态
notify():唤醒处于等待的线程,具体唤醒哪一个线程由虚拟机控制。
notifyAll():唤醒所有处于等待的线程。继续去进行锁竞争,竞争失败则留在锁池等待锁被释放再次参与竞争。
Lock lock = new ReentrantLock();
lock. lock();
try {
System. out. println("获得锁");
} catch (Exception e) {
// TODO: handle exception
} finally {
System. out. println("释放锁");
lock. unlock();
}
控制代码块不被多个线程同时执行,可以修饰类、方法、变量、代码块。
JDK1.6之前,synchronized属于重量级锁,监视器锁(monitor)依赖于底层操作系统,Java线程是映射到操作系统的原生线程上的。如果挂起或者唤醒一个线程,需要操作系统完成,需要从用户态切换到内核态,时间成本较大。
JDK1.6之后,对锁的实现进行了大量的优化,自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等。
修饰实例方法:当前实例对象加锁。
修饰静态方法:给当前类加锁,作用于类的所有对象实例。静态成员不属于任何一个实例(static声明该类的一个静态资源,不过new了多少个对象,只有这一份)。
修饰代码块:指定加锁对象,进入代码库前,需要获得指定对象锁。
尽量不要使用字符串类型作为锁,因为JVM中,字符串常量池具有缓存功能。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance = new Singleton()的执行步骤
多线程环境下,JVM可能进行指令重排,所以需要用volatile修饰,禁止JVM的指令重排。
Java对象头:对象在内存中的存储布局分为三个区域
对象头主要包括两部分:
synchronized使用的锁对象,存储在Java对象头的标记字段里面。
Monitor监视器:
保证每次只能有一个线程进入被保护的数据,进入房间持有Monitor,退出房间释放Monitor。
synchronized加锁的同步代码块在字节码引擎中执行时,主要是通过锁对象的monitor的取用(monitor)和释放(monitorexit)实现的。有两个monitorexit是为了保证线程异常退出,锁也能被释放,防止死锁
Monitor上的线程流转:对象监视器会设置几种状态区分请求的线程:
锁解决了数据的安全性,但是也带来了性能的下降,调查发现加锁的代码总是有一个线程多次获得。
基于这个问题,JDK1.6后,为减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁,锁的状态从低到高不断升级。
无锁:没有对资源进行锁定,所有线程均能访问,但仅有一个可以修改成功。
偏向锁:偏向于第一个获得它的线程,如果接下来该锁没有被其他线程获取,则持有该锁的线程永远不需要同步。
轻量级锁:锁位偏向锁时,被其他线程访问,升级为该锁,其他线程会通过自旋的形式尝试获取,不会阻塞(提高性能)
重量级锁:原始的synchronized的实现。其他线程视图获取锁时,都会被阻塞,只有持有锁的线程释放锁,其他线程才会被唤醒。
重入锁:一个线程获取到锁之后,该线程还可以继续获得该锁。底层原理:维护一个计数器,获得锁时+1,再次获得再+1,释放时-1,当计数器为0时,表示没有被任何线程占用,可以竞争。
自旋:等待的锁不被阻塞,在边界做循环尝试获取锁。(线程阻塞涉及用户态和内核态的切换)
作用:保证可见性和禁止指令重排。确保一个线程的修改能对其他线程的可见性。
一个共享变量被修改,确保其立即被更新到主内存,其他线程可以立即读取到它的新值。
常用于多线程环境下的单次操作(读或写)
相比较于同步方法和同步块,Lock接口提供了更具扩展性的锁操作。允许更灵活的结构,可以具有完全不同的性质。
优势:
synchronized的扩展版,提供了无条件的、可轮询的、定时的、可中断的、可多条件队列的锁操作。
实现类基本都支持非公平锁和公平锁,synchronized仅支持非公平锁。
悲观锁:总是假设最坏的情况,每次拿数据都认为会被其他人修改,因此每次拿数据的时候都上锁。(行锁、表锁、读锁、写锁、synchronized)
乐观锁:每次拿数据都认为不会有人修改,不会上锁,但是更新时会去判断在此期间别人有没有更新,可以使用版本号机制。适用于多读的应用类型,提高吞吐量。
乐观锁的实现方式:
compare and swap,即交换。
基于乐观锁,CAS操作中包含三个操作数:需要读写的内存位置(V),进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置的V的值与A匹配,处理器自动将该位置更新为B值,否则不做处理。
通过无限循环来获取数据,如果第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能有机会执行。
ABA问题:线程1从内存位置取出A,线程2也取出了A并将其改成B,然后线程2又将V的数据变成A,此时线程1会发现内存中仍是A,然后线程1操作成功。但可能存在隐藏问题。
循环时间长开销大:资源竞争严重的情况,CAS自旋的概率较大,浪费过多CPU资源。
只能保证一个共享变量的原子操作:多个共享变量操作时,循环CAS无法保证操作的原子性,需要用到锁。
概念:线程A持有锁a,尝试去获取锁b时,线程B持有锁b,且尝试获取锁a,两个线程互相持有对方需要的锁,发生阻塞。
产生死锁的条件:
只要破坏了其中一个条件,死锁则破除。
防止死锁的方法:
抽象类 ,在java.util.concurrent.locks包下面。
作用:一个用来构建锁和同步器的框架,比如ReentrantLock、ReentrantReadWriteLock,SynchronousQueue,FutureTask等,还可以自定义同步器。
原理:如果被请求的共享资源空闲,将当前请求资源的线程设置为有效工作线程,且将共享资源设置为锁定状态。如果被请求的资源被占用,就需要一套线程阻塞等待以及被唤醒时锁分配的机制。
CLH队列锁实现。将暂时获取不到锁的线程加入到队列中。
CLH是一个虚拟的双向队列,AQS将每条请求共享资源的线程封装成一个CLH锁队列的节点(Node)来实现锁的分配。
定义了两种资源共享方式:
独占:只要一个线程能执行,又分为公平锁和非公平锁。
共享:多个线程可同时执行。
//返回同步状态的当前值
protected final int getState() {
return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
概念:Lock的实现类,支持重入性,即可以给共享资源重复加锁,当前线程再次获取该锁不会被阻塞。
synchronized锁升级的偏向锁也支持重入,通过获取自增,释放自减实现重入。
重入性的实现原理:
作用:如果使用 ReentrantLock,多个线程读取时也进行了加锁,降低程序性能。而ReentrantReadWriteLock是一个读写锁,提升性能的锁分离技术,本身也是ReentrantLock的子类。
实现了读写的分离,读锁共享,写锁独占,提升了读写的性能。
特性:
作用:减少每次获取资源的消耗,提高资源的利用率。
本质:先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程,而不需要自行创建;使用完也不需要销毁线程,而是放回池中,减少了创建、销毁小程对象的开销。
Executor接口的子类型即线程池接口ExecutorService提供了一些静态工厂方法生成一些常用线程池。
ThreadPoolExecutor 可以创建自定义线程池。
接受参数:execute()只能执行Runnable类型的任务。submit()可以执行Runnable和Callable类型的任务
返回值:submit()方法可以返回持有计算结果的Future对象。
异常处理:submit()方便Exception管理。
Executors 各个方法的弊端:
而ThreadPoolExecutor是通过构造函数自定义线程池(阿里巴巴开发规范中明确规范的线程池创建方式)
核心参数:
corePoolSize
:核心线程数,定义了最小可同时运行的线程数maximumPoolSize
:线程池中可运行存在的工作线程的最大数量workQueue
:新任务来的时候,先判断当前运行的线程数是否达到核心线程数,达到则会放入队列中。其他参数:
keepAliveTime
:线程池中的参数大于核心线程数时,如果没有新任务提交,核心外的线程会等待超过keepAliveTime时间后,被销毁。unit
:keepAliveTime参数的时间单位。threadFactory
:为线程池提供创建新线程的线程工厂。handler
:线程池任务队列超过可运行的最大工作线程数量之后的拒绝策略。饱和策略:
ThreadPoolExecutor.AbortPolicy
:抛出RejectedExecutionException异常来拒绝新任务。ThreadPoolExecutor.CallerRunsPolicy
:调用执行自己的线程运行任务,不会扔掉任何一个任务。ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃。ThreadPoolExecutor.DiscardOldestPolicy
:丢弃最早的未处理的任务请求。线程池实现原理:
首先创建一个 Runnable
接口的实现类
public class MyRunnable implements Runnable {
private String command;
public MyRunnable(String command) {
this.command = command;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date() + ";command = " + command);
sleepTest();
System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date() + ";command = " + command);
}
private void sleepTest() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return this.command;
}
}
使用 ThreadPoolExecutor
构造函数自定义参数的方式来创建线程池。
public class ThreadPoolExecutorDemo {
//核心线程数
private static final int CORE_POOL_SIZE = 5;
//最大工作线程数
private static final int MAX_POOL_SIZE = 10;
//队列容量
private static final int QUEUE_CAPACITY = 100;
//非核心线程销毁等待时间
private static final Long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
//使用阿里巴巴推荐的创建线程池的方式
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,//核心线程数
MAX_POOL_SIZE,//最大工作线程数
KEEP_ALIVE_TIME,//非核心线程销毁等待时间
TimeUnit.SECONDS,//KEEP_ALIVE_TIME的时间单位
new ArrayBlockingQueue<>(QUEUE_CAPACITY),//队列容量
new ThreadPoolExecutor.CallerRunsPolicy());//淘汰策略
for (int i = 0; i < 10; i++) {
Runnable worker = new MyRunnable(""+i);
executor.execute(worker);
}
//终止线程池
executor.shutdown();
//死循环判断线程是否已关闭
while (!executor.isTerminated()) {
}
System.out.println("所有的子线程都结束了!");
}
}
原子操作:不可被中断的一个或一些列操作。多线程环境下避免数据不一致问题必须得手段。
处理器使用基于对缓存加锁或总线加锁的方式实现多处理器之间的原子操作。
Java中通过锁和循环CAS的方式实现原子操作
jdk1.5后推出了java.util.concurrent 包,提供了一组原子类。
基本特性:多线程环境下,多个线程同时执行这些类的方法时,具有排他性,即不会被其他线程打断,此时别的线程就像自旋锁一样,直到该方法执行完成,才由JVM从等待队列中选择另一个线程进入。
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个 boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个 int 来累加来反映中间有没有变过)
AtomicInteger 类的部分源码:
通过CAS+volatile和native方法保证原子操作,避免了synchronized的高开销,效率提升。
CAS的原理是将期望的值和原本的值进行比较,相同则更新成新的值。Unsafe类的objectFieldOffset方法是一个native方法,用来拿到“原来的值”的内存地址。value被volatile修饰,内存中可见,JVM可以保证任何时刻线程总能拿到改变量最新的值。
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;