并发编程三要素
- 原子性
- 有序性
- 可见性
守护线程和非守护线程
非守护线程运行结束,jvm退出,与守护线程是否存在无关。
守护线程通常用在作为垃圾收集器或缓存管理器的应用程序中,执行辅助任务
Callable 有返回值
package com.lagou.concurrent.demo;
import java.util.concurrent.*;
public class Main2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10) ) {
@Override protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
}
};
Future<String> future = executor.submit(new MyCallable());
String s = future.get(); System.out.println(s);
executor.shutdown();
}
}
synchronized关键字
给某个对象加锁
实例方法锁加在对象 myClass上;静态方法锁加在 MyClass.class
锁🔐的本质
锁是一个对象
-
这个对象内部得有一个标志位(state变量),记录自己有没有被某个线程用。
-
如果这个对象被某个线程占用,记录这个线程的thread ID。
-
这个对象维护一个thread id list,记录其他所有阻塞的、等待获取拿这个锁的线程。在当前线程释 放锁之后从这个thread id list里面取一个线程唤醒。
轻量级阻塞和重量级阻塞
能够被中断的阻塞称为轻量级阻塞,对应的线程状态是WAITING或者TIMED_WAITING;
而像 synchronized 这种不能被中断的阻塞称为重量级阻塞
Interrupted异常
只有那些声明了会抛出InterruptedException的函数才会抛出异常,
也就是下面这些常用的函数 sleep(),wait(),join()
thread.interrupted()的精确含义是唤醒轻量级阻塞,而不是字面意思“中断一个线程”。如果线程此时恰好处于WAITING 或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。
thread.isInterrupted()与Thread.interrupted()的区别
- thread.isInterrupted() 实例方法,不改变线程中断状态
- Thread.interrupted() 静态方法,改变线程中断状态,对中断状态取反
并发和并行
并发: 一个cpu核心执行多个任务
并行:不同核心上同时运行多个任务
happen-before
如果A happen-before B,意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性
CopyOnWrite
CopyOnWrite指在“写”的时候,不是直接“写”源数据,而是把数据拷贝一份进行修改,再通过悲观锁或 者乐观锁的方式写回。
为了在“读”的时候不加锁。
同步工具
Semaphore
信号量,提供了资源数量的并发访问控制
// 5份共享资源。第二个参数表示是否是公平
Semaphore myResources = new Semaphore(5, true);
//工作线程每获取一份资源,就在该对象上记下来
myResources.acquire();
// 工作线程每归还一份资源,就在该对象上记下来
myResources.release();
CountDownLatch
主线程要等待5个 Worker 线程执行完才能退出
CountDownLatch latch = new CountDownLatch(5);
// 子线程中任务完成
latch.countDown();
// 主线程等待
latch.await();
自旋与阻塞
阻塞:放弃CPU,进入阻塞状态,等待后续被唤醒,再重新被操作系统调度。 自旋:多核cpu,不放弃CPU,空转,不断重试,也就是所谓的“自旋”。
Lock与Condition
“可重入锁”是指当一个线程调用 object.lock()获取到锁,进入临界区后,再次调用object.lock(),仍然可 以获取到该锁。
ForkJoin
RecursiveAction 没有返回值
RecursiveTask<Long>,有返回值。
求1到n个数的和
继承RecursiveTask,重写compute()方法
public class ForkJoinPoolDemo02 {
static class SumTask extends RecursiveTask<Long> {
private static final int THRESHOLD = 10;
private long start;
private long end;
public SumTask(long n) { this(1, n); }
public SumTask(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
// 如果计算的范围在threshold之内,则直接进行计算
if ((end - start) <= THRESHOLD) {
for (long l = start; l <= end; l++) {
sum += l;
}
} else {
// 否则找出起始和结束的中间值,分割任务
long mid = (start + end) >>> 1;
SumTask left = new SumTask(start, mid);
SumTask right = new SumTask(mid + 1, end);
left.fork();
right.fork();
// 收集子任务计算结果
sum = left.join() + right.join();
}
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
SumTask sum = new SumTask(100);
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> future = pool.submit(sum);
Long aLong = future.get();
System.out.println(aLong);
pool.shutdown();
}
}
JVM
JVM与操作系统
JVM 与操作系统之间的关系:JVM 上承开发语言,下接操作系统,它的中间接口就是字节码
JVM 内存
JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分
线程私有的: ①程序计数器 ②虚拟机栈 ③本地方法栈
线程共享的: ①堆 ②方法区 直接内存(非运行时数据区的一部分)
虚拟机栈
Java虚拟机栈和线程同时创 建,用于存储栈帧。
每个方法在执行时都会创建一个栈帧(Stack Frame), 用于存储局部变量表、操作数栈、动态 链接、方法出口等信息。
堆
唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。在内存中不连续
设置堆空间大小
-Xmx20m -Xms5m
当下Java应用最大可用内存为20M, 最小内存为5M
永久代和元空间
-
存储位置不同:永久代在物理上是堆的一部分,和新生代、老年代的地址是连续的,而元空间属于本地内存。
-
存储内容不同:在原来的永久代划分中,永久代用来存放类的元数据信息、静态变量以及常量池等。 现在类的元信 息存储在元空间中,静态变量和常量池等并入堆中,相当于原来的永久代中的数据,被元空间和堆内存给瓜分了。
常量池和运行时常量池
字节码文件中,内部包含了常量池
方法区中,内部包含了运行时常量池
常量池:存放编译期间生成的各种字面量与符号引用
运行时常量池:常量池表在运行时的表现形式
JVM加载机制
类加载器ClassLoader角色
- class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到 JVM当中来根据这个文件实例化出n个一模一样的实例。
- class file 加载到JVM中,被称为DNA元数据模板。
- 在 .class文件 --> JVM --> 最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一 个快递员的角色。Class对象存放在方法区
类使用的阶段
类从被加载到虚拟机内存中开始,到卸载出内存,
它的整个生命周期包括:
加载(Loading)、验证 (Verification)、
准备(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸载 (Unloading)这7个阶段
初始化
<clinit>
由Javac编译器的 自动生成物
执行类变量的赋值动作和静态语句块(static{}块) 中的 语句
- 父类的clinit()方法先执行
- 执行接口的 clinit()方法不需要先执行父接口的()方法,
只有当父接口中定义的变量被使用 时, 父接口才会被初始化。
此外, 接口的实现类在初始化时也 一样不会执行接口的clinit()方法。 - 多个线程同 时去初始化一个类, 那 么只会有其中一个线程去执行这个类的()方法, 其他线程都需要阻塞等 待, 直到活动线程执行完毕clinit()方法
垃圾回收
垃圾回收算法
判断对象生死算法
- 引用计数法
- 可达性分析算法
收集死亡对象方 法
有四种,如标记-清除算法、标记-复制算法、标记-整理算法。
垃圾收集器
垃圾收集器是算法的落地实现

网友评论