当前位置: 首页 > news >正文

可以登录国外网站吗黄页88网注册

可以登录国外网站吗,黄页88网注册,海外医疗手机网站建设,网站建设如何更改背景图片Java内存模型#xff1a;JMM#xff08;Java Memory Model#xff09;#xff0c;定义了一套在多线程环境下#xff0c;读写共享数据#xff08;成员变量、数组#xff09;时#xff0c;对数据的可见性#xff0c;有序性和原子性的规则和保障。 原子性 问题分析 【问…Java内存模型JMMJava Memory Model定义了一套在多线程环境下读写共享数据成员变量、数组时对数据的可见性有序性和原子性的规则和保障。 原子性 问题分析 【问题】两个线程对初始值为0的静态变量操作一个线程做自增一个线程做自减各做50000次结果是0吗 public class Demo01 {static int i 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int j 0; j 50000; j) {i;}});Thread t2 new Thread(() - {for (int j 0; j 50000; j) {--i;}});t1.start();t2.start();// 让主线程等待t1和t2两个子线程执行完毕后再执行后续代码t1.join();t2.join();System.out.println(i);} }【结果】上边代码输出每次运行的结果不一样。 【原因】Java中对静态变量的自增自减并不是原子操作对于i而言 getstatic i // 获取静态变量i的值 iconst_1 // 准备常量1 iadd // 加法 putstatic i // 将修改后的值存入静态变量i中Java的内存模型如下如果需要完成静态变量的自增、自减需要在主内存和工作线程的内存中进行交换数据。 由于当线程是按顺序执行所以并不会出现问题。 但是在多线程下可能出现交错运行。线程是一个抢占式的大家都是轮流使用CPU 解决方法 synchronized(对象) {要作为原子操作的代码 }修正后 public class Demo02 {static int i 0;static Object obj new Object();public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {synchronized (obj) {for (int j 0; j 50000; j) {i;}}});Thread t2 new Thread(() - {synchronized (obj) {for (int j 0; j 50000; j) {--i;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(i);} } 这样i和–i的四条指令都可以作为一个整体来运行。 并且t1和t2必须锁的是同一个obj对象相当于这两个人进入了两个不同的房间 Monitor监视器 Monitor 是一种线程同步机制可以理解为 对象锁 的内部实现。每个 Java 对象Object在 JVM 内部都有一个关联的 Monitor用于实现 synchronized 同步机制。 Monitor 的组成部分 (1) Owner持有者 作用表示当前持有 Monitor 的线程。特点 当线程进入 synchronized 代码块时会尝试获取 Monitor 的 owner 权限。如果 owner 为 null即没有线程持有锁当前线程会成为 owner。如果 owner 已经被其他线程持有当前线程会进入 entryList 等待。 (2) EntryList入口队列 作用存储 竞争锁的线程即等待获取锁的线程。特点 当线程 A 持有锁时线程 B 尝试进入 synchronized 代码块会进入 entryList 并进入 BLOCKED 状态。当线程 A 释放锁退出 synchronized 代码块JVM 会从 entryList 中唤醒一个线程使其竞争锁。 (3) WaitSet等待队列 作用存储 调用了 wait() 的线程即主动放弃锁的线程。特点 当线程 A 调用 wait() 时它会释放锁并进入 waitSet状态变为 WAITING。当其他线程调用 notify() 或 notifyAll() 时JVM 会从 waitSet 中随机唤醒一个或全部线程使其重新竞争锁。 3. Monitor 的工作流程 synchronized (obj) { // 1. 尝试获取 Monitor 的 ownerwhile (!condition) {obj.wait(); // 2. 释放锁进入 waitSet}// 3. 执行同步代码 } obj.notify(); // 4. 唤醒 waitSet 中的线程线程 A 进入 synchronized 代码块 检查 owner如果为空线程 A 成为 owner。如果 owner 已被线程 B 持有线程 A 进入 entryListBLOCKED 状态。 线程 A 调用 wait() 释放 owner线程 A 进入 waitSetWAITING 状态。JVM 从 entryList 中唤醒一个线程如线程 B使其成为新的 owner。 线程 B 调用 notify() 从 waitSet 中随机唤醒一个线程如线程 A使其重新进入 entryListBLOCKED 状态。线程 A 需要重新竞争锁不会立即获得锁。 线程 B 退出 synchronized 代码块 释放 ownerJVM 从 entryList 中选择一个线程如线程 A使其成为新的 owner。 可见性 问题分析 【问题】main线程对于run变量的修改对t线程是不可见的这就导致了t线程无法停止 public class Demo03 {static boolean run true;public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {while (run) {// ...}});t.start();Thread.sleep(1000);run false; // 不会停下来} }【原因】 初始的时候t线程刚开始从main线程的内存中读取了run的值到工作线程因为t线程要频繁的从主内存中读取run的值JIT编译器会将run的值缓存到自己的工作内存中的高速缓冲区中这样就可以减少对主内存的读取。主线程睡眠1s后main线程修改了run的值并同步到贮存而t线程仍然是从自己工作内存中的高速缓存中读取这个变量的值结果永远是旧值。 解决办法 volatile易变关键字用来修饰成员变量和静态成员变量可以避免线程从自己的工作缓存中查找变量的值必须到主存中获取它的值线程操作volatile变量都是直接操作主内存。 public class Demo03 {static volatile boolean run true;public static void main(String[] args) throws InterruptedException {Thread t new Thread(() - {while (run) {// ...}});t.start();Thread.sleep(1000);run false; // 不会停下来} }volatile保证的是在多个线程之间一个线程对volatile变量的修改对另一个线程可见但是不能保证原子性只能用在一个写线程多个读线程的情况。synchronized既可以保证原子性也能保证代码块变量的可见性。但是缺点是synchronized属于重量级操作性能相对更低。 【补充】在上边的代码中如果不加volatile但是在for循环里加System.out.println()t线程也能正常看到对run变量的修改。 【原因】System.out.println()底层使用syncronized关键字强制要求当前的线程不要从高速缓存中获取从主线程中获取。 有序性 问题分析——指令重排序 public class Demo04 {static int num 0;static boolean ready false;// 线程1执行此方法public static void actor1(I_Result r) {if(ready) {r.r1 num num;}else {r.r1 1;}}// 线程2执行此方法public static void actor2(I_Result r) {num 2;ready true;}public static void main(String[] args) throws InterruptedException {I_Result r new I_Result();Thread t1 new Thread(() - {actor1(r);});Thread t2 new Thread(() - {actor2(r);});t1.start();t2.start();t1.join();t2.join();System.out.println(r.r1);} }class I_Result {int r1; }上边的代码执行一共可能会有三种不同的输出 正常执行t2先执行完t1后执行 输出4t1先执行完t2后执行 输出1指令重排序导致ready先变成truenum还未赋值 t2先执行ready truet1执行此时num还未被t2修改r.r1 num num 0t2再执行num 2但是t1已经计算完毕不会影响结果 解决方法 如果要保证线程安全可以 使用 volatile 修饰 ready 和 num禁止指令重排序并保证可见性 static volatile int num 0;static volatile boolean ready false;这样 t2 的 num 2 和 ready true 不会重排序且 t1 能立即看到修改。可能的输出1 或 4不会出现 0。 使用 synchronized 加锁确保原子性 public static synchronized void actor1(I_Result r) { ... }public static synchronized void actor2(I_Result r) { ... }这样 t1 和 t2 不会同时执行输出一定是 1 或 4。 有序性理解 static int i, j; // 在某个线程内执行 i ...; // 较为耗时的操作 j ...; 由于这段代码先执行i还是先执行j对结果并不会有影响所以上面代码的执行可以是先对i赋值再对j赋值也可以是先对j赋值再对i赋值。 案例双重检查锁 public class Singleton {private Singleton(){}private static Singleton INSTANCE null;public static Singleton getInstance(){// 实例没创建才会进入内部的synchronized代码块if(INSTANCE null){synchronized (Singleton.class){// 也许有其他线程已经创建实例所以再判读一次if(INSTANCE null){INSTANCE new Singleton();}}}return INSTANCE;} }上边是通过懒汉式的方式实现单例模式只有首次使用getInstance()才使用synchronized加锁后续使用无需加锁。 多线程下可能的问题 但是在多线程环境下上边的代码是有问题的 INSTANCE new Singleton();这行代码在JVM中并不是原子操作它分为三个步骤 分配内存空间malloc初始化对象调用构造方法Sington()将INSTANCE指向分配的内存地址赋值 但是JVM可能会对指令重排序优化执行顺序变成 分配内存空间将INSTANCE指向分配的内存地址此时INSTANCE ! null但是对象未初始化初始化对象调用构造方法 如果发生这种重排序可能导致 线程 A 执行 INSTANCE new Singleton();但只完成了 步骤 1 和 2INSTANCE 已不为 null但对象未初始化。线程 B 调用 getInstance()发现 INSTANCE ! null直接返回 未初始化完成的对象导致错误 解决办法 使用volatile禁止指令重排序 private static volatile Singleton INSTANCE null;happens-before 是JMM的核心规则定义了 多线程环境下操作的可见性和顺序性确保一个线程对共享变量的修改能被其他线程正确观察到。 Java 内存模型定义了 6 种 Happens-Before 规则程序顺序、锁、volatile、线程启动、线程终止、传递性 (1) 程序顺序规则Program Order Rule 在同一个线程中前面的操作 Happens-Before 后面的操作。 int x 1; // (1) int y x 1; // (2) —— (1) Happens-Before (2)单线程下代码顺序执行(1) 的结果对 (2) 可见。 (2) 锁规则Monitor Lock Rule 解锁操作 Happens-Before 后续的加锁操作。 synchronized (lock) {x 10; // (1) } // 解锁 (1) Happens-Before 后续的加锁 synchronized (lock) {int y x; // (2) —— 能读到 x 10 }线程 A 解锁后线程 B 加锁时能看到 A 的修改。 (3) volatile 变量规则Volatile Variable Rule volatile 变量的写操作 Happens-Before 后续的读操作。 volatile boolean flag false;// 线程 A flag true; // (1) —— 写操作// 线程 B if (flag) { // (2) —— (1) Happens-Before (2)能读到 flag true// do something }volatile 保证可见性写操作后读操作一定能看到最新值。 (4) 线程启动规则Thread Start Rule 线程的 start() 方法 Happens-Before 该线程的所有操作。 int x 0;Thread t new Thread(() - {System.out.println(x); // (2) —— 能读到 x 1 }); x 1; // (1) t.start(); // (1) Happens-Before (2)主线程修改 x 1 后子线程能读到这个值。 (5) 线程终止规则Thread Termination Rule 线程的所有操作 Happens-Before 它的终止检测如 join()。 int x 0;Thread t new Thread(() - {x 1; // (1) }); t.start(); t.join(); // (2) —— (1) Happens-Before (2) System.out.println(x); // 输出 1子线程修改 x 1 后主线程 join() 后能读到最新值。 (6) 传递性规则Transitivity Rule **如果 A Happens-Before B且 B Happens-Before C那么 A Happens-Before C。 int x 0; volatile boolean flag false;// 线程 A x 1; // (1) flag true; // (2) —— (1) Happens-Before (2)// 线程 B if (flag) { // (3) —— (2) Happens-Before (3)System.out.println(x); // 输出 1 —— (1) Happens-Before (3) }由于 (1) → (2) → (3)所以 (1) 对 (3) 可见。 CAS与原子类 CAS CASCompare and Swap是一种乐观锁的思想。 【案例】多个线程要对一个共享变量的整型变量执行 1操作 // 需要不断尝试 while(true) {int 旧值 共享变量; // 旧值 0int 结果 旧值 1 // 结果 0 1 1/*这时候如果别的线程把共享变量改成了5本线程的正确结果1就作废了此时compareAdnSwap返回false重新尝试直到compareAndSwap返回true表示本线程做修改的同时其他线程没有干扰*/if(compareAndSwap(旧值, 结果)) {// 成功退出循环} }【注意】 共享变量一定要用volatile修饰保证共享变量的可见性当前线程拿到的共享变量必须一定要是新值。结合CAS和volatile就可以实现无锁并发了适用于竞争不激烈、多核CPU的场景 如果竞争激烈线程重试会频繁发生效率会受到影响因为没有使用synchronized线程并不会陷入阻塞这也是效率提升的因素 CAS底层依赖于Unsafe类来直接调用操作系统底层的CAS指令 乐观锁与悲观锁 CAS最乐观的估计不怕别的线程来修改共享变量如果改了就重试即可。 synchronized最悲观的估计得防着其他线程来修改共享变量直接给代码上锁等执行完解开锁了其他线程才有机会执行。 原子操作类 jucjava.util.concurrent包下提供了原子操作类可以提供线程安全的操作例如AtomicInteger、AtomicBoolean…他们的底层就是使用CAS volatile来实现的。 public class Demo05 {static AtomicInteger i new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int j 0; j 50000; j) {i.getAndIncrement();}});Thread t2 new Thread(() - {for (int j 0; j 50000; j) {i.getAndDecrement();}});t1.start();t2.start();// 让主线程等待t1和t2两个子线程执行完毕后再执行后续代码t1.join();t2.join();System.out.println(i);} }synchronized优化 JVM中每个对象都有对象头包括class指针、Mark Word。Mark Word平时存储这个对象的哈希码、分代年龄…当加锁时这些信息就会被替换成标记位、线程锁记录指针、重量级指针、线程id… 轻量级锁 如果一个对象虽然有多个线程访问但多线程访问的时间是错开的没有竞争那么可以用轻量级锁来优化。 这就类似于学生A线程A用课本占座短暂的离开教室了一下时间片到 回来发现课本没变没有竞争就会继续上课仍然保持轻量级锁如果期间又来了一个学生B线程B就会告知学生A线程A此时有并发访问线程A就会升级成重量级锁进入重量级锁的流程。 每个线程的栈帧都会包含一个锁记录的结构内部可以存储锁定对象的Mark Word。 static Object obj new Object(); public static void method1() {synchronized(obj) {// 同步块Amethod2();} } public static void method2() {synchronized(obj) {// 同步块B} } 线程1对象Mark Word线程2访问同步块A把MarkWord赋值到线程1的锁记录01无锁-CAS修改MarkWord为线程1锁记录01无锁-成功加锁00轻量级锁线程1锁记录地址-执行同步块A00轻量级锁线程1锁记录地址-访问同步块B把MarkWord赋值到线程1的锁记录00轻量级锁线程1锁记录地址-CAS修改MarkWord为线程1锁记录00轻量级锁线程1锁记录地址-失败发现是自己的锁00轻量级锁线程1锁记录地址-锁重入00轻量级锁线程1锁记录地址-执行同步块B00轻量级锁线程1锁记录地址-同步块B执行完毕00轻量级锁线程1锁记录地址-同步块A执行完毕00轻量级锁线程1锁记录地址-成功解锁01无锁--01无锁访问同步块A把MarkWord赋值到线程2的锁记录-01无锁CAS修改MarkWord为线程2锁记录-00轻量级锁线程1锁记录地址成功加锁……… 锁膨胀 在尝试加轻量级锁的过程中CAS操作无法成功这时如果其他线程为这个对象加上轻量级锁有竞争这时就需要进行锁膨胀将轻量级锁变为重量级锁 static Object obj new Object(); public static void method1() {synchronized(obj) {// 同步块} }线程1对象Mark Word线程2访问同步块把MarkWord赋值到线程1的锁记录01无锁-CAS修改MarkWord为线程1锁记录01无锁-成功加锁00轻量级锁线程1锁记录地址-执行同步块00轻量级锁线程1锁记录地址-执行同步块00轻量级锁线程1锁记录地址访问同步块把MarkWord赋值到线程2执行同步块00轻量级锁线程1锁记录地址CAS修改MarkWord为线程2锁记录执行同步块00轻量级锁线程1锁记录地址失败发现别人已经占了锁执行同步块00轻量级锁线程1锁记录地址CAS修改Mark为重量级锁执行同步块10重量级锁重量锁指针阻塞中执行完毕10重量级锁重量锁指针阻塞中失败解锁10重量级锁重量锁指针阻塞中释放重量锁唤起阻塞线程竞争10重量级锁重量锁指针阻塞中-10重量级锁重量锁指针竞争重量锁-10重量级锁重量锁指针成功加锁……… 加重量级锁是为了后边唤醒的时候根据重量级锁的指针唤醒阻塞中的线程。 重量级锁 重量级锁竞争时可以使用自旋来进行优化如果当时线程自旋成功说明此时持有锁的线程已经退出同步代码块释放锁此时当前线程就可以避免阻塞直接进入运行状态。 自旋锁是自适应的 对象刚刚的一次自选操作成功了那么认为这次自旋成功的可能性会高就会多自旋几次反之就少自旋 或 不自旋 注意自旋会占用CPU时间只有多核的CPU才能发挥自旋的优势。 偏向锁 只有第一次使用CAS将线程ID设置到对象的Mark Word投之后发现这个线程ID是自己的就表示没有竞争不用重新CAS。 其他优化 较少上锁时间同步代码块中尽量短减少锁的粒度将一个锁拆分成多个锁提高并发度 ConcurrentHashMap每次只锁住了一个部分其他读取操作不会受到影响。LongAdder累加工具类分为base和cells两部分 没有并发争用或cells数组正在初始化时就会使用CAS来累加到base有并发争用就会初始化cells数组数组有多少个cell就允许多少线程并行修改最后将数组中每个cell累加再加上base就是最终的值 LinkedBlockingQueue出队和入队使用的就是不同的锁相对于LinkedBlockingArray只有一个锁效率要高 锁粗化StringBuffer的append方法都会调用synchronized来进行同步保护如果不加以限制那么下边这段代码会重复调用三次synchronized。JVM会将多次的append的加锁操作粗化为一次因为都是一个对象加锁没必要重入多次 new StringBuffer().append(a).append(b).append(c);锁消除JVM会进行代码的逃逸分析例如某个加锁对象是方法内局部变量不会被其他线程访问到这时就会被即时编译器忽略掉所有同步操作。读写分离CopyOnWriteArrayList、CopyOnWriteSet读原始数组的内容写操作会复制一份在新数组上进行写操作
http://www.w-s-a.com/news/455418/

相关文章:

  • 做企业网站的轻量级cmswordpress 越来越慢
  • 无锡中英文网站建设莱芜网络公司
  • ps软件下载官方网站相关搜索优化软件
  • 世界杯网站源码下载做网站推广代理
  • 用股票代码做网站的wordpress通过标签调用文章
  • iis添加网站ip地址树莓派运行wordpress
  • 网站空间域名多少钱宿迁做网站公司
  • 福州建设企业网站网站交互主要做什么的
  • 英文网站建设方法门户网站特点
  • 腾讯云备案 网站名称萧山城市建设网站
  • 漳浦网站建设网络营销推广策略
  • 龙岗商城网站建设教程百度关键词排名突然没了
  • 深圳网站建设服务哪家有织梦网站模板安装
  • 网站设计与网页制作代码大全网站开发还找到工作吗
  • 给设计网站做图会字体侵权吗站长工具seo综合查询张家界新娘
  • 网站的建设与颜色搭配win7在iis中新建一个网站
  • 单位做网站有哪些功能型类的网站
  • 网站怎样做优惠卷移动互联网开发培训
  • 重庆网站建设帝维科技网站做定向的作用
  • 网站建设工作室wp主题模板做污事网站
  • 网站建设 深圳 凡科重庆家居网站制作公司
  • 自己也可以免费轻松创建一个网站企业收录网站有什么用
  • 帮别人做网站违法导航网站开发工具
  • seo网站外包公司字画价格网站建设方案
  • 网站国内空间价格销售技巧
  • 广安建设企业网站qq互联网站备案号
  • 京东网站建设的要求vs2010做的网站
  • wordpress 新闻杂志主题佛山企业网站排名优化
  • 选服务好的网站建设金华市开发区人才网
  • 广州建站商城南阳高质量建设大城市网站