1.CPU、缓存、内存
cpu组成:寄存器(暂时存放参与运算的数据和运算结果)、控制单元(指令寄存器、程序计数器)、逻辑运算单元
缓存:为了解决CPU和主存速度差异而产生的,分为3级缓存,位于CPU中,1级(静态RAM,很贵)2级(动态RAM,拓展的结果)是独立的,3级是多核共享的
内存:程序运行时,数据暂时存放的地方,一旦断电,数据会丢失
CPU操作时数据流动:数据会从内存中加载到L3中,再加载到核心独有的L2中,然后再加载到速度最快的L1中,最后被CPU寄存器读取
数据一致性:通过MESI协议实现,也就是当CPU写数据时,发现操作变量是共享变量,就会通知其他CPU将该变量的缓存置为无效,当其他CPU读取该变量发现缓存中的状态为无效,就会从主存中读取
2.多线程的作用
2.1.发挥多核CPU优势
多核CPU上的多线程才是真正的多线程,能让你的多段逻辑同时工作,多线程可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
2.2.防止阻塞
Java为多线程编程提供了内置支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
2.3.便于建模
将大任务分解为一个个小任务分别交由线程去做,再归集与进程,使用了更小的资源开销,线程必须依赖进程,是进程的一部分,进程会一直运行,直到所有非守护线程都运行结束后才能结束,下面是线程的生命周期
3.线程使用
3.1.创建线程
3.1.1.继承Theread
点击查看代码示例
public class MyThread extends Thread{
public class run(){
//业务逻辑
}
}
Thread t1 = new MyThread();
t1.start();
3.1.2.实现Runnable接口
点击查看代码示例
Public class MyRunnable implements Runnable{
Public class run(){
//业务逻辑
}
}
Runnable rb = new MyRunnable();
Thread t2 = new Thread()’
t2.start();
3.1.3.实现Callable接口
点击查看代码示例
import java.util.concurrent.*;
public class ThreadMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//结合Future
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
Future<Integer> future = executorService.submit(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
return 1;
}
});
System.out.println(future.get());
executorService.shutdown();
//结合FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
return 2;
}
});
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
3.1.4.比较
相较于继承来说,实现接口更灵活,减少程序之间的耦合度,面向接口也是设计模式6大原则的核心;
实现Runnable接口是直接执行run()中的逻辑,没有返回值,而实现Callable接口会返回一个泛型类型;
实现Callable接口不能单独使用,需要结合Futrue或者FutrueTask使用
3.2.Object的api
o.wait() :锁对象调用该方法使当前线程进入等待状态,并立刻释放锁对象,直到被其他线程唤醒进入等锁池。
o.wait(long) :锁对象调用该方法使当前线程进入等待状态,同时释放锁对象。但是超过等待的时间后线程会自动唤醒,或者被其他线程唤醒,并进入等锁池中。
o.wait(long,int) :和o.wait(long)方法一样,如果int参数大于0则前面的long数字加1
o.notify():随机唤醒一个处于等待中的线程(同一个等待阻塞池中)
o.notifyAll():唤醒所有等待中的线程(同一个等待阻塞池中)
3.3.Thread的api
Thread.currentThread():返回对当前线程对象的引用
Thread.interrupted():检测当前线程是否已经中断(调用该方法后后就将该线程的中断标志位设置位false,所以连续两次调用该方法第二次肯定时false)
Thread.sleep(long millis):使当前线程睡眠(不会释放锁对象,可以让其他线程有执行的机会)
Thread.yield():使当前线程放弃cpu的执行权(有可能立刻又被重新选中继续执行)
t.getId():返回该线程的id
t.getName():返回该线程的名字
t.getPriority():返回该线程的优先级
t.getState():返回该线程的状态
t.getThreadGroup():返回该线程所属的线程组
t.interrupt():将该线程中断(实际并不会中断,只是将中断标志设置为true),如果线程正处在sleep(),join(),wait()方法中时(也就是正在阻塞中)调用该方法,该方法会抛出异常。
t.interrupted():检测该线程是否已经中断(对中断标志位不作处理)
t.isAlive():检测该线程是否还活着
t.isDaemon():检测该线程是否为守护线程
t.isInterrupted():检测该线程是否已经中断
t.join():在a线程中调用b.join(),则a线程阻塞,直到b线程执行完
t.join(long millis):和上面的方法一样,不过a线程阻塞的时间根据long的大小有关,如果达到设定的阻塞时间,就算b线程没有执行完,a线程也会被唤醒。
3.4.start()与run()
start()是线程的正常启动方法,调用run()实际是调用同步方法,不会表现出多线程的特性
3.5.线程池
3.5.1.主要创建参数
corePoolSize: 核心线程数数量;线程池维护的最小线程数量,即使这些线程空闲也不会被销毁,除非设置了allowCoreThreadTimeOut;任务提交到线程池后,会检查当前线程数是否达到核心线程数,未达到则新建一个线程来处理任务
maxinumPoolSize:最大线程数;任务提交时会先找空闲的存活线程,没有就放到任务队列中,若队列满了就会新建一个线程处理队列的第一个任务,若队列满了,线程数也达到最大线程数,此时会调用拒绝策略;全线程任务情况下,可设置corePoolSize=maxinumPoolSize=2N+1,
keepAliveTime:多余的空闲线程存活时间;空闲线程存活时间达到该设置值时会被销毁,直到只剩下设置的核心线程数为止
threadFactory:线程工厂;用来创建线程,设定线程属性,比如名称,是否守护线程...
workQueue:任务队列;提交任务到线程池,会先放入任务队列,任务调度时再从队列中取出
ArrayBlockingQueue:有界数组阻塞队列,FIFO;必须初始化大小
LinkedBlockingQueue:无界链表阻塞队列,FIFO;大小默认使用Integer.MAX_VALUE,使用该队列maxinumPoolSize不起作用,当达到核心线程数后,任务会一直存入任务队列
SynchronousQueue:无缓存阻塞队列;不会保存提交的任务,会一直创建新线程执行任务,触发最大线程数就执行拒绝策略
PriorityBlockingQueue:无界有优先级阻塞队列;优先级通过参数Comparator实现
DelayQueue:无界延迟阻塞队列;底层通过PriorityBlockingQueue实现,等任务达到过期时间,才会出队
rejectedExecutionHandler:拒绝策略;队列满了,并且触发最大线程数时执行
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
DiscardPolicy:丢弃任务但是不跑出异常
DiscardOldestPolicy:丢弃任务队列最早的任务,把当前任务放入队尾
CallerRunsPolicy:由提交任务的线程处理该任务,若线程池已shutdown则直接丢弃
3.5.2.线程池种类及创建方式
new ThreadPoolExcutor():自定义创建,需要自己根据构造方法设置所有的创建参数
Executors.newFixedThreadPool(n):创建固定数量线程的线程池,corePoolSize=maxinumPoolSize=n,任务队列使用LinkedBlockingQueue拒绝策略使用AbortPolicy;用于已知并发压力下,限制线程数量,等待执行任务
Executors.newCachedThreadPool():创建缓存线程池,当线程数量超过任务数量,自动回收空闲线程,corePoolSize=0,maxinumPoolSize=Integer.MAX_VALUE,任务队列使用SynchronousQueue拒绝策略使用AbortPolicy;适合任务量小执行时间短的任务
Executors.newScheduledThreadPool(n):创建支持周期任务的线程池,corePoolSize=n,maxinumPoolSize=Integer.MAX_VALUE,任务队列使用DelayedWorkQueue,拒绝策略使用AbortPolicy;用于执行定时任务
Executors.newSingleThreadExecutor():创建一个单线程线程池,线程异常结束会创建一个新线程,corePoolSize=maxinumPoolSize=1,任务队列使用LinkedBlockingQueue拒绝策略使用AbortPolicy;可用于执行顺序任务场景,只有一个线程在执行
3.6.Future与FutureTask
3.6.1.Future
Future是一个泛型接口,用来获取异步计算结果,不能手动创建,api如下
V get():获取异步执行结果,如果没有结果,则会阻塞直到异步方法执行完成
V get(long timeout, TimeUnit unit):获取异步执行结果,如果没有结果,则方法阻塞至预设的时间,若仍然无结果则抛出异常
boolean cancel(boolean mayInterruptIfRunning):参数表示是否中断执行中的线程,任务未开始执行返回false,任务执行完执行返回false;任务已启动执行cancel(true)将中断任务线程试图停止任务,停止成功返回true;任务已启动执行cancel(false)不会影响任务线程(线程会执行完),返回false;
boolean isCancelled():若任务完成前被取消返回true
boolean isDone():无论是正常结束还是异常结束,都会返回true,不会造成线程阻塞
点击查看代码示例
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
ArrayList<Future> futures = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
int a = i;
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(10 / a * 1000);
System.out.println("Thread " + a + " is running");
return a;
}
});
futures.add(future);
}
for (Future future : futures) {//阻塞主线程,等待子线程执行结束获取执行结果
System.out.println(future.get());
}
System.out.println("main is running");
executorService.shutdown();
}
3.6.2.FutureTask
既实现Future接口又实现Runnable接口,故既可以使用Thread方式启动,又可以交给执行器执行
点击查看代码示例
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 2;
}
});
new Thread(futureTask).start();
4.多线程辅助工具CyclicBarrier与CountDownLatch
4.1.作用
都是用来表示代码运行到某个点上
4.2.区别
4.3.api
4.3.1.CountDownLatch
CountDownLatch(int count):构造方法,coun为计数值,最小值为0
void await():挂起当前线程,直到count为0时才会执行,一般用在主线程中
bookean await(long timeout, TimeUnit unit):阻塞线程,直到count为0时才会执行,参数为阻塞时间
void countDown():与await配合使用,将count值-1,用在子线程中,该方法后的有逻辑会出现线程顺序混乱
long getCount():获取已执行的数量,为保证准确需在countDown方法后执行
4.3.2.CyclicBarrier
CyclicBarrier(int parties):构造方法,parties指让多少线程等待至barrier状态
CyclicBarrier(int parties, Runnable barrierAction):构造方法,barrierAction为当线程都达到状态时执行的内容,会挂起
int await():挂起当前线程,直到所有线程都达到barrier状态再同时执行后续任务
int await(long timeout, TimeUnit unit):挂起当前线程一段时间,若还有线程未达到barrier状态则直接让已达到的线程执行后续任务
int getNumberWaiting():获取当前等待线程的数量
int getParties():获取设置需要等待的线程数
int isBroken():查询屏障是否损坏,
一个线程抛异常对其他线程无影响,对屏障无影响;
一个线程被中断,全部线程都抛出异常,屏障被破坏
void reset():重置屏障
4.4.使用演示
4.4.1.CountDownLatch
点击查看代码示例
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(finalI * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
long count = latch.getCount();
System.out.println(String.format("线程%d执行,计数器:%d", finalI, count));
}
}).start();
}
latch.await();
System.out.println("main is running");
}
4.4.2.CyclicBarrier
点击查看代码示例
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
final boolean[] flag = {false};
CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
if (flag[0]){
System.out.println("任务结束");
}else {
System.out.println("开始");
flag[0] = true;
}
}
});
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("id:" + finalI + "已准备");
try {
barrier.await();//第一处屏障
//TO DO任务处理处理
System.out.println("线程" + finalI + "已完成");
barrier.await();//第二处屏障
} catch (Exception e) {}
}
}).start();
}
System.out.println("准备好了吗");
}
5.Volatile(变量修饰符)
5.1.内存模型相关概念
程序的运行其底层都是指令的执行,将需要执行的数据从主存中复制一份到CPU中,计算结束后再将计算结果刷新到主存中,共享变量在多个CPU中都有缓存就会造成不一致现象
并发编程中会遇到三个问题:原子性问题(多个步骤一致性)、可见性问题(共享数据)、有序性问题(程序的执行顺序)
语义(指令)重排序:处理器为提高程序运行效率对输入代码优化(按照数据间的依赖性),不保证执行顺序与代码顺序一致,只保证最终结果与顺序执行结果一致
Java内存模型:每个线程都有自己的工作内存,所有操作都必须在自己的工作内存中进行,不能直接操作主存,并且不能访问其他线程的工作内存
5.2.Volatile作用
不能保证变量操作的原子性
不能保证操作的有序性
保证变量在多线程之前的可见性:每个线程都可对volatile修饰的变量的修改都会立即被更新到主存,可被其他线程看到
禁止语义重排序:线程修改volatile修饰的变量会造成其他线程对该变量的缓存无效,其他线程再读取该变量时必须从主存中读取
6.线程安全
6.1.定义
程序在多线程环境下的执行结果与在单线程下的执行结果一致,我们称为是线程安全的
6.2.内存模型
内存可分为工作内存和主存,程序运行时先将所需要的数据从主存中读取到工作内存中,等操作完成后再更新到主存。在多线程环境下,每个线程都有自己的工作内存,相互之间是隔离的
6.3.线程安全级别
不可变:类似使用final修饰的变量,创建之初就已经初始化了,在执行中无法修改值
绝对线程安全:任何运行环境下,都不需要额外的同步措施,JAVA中很多都不是线程安全的类,当然也有,比如CopyOnWriteArrayList、CopyOnWriteArraySet
相对线程安全:是我们通常意义上说的线程安全,能够保证原子操作,比如Vector的add和remove操作
线程不安全:不满足线程安全条件,比如ArrayList、LinkedList、HashMap等都是线程非安全的类
6.4.解决方案
若想保证线程安全,需要使用锁机制
7.锁
7.1.自旋锁
7.1.1.定义及优缺点
线程在获取锁时,该锁已被占用,那么程序将循环判断锁的状态,直到成功拿到锁才会退出循环,这一过程不会造成当前线程挂起,线程也不会失去CPU时间片
优点:因为线程不会挂起,所以节省了线程来回切换的开销
缺点:线程在未获得锁的情况下会一直循环,容易造成CPU资源的浪费,解决办法是加限制阈值;若程序循环的情况下进行了自调用,容易造成死锁;
7.1.2.案例
点击查看代码示例
public class SpinLock implements Lock {
private AtomicReference<Thread> owner = new AtomicReference<>();
private int count = 0;
@Override
public void lock() {
Thread t = Thread.currentThread();
if (t == owner.get()) {
++count;
return;
}
while (owner.compareAndSet(null, t)) {
}
}
@Override
public void unlock() {
Thread t = Thread.currentThread();
if (t == owner.get()) {
if (count > 0) {
--count;
} else {
owner.set(null);
}
}
}
}
7.2.悲观锁
7.2.1.定义
认为竞争总是会发生,对线程安全保持悲观态度,每次对竞争资源操作时都会持有独占的锁,synchronized也是悲观锁,不管任何情况下直接上锁,但是synchronized不支持分布式只能对本地有效
7.3.乐观锁
7.3.1.定义
认为竞争不会总是发生对线程安全保持乐观态度,操作共享资源时会先比较再操作,将”比较-设置“作为一个原子操作,CAS就是乐观锁
7.4.CAS
7.4.1.定义
全称为Compare and Set,也就是比较-设置,假设有内存值V, 预期值A和更新值B,只有当A=V是才会将内存值修改为B,并返回true,否则返回false,根据业务需要判断是否终止业务或者循环重试;CAS必须结合volatile变量使用,只有这样才能实时的拿到内存中的值,否则就只是线程内部操作,容易引发线程安全。
7.4.2.相关类
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解决ABA问题的原子类:AtomicMarkableReference(通过引入一个boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个int来累加来反映中间有没有变过)
ABA的影响:对于单个变量的值更新没有影响,但是对于变量之间的交换有影响,例如:栈 STACK中由B A,线程1读取后想将栈顶换成B,但是被线程2中断了,线程2将STACK中的元素换成了C D A,然后线程1执行,比较发现栈顶都是A,就直接将栈顶换成B了。
7.5.AQS
7.5.1.定义
全称AbstractQueuedSynchronizer,也就是抽象队列同步器,定义了一套多线程访问共享资源的同步器框架;AQS维护了一个共享资源state(volatile修饰)和一个线程等待队列,当线程竞争资源阻塞时进入等待队列;AQS定义了两种资源共享方式,一种是独占(Exclusive, 同时只能有一个线程执行)另一种是共享(Share,多个线程可同时执行)
7.5.2.state状态
AQS对于state的操作是原子的,不能被继承,接口如下
getState():获取状态值
setState():设置状态值
compareAndSetState():比较并设置状态值
7.5.3.资源共享api
队列维护AQS顶层已经实现完成,自定义同步器实现时只需要实现共享资源的获取和释放方式,涉及方法如下
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
7.5.4.案例
点击查看代码示例
// 自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 判断是否锁定状态
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 尝试获取资源,立即返回。成功则返回true,否则false。
public boolean tryAcquire(int acquires) {
assert acquires == 1; // 这里限定只能为1
if (compareAndSetState(0, 1)) {//state为0才设置为1,不可重入!
setExclusiveOwnerThread(Thread.currentThread());//设置为当前线程独占资源
return true;
}
return false;
}
// 尝试释放资源,立即返回。成功则为true,否则false。
protected boolean tryRelease(int releases) {
assert releases == 1; // 限定为1个量
if (getState() == 0)//既然来释放,那肯定就是已占有状态了。只是为了保险,多层判断!
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);//释放资源,放弃占有状态
return true;
}
}
7.6.Condition的使用
7.6.1.介绍
condition是一个接口(JDK5),与ReentrantLock结合使用可以实现线程之间的另外一种通讯方式,类似与synchronized与notify或notifyAll结合实现线程的等待/通知模式; 可以实现多路通知(也就是在一个Lock对象里可以创建多个condition实例,线程对象可以注册在指定的condition中从而选择性的进行线程通知),在线程调度上更加灵活。
7.6.2.创建
Condition的创建依赖于Lock的newCondition()方法, Lock也是一个接口,通常使用其字类对象创建condition
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
7.6.3.Condition的api
Condition的主要方法是await()和signal(), 调用必须在lock的保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
await():线程阻塞,会释放当前持有的锁,生成线程等待node,存储到condition的单链表中,被唤醒后再加入到锁的等待队列
signal():线程唤醒,对应Object的notify()
signalAll():线程唤醒,对应Object的notifyAll()
7.6.4.案例
点击查看代码示例
class ConditionTest {
public static ReentrantLock lock = new ReentrantLock();
public static Condition conditionA = lock.newCondition();
public static Condition conditionB = lock.newCondition();
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
lock.lock();//请求锁
try {
System.out.println(Thread.currentThread().getName() + "==》进入等待");
conditionA.await();
System.out.println(Thread.currentThread().getName() + "==》休息结束");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}.start();
new Thread() {
@Override
public void run() {
lock.lock();//请求锁
try {
System.out.println(Thread.currentThread().getName() + "==》进入等待");
conditionB.await();
System.out.println(Thread.currentThread().getName() + "==》休息结束");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}.start();
try {
Thread.sleep(5000);//保证唤醒调用晚于await()执行
lock.lock();
System.out.println(Thread.currentThread().getName() + "==》唤醒成员");
conditionA.signalAll();//唤醒A对象管理的所有线程
conditionB.signalAll();//唤醒B对象管理的所有线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
浙公网安备 33010602011771号