堆是线程间共享的,要合理的给一个对象上锁。
类锁通过对象锁实现的
因为类锁是对象锁实现的 同对象,传入了同种对象实例
互不干扰,因为锁两个对象锁
package thread;
public class SyncDemo {
public static void main(String... args) {
SyncThread syncThread = new SyncThread();
// 异步
Thread A_thread1 = new Thread(syncThread, "A_thread1");
Thread A_thread2 = new Thread(syncThread, "A_thread2");
// 对象锁
// 同步代码块
Thread B_thread1 = new Thread(syncThread, "B_thread1");
Thread B_thread2 = new Thread(syncThread, "B_thread2");
// 同步非静态方法
Thread C_thread1 = new Thread(syncThread, "C_thread1");
Thread C_thread2 = new Thread(syncThread, "C_thread2");
// 类锁
// 同步代码块
Thread D_thread1 = new Thread(syncThread, "D_thread1");
Thread D_thread2 = new Thread(syncThread, "D_thread2");
// 同步静态方法
Thread E_thread1 = new Thread(syncThread, "E_thread1");
Thread E_thread2 = new Thread(syncThread, "E_thread2");
A_thread1.start();
A_thread2.start();
B_thread1.start();
B_thread2.start();
C_thread1.start();
C_thread2.start();
D_thread1.start();
D_thread2.start();
E_thread1.start();
E_thread2.start();
}
}
package thread;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SyncThread implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.startsWith("A")) {
async();
} else if (threadName.startsWith("B")) {
syncObjectBlock1();
} else if (threadName.startsWith("C")) {
syncObjectMethod1();
} else if (threadName.startsWith("D")) {
syncClassBlock1();
} else if (threadName.startsWith("E")) {
syncClassMethod1();
}
}
/**
* 异步方法
*/
private void async() {
try {
System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 方法中有 synchronized(this|object) {} 同步代码块
*/
private void syncObjectBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* synchronized 修饰非静态方法
*/
private synchronized void syncObjectMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void syncClassBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SyncThread.class) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized static void syncClassMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Java对象头 Monitor
对象头 实例数据 对齐填充
synchronized的锁对象是存在对象头里的 MarkWord存储对象运行时数据。 是实现 轻量级锁 和 偏向锁(Java6后新增加的)的关键 由于对象头信息是与对象自身定义没有关系的额外存储成本,因此考虑到JVM的效率,MarkWord 被设计为一个非固定的数据结构,以便存储更多的有效数据,根据对象状态复用自身的存储空间 重量级锁:synchronized对象锁,指向的指针是Monitor的起始地址。每个对象和Monitor存在关联,对象与Monitor之间有多种实现形式 如:对象可以和Monitor同时创建或销毁 或 单线程试图获得对象锁时,自动生成。 Monitor被某个线程持有后变为锁定状态 在Java虚拟机及hospot虚拟机,Monitor是由hospotMonitor实现的(源码C++实现的)
MonitorJava娘胎创建的锁 管程 或 监视器锁(描述为 同步工具,或同步机制,通常被描述为同步对象)
Monitor存在每个对象的对象头中,synchronized通过这种方式获取锁 ,这也是Java任意对象可以作为锁的原因
看.class里边的文件
javap -verbose
重入请求将会成功,synchronize是Java原子性的内部锁机制是可重入的
线程通过计数器判断,本次是否应该先阻塞
没有monitorenter、monitorexit、指令少。 方法体是隐式的,即无需通过字节码控制 正常完成或非正常完成,都释放monitor 如果内有异常,内部无法处理,会抛到外部直到释放
重量级锁,监视器锁Monitor运行在底层操作系统。切换线程,时间长。效率低
自适应自旋 锁消除 锁粗化 轻量级锁 偏向锁 …… 为了在线程间更高效的共享数据,解决竞争问题,高效程序执行效率
等一会,不放弃cpu,忙循环等待持有锁的线程释放锁 不像sleep一样放弃cpu的执行时间 Java4被引入默认关闭,但Java6才启用 本质上与阻塞并不相通,考虑其对多处理器 的要求
每次线程需要等待的时间还有次数是不固定的 因此PreBlockSpin无法设计的很合理 JVM对锁的预测越来越精准,JVM越来越聪明
StringBuffer虽然是线程安全的,但操作本地变量(不是共享的)JVM消除内部锁,避免资源浪费
随竞争情况逐渐升级 其实锁降级会发生:当JVM进入前面的安全点,会检测是否有闲置的Monitor ,然后试图降级
CAS无锁算法
初始-无锁01 拷贝-(CAS更新)双向指向-成功轻量级00 失败-判断markword是否指向栈帧 ?有则说明当前线程拥有这个对象的锁,进入同步块:没有则说明多个线程竞争锁,轻量级锁->重量级锁10 变成重量级锁时候,其他线程会阻塞,自旋会防止阻塞
Java5之前只有synchronized,从5之后有了ReentrantLock(再入锁)
AQS
state数组成员表存状态 队列 CAS基础 acquire获取资源独占权 release释放对某个资源的独占
公平性Java中一般用的不多,synchronized一般也不会引起饥饿, 除非必须要用公平性,因为开启公平性会增加额外的开销,导致程序吞吐量下降
ArrayBlockingQueue 数组实现、线程安全的(ReentrantLock互斥锁保护资源)、有界的(数组长度)、阻塞队列 ArrayBlockingQueue与Condition是组合的关系 Condition依附于ArrayBlockingQueue存在
队列为空等待有新的消息加入再返回 条件判断通知等待线程
来自AQS框架
unsafe 类似后门工具 可以在任意内存位置读写数据,对普通用户操作比较危险 支持一些CAS操作
工作内存是每个线程的私有区域
主内存:好似,堆、方法区 工作内存:
忽略硬件部分,简单理解为从内存读取到缓存取处理,后存入内存。 线程共享变量,一致性问题。由于多线程,很有可能第二条线程处理的数据是前一条线程处理前的旧状态,为此引入了复杂的数据依赖性。 重排序要尊重数据依赖性的要求,否则就打破了数据的正确性。
为了提高性能。处理器,编译器常常对指令重排序(不能随意重排序) JMM一般通过禁止某些指令的重排序,来保障内存可见性,也就是实现了happens-before规则。
使用interrupt()中断线程 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用interrupt()方法,线程并没有实际被中断,会继续往下执行。
Thread Join 方法。在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
上述不满足线程安全,需要通过以下两条变成happens-before 修复代码:(使满足锁 或 volatile变量规则)
volatile保证写的可见性 但是多线程的运算 操作并不保证安全性
synchronized控制直接反馈给主存
利用立即可见的原子性
通过(对主内存) 写 读 的方式,达到立即可见的效果
内存屏障 cpu指令 因为编译器、处理器,都会影响指令的重排优化。 如果插入 内存屏障 ,则告诉编译器、或cpu,任何指令都不能与 内存屏障 重排序,不能打乱他们的顺序
cynchronize属于悲观锁,始终假定会出现并发冲突,因此屏蔽一切可能违反数据完整性的操作 乐观锁则,假设不会发生并发冲突,因此只在提交操作时检查是否违反数据完整性,如果提交失败,则会重试,乐观锁最常见的是CAS CAS上层感觉无锁,其实底层还是有加锁行为的。操作失败,自己决定,因此不会被阻塞挂起
V和A相比较,如果一致,会将该位置的值更新为新值。否则不做任何操作 V是主内存的值
场景举例: 当要更新一个共享变量的值。 会取该变量的值到A,然后经过计算,得到新值B 需要更新共享变量的值的时候,调用CAS方法去更新共享变量的值
volatile保证线程可见性,同时不允许线程对其重排序,但是不能保证下面三个指令原子执行,在多线程并发无法做到线程安全得到正确结果 改进方案(可行,但近一步提升性能,不用synchronized的悲观锁)
深入到jvm指令集源码会发现,不同的体系架构指令集不同
多数情况下,开发者不需要直接利用CAS代码实现线程安全容器 更多的是使用并发包间接的享受在扩展性的好处
CAS虽然高效的解决了原子操作问题 循环长 比如上方:getAddInt,如果失败一直while尝试 CAS可保障一个共享变量 原子性,多个共享变量就得用锁来保障原子性 ABA:如果V初次读取的是A,(这期间可能被改变过B)并在准备赋值的时候仍然为A。可能被误认为重来没有被改变过 为解决ABA问题:JUC带有标记的原子引用类, 通过控制变量值的版本来解决ABA的问题 改为传统的互斥同步,可能比原子的更高效 设计程序前,想好ABA问题是否影响程序的并发性
服务端处理并发请求多,但每个线程执行的时间很短,就会频繁的创建销毁请求,会大大降低系统效率。可能出现创建销毁的时间,要比处理用户请求的时间和资源更多,那么有没有一种方法重复利用线程去完成新任务呢?
通过重复利用已经创建的线程,降低(线程频繁创建、销毁)资源消耗 使用线程池可统一进行分配、调优、监控
1
2
3
深入
4
5
后面又是Excecutor
以上都是属于此框架的体系的类或接口 根据一组执行策略调度异步任务的框架,目的是将任务提交,和任务如何运行分离开的机制 Executors提供尽量简化操作的工厂方法
对与不同execute实现可能情况 创建新线程 传入已有线程 根据线程池的容量 阻塞队列的容量,来决定传入的线程是否放入阻塞队列中,或者拒绝接受阻塞的线程 执行方法 直接将线程给execute方法去执行
大多数场景使用Executor提供的5种线程池就够,但其他场景还是要自己用ThreadPoolExecutor来创建 分析线程池的设计与实现: 队列存储用户的提交,是一个工作队列 队列接到客,就排队提交给内部的线程池(工作线程的集合),该集合需要管理线程的创建和销毁 当线程池压力较大,会创建新的线程,当任务量变小,线程池会闲置一段时间,结束线程 。线程池的工作线程,被抽象为静态内部类worker,TreadPool实际上维护的就是一组worker对象
firstTask来保存传入的任务 thread调用构造方法函数时,通过ThreadFactory来创建的线程
因为worker实现了runable方法 调用run方法去执行里面的逻辑
比如任务提交被拒绝,线程池已经处于shutdown的状态,此时新来的任务需要有处理机制来处理
以及最后补充的饱和策略
长期驻留线程数,对不同线程池可能会有很大区别
比如
或
比如固定
或动态
线程提交时,线程池的线程数量大于corePoolSize,把该任务封装成一个worker对象放入等待队列。 由于存在许多种等待队列,每种不同队列有不同的排队机制。可以自行体会感受线程池带来的灵活
线程池维护线程,所允许的空闲时间。 当线程池里的线程数量大于corePoolSize,如果这时没有新的任务提交,核心线程不会立即被销毁,直到等待时间超过keepAliveTime,才会被销毁
会使新创建的线程具有相同的优先级,并且是非守护线程,同时也设置了线程名称
如果阻塞队列满了,并且没有空闲的线程,如果继续提交任务,就需要采取一种策略
线程池和线程一样也会有生命周期,生命周期通过状态值来表示的。 线程池用来管理线程,因此对线程的数量一定是了如指掌的,有存储当前有效线程数 ThreadPoolExecutor将状态值和有效线程数合二为一,存储到了ctl ctl高三位存状态值,低29位保存有效线程数
对ctl进行优雅计算的(用的与或非,执行起来相当高效) 获取运行状态 获取活动线程数 ctl获取两者
TIDYING workercount有效线程数为零,正在做打扫操作 TERMINATED什么也不做只作为一个标识
没有固定,看工作经验
Java并发编程实战