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

哪个网站做质量认证书范本帮助网站源码

哪个网站做质量认证书范本,帮助网站源码,商丘网站制作案例,wordpress 分页静态目录 十七 . 并发相关基础概念 17.1 线程安全 17.2 保证线程安全的两个方法 17.2.1 封装 17.2.2 不可变 17.2.2.1 final 和 immutable解释 17.3 线程安全的基本特性 17.3.1 原子性#xff08;Atomicity#xff09; 17.3.2 可见性#xff08;Visibility#xff09; 17.3.2.1… 目录 十七 . 并发相关基础概念 17.1 线程安全 17.2 保证线程安全的两个方法 17.2.1 封装 17.2.2 不可变 17.2.2.1 final 和 immutable解释  17.3 线程安全的基本特性 17.3.1 原子性Atomicity 17.3.2 可见性Visibility 17.3.2.1  volatile 关键字 17.3.2.2 synchronized 关键字 17.3.2.3  Lock 接口 17.3.2.3.1 解释Lock接口 17.3.3 有序性  17.3.3.1 volatile 关键字 17.3.3.2 synchronized 关键字 17.3.3.3 Lock 接口 17.3.3.4 happens-before 原则 17.3.3.4.1 线程中断规则Thread Interruption Rule 17.3.3.4.2  线程终止规则 17.3.4 互斥性  17.3.4.1 synchronized 关键字例子 17.3.4.2 Lock 接口例子 十八 . synchronized底层如何实现什么是锁的升级降级 18.1 典型回答 18.1.1 monitorenter和monitorexit解释 18.1.2 Monitor实现 18.1.3 锁的升级降级 18.1.4 偏向锁 18.1.4.1 偏向锁的原理 18.1.4.2 偏向锁例子 18.1.4.2.1例子详细解释 18.1.4.3 偏斜锁如何保证多线程环境下数据安全 18.1.4.4 可重入锁或非重入锁解释 18.1.4.4.1 可重入锁的例子 18.1.4.5 互斥操作 18.1.4.6 偏向锁的优化点小结 18.1.5  轻量级锁 18.1.6 重量级锁 18.1.7 轻量级锁和重量级锁的比较 18.1.8 Java是否会进行锁的降级 18.1.9 临界区域Critical Section解释 十七 . 并发相关基础概念 可能前面几讲一些同学理解可以有一些困难这一篇将进行一些并发相关概念比较模糊我们将进行并发相关概念的补充 17.1 线程安全 线程安全就是在多线程的环境下正确的一个概念保证在多线程的环境下是实现共享的可修改的状态是正确性状态可以类比为程序里面的数据。 如果状态不是共享的或者不是可修改的就不存在线程安全的问题。 17.2 保证线程安全的两个方法 17.2.1 封装 进行封装我们将对象内部的状态隐藏保护起来。 17.2.2 不可变 可以进行final和immutable进行设置。 17.2.2.1 final 和 immutable解释 final 和 immutable 是 Java 中用来描述对象特性的关键字。 final用于修饰变量、方法和类。它的作用如下 变量final 修饰的变量表示该变量是一个常量不可再被修改。一旦赋值后其值不能被改变。通常用大写字母表示常量并在声明时进行初始化。方法final 修饰的方法表示该方法不能被子类重写覆盖。类final 修饰的类表示该类不能被继承。 immutable指的是对象一旦创建后其状态数据不能被修改。不可变对象在创建后不可更改任何操作都不会改变原始对象的值而是返回一个新的对象。 不可变对象的主要特点包括 对象创建后其状态无法更改。所有字段都是 final 和私有的不可直接访问和修改。不提供可以修改对象状态的公共方法。 不可变对象的优点包括 线程安全由于对象状态不可更改因此多线程环境下不需要额外的同步措施。缓存友好不可变对象的哈希值不会改变因此可以在哈希表等数据结构中获得更好的性能。 17.3 线程安全的基本特性 17.3.1 原子性Atomicity 指的是一系列操作要么全部执行成功要么全部失败回滚。即一个操作在执行过程中不会被其他线程打断保证了操作的完整性。 17.3.2 可见性Visibility 指的是当一个线程修改了共享变量的值后其他线程能够立即看到最新的值。需要通过使用 volatile 关键字、synchronized 关键字、Lock 接口等机制来确保可见性。 详细解释 17.3.2.1  volatile 关键字 当一个变量被声明为volatile时任何对该变量的修改都会立即被其他线程可见。 当写线程将flag值修改为true后读线程会立即看到最新的值并进行相应的操作。这是因为flag变量被声明为volatile确保了可见性。 public class VisibilityExample {private volatile boolean flag false;public void writerThread() {flag true; // 修改共享变量的值}public void readerThread() {while (!flag) {// 循环等待直到可见性满足条件}System.out.println(Flag is now true);} } 17.3.2.2 synchronized 关键字 两个方法都使用synchronized关键字修饰确保了对flag变量的原子性操作和可见性。当写线程修改flag的值为true后读线程能够立即看到最新的值。 public class VisibilityExample {private boolean flag false;public synchronized void writerThread() {flag true; // 修改共享变量的值}public synchronized void readerThread() {while (!flag) {// 循环等待直到可见性满足条件}System.out.println(Flag is now true);} } 17.3.2.3  Lock 接口 通过使用ReentrantLock实现了显式的加锁和释放锁操作。当写线程获取锁并修改flag的值为true后读线程也需要获取同样的锁才能看到最新的值。 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class VisibilityExample {private boolean flag false;private Lock lock new ReentrantLock();public void writerThread() {lock.lock();try {flag true; // 修改共享变量的值} finally {lock.unlock();}}public void readerThread() {lock.lock();try {while (!flag) {// 循环等待直到可见性满足条件}System.out.println(Flag is now true);} finally {lock.unlock();}} } 17.3.2.3.1 解释Lock接口 使用Lock接口进行同步时通过持有锁可以确保在临界区内的操作是互斥的即同一时间只能有一个线程执行临界区的代码。这样可以避免多个线程同时对共享变量进行修改带来的问题。 当读线程在访问共享变量之前发现变量的值不符合预期即不满足可见性条件时它会进入循环等待的状态。这样做的目的是等待写线程将最新的值写回共享变量并使其对其他线程可见。 循环等待的方式可以有效地解决可见性问题。当写线程修改共享变量的值后它会释放锁。此时读线程能够重新获取锁并再次检查共享变量的值。如果值已经满足可见性条件读线程就能够继续执行后续的操作。 需要注意的是在循环等待的过程中读线程应该使用适当的等待方式例如Thread.sleep()或者Lock接口提供的Condition条件对象的await()方法以避免占用过多的CPU资源。 通过循环等待直到可见性满足条件可以确保读线程在访问共享变量时能够看到最新的值从而实现了可见性的要求。 17.3.3 有序性 指的是程序执行的顺序与预期的顺序一致不会受到指令重排序等因素的影响。可以通过 volatile 关键字、synchronized 关键字、Lock 接口、happens-before 原则等来保证有序性。 例子 17.3.3.1 volatile 关键字 使用volatile关键字修饰counter变量确保了对变量的读写操作具有可见性和有序性。其他线程能够立即看到最新的值并且操作的顺序不会被重排序。 public class OrderingExample {private volatile int counter 0;public void increment() {counter; // 非原子操作但通过volatile关键字确保了可见性和有序性}public int getCounter() {return counter; // 获取变量的值} } 17.3.3.2 synchronized 关键字 使用synchronized关键字修饰了increment()和getCounter()方法确保了对counter变量的原子操作同时也提供了可见性和有序性的保证。 public class OrderingExample {private int counter 0;public synchronized void increment() {counter; // 原子操作同时具备可见性和有序性}public synchronized int getCounter() {return counter; // 获取变量的值} } 17.3.3.3 Lock 接口 通过使用Lock接口实现了显式的加锁和释放锁操作确保了对counter变量的原子操作同时也提供了可见性和有序性的保证。 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class OrderingExample {private int counter 0;private Lock lock new ReentrantLock();public void increment() {lock.lock();try {counter; // 原子操作同时具备可见性和有序性} finally {lock.unlock();}}public int getCounter() {return counter; // 获取变量的值} } 17.3.3.4 happens-before 原则 happens-before是并发编程中的一个概念用于描述事件之间的顺序关系。在多线程或多进程的环境中经常会出现多个事件同时发生的情况而它们之间的执行顺序可能是不确定的。为了确保程序正确地执行我们需要定义一些规则来解决竞态条件和并发问题。 happens-before关系用于描述事件之间的顺序关系并指定了一个事件在执行结果上的先于另一个事件。如果一个事件A happens-before 另一个事件B那么我们可以说事件A在时间上 早于 事件B而事件B在时间上 晚于 事件A。 根据Java内存模型Java Memory Model简称JMM的规定。 happens-before关系例子 程序顺序原则Program Order Rule在单个线程中按照程序的顺序前面的操作 happens-before 后面的操作。volatile变量规则Volatile Variable Rule对一个volatile域的写操作 happens-before 于后续对该域的读操作。volatile变量的写-读能够确保可见性。传递性Transitive如果事件A happens-before 事件B事件B happens-before 事件C那么可以推导出事件A happens-before 事件C。通过传递性可以推断出不同事件之间的happens-before关系。线程启动规则Thread Start RuleThread对象的start()方法调用 happens-before 新线程的所有操作。线程终止规则Thread Termination Rule线程的所有操作 happens-before 其他线程中对该线程终止检测的操作。线程中断规则Thread Interruption Rule对线程的interrupt()方法的调用 happens-before 所被中断线程中的代码检测到中断事件的发生。 例子 17.3.3.4.1 线程中断规则Thread Interruption Rule 线程A会执行一段任务。在线程A的任务执行的过程中会循环检查中断状态当线程B调用线程A的interrupt()方法进行中断时线程A会在检查中断状态的代码处发现自己已被中断并返回。这里线程B的interrupt()调用和线程A的检查中断状态的操作之间存在一个happens-before关系保证线程B中的中断操作能被线程A正确检测到。 class MyTask implements Runnable {Overridepublic void run() {// 执行任务的代码// ...// 检查中断状态if (Thread.interrupted()) {// 在此处被中断return;}// 继续执行任务的代码// ...} }public class Main {public static void main(String[] args) throws InterruptedException {Thread threadA new Thread(new MyTask());threadA.start();// 主线程等待一段时间后中断线程AThread.sleep(1000);threadA.interrupt();} } 17.3.3.4.2  线程终止规则 主线程首先创建一个子线程并将isRunning设置为true然后子线程进入一个死循环并在每次循环中检查isRunning的值。主线程等待2秒后将isRunning设置为false终止子线程的执行并使用join()方法等待子线程终止。最后主线程打印出主线程继续执行。 子线程的终止操作isRunning false happens-before 主线程中对isRunning的读取操作因此主线程能够观察到子线程的终止并能够继续执行。这符合线程终止规则。 public class ThreadTerminationExample {private static volatile boolean isRunning true;public static void main(String[] args) throws InterruptedException {Thread thread new Thread(() - {while (isRunning) {// 线程执行的工作...}System.out.println(线程已终止);});thread.start();Thread.sleep(2000);isRunning false; // 终止线程thread.join(); // 等待线程终止System.out.println(主线程继续执行);} } happens-before关系的定义保证了程序执行的可见性和有序性为并发编程提供了一定的保证。开发人员可以利用这些规则来避免竞态条件和并发问题。 17.3.4 互斥性  指的是同一时间只允许一个线程对共享资源进行操作其他线程必须等待。可以通过使用 synchronized 关键字、Lock 接口来实现互斥性。 17.3.4.1 synchronized 关键字例子 使用synchronized关键字修饰了increment()和getCount()方法这意味着同一时间只能有一个线程访问这两个方法。当一个线程在执行increment()方法时其他线程需要等待直到当前线程执行完毕才能继续访问。这样可以保证count的操作是原子的避免了并发访问导致的数据冲突。 public class Counter {private int count 0;public synchronized void increment() {count;}public synchronized int getCount() {return count;} } 17.3.4.2 Lock 接口例子 使用ReentrantLock来创建一个锁并在increment()和getCount()方法中使用lock()方法获取锁unlock()方法释放锁。这样同一时间只允许一个线程获取锁并执行代码块其他线程需要等待锁被释放后才能继续执行从而实现了互斥性。 无论是使用synchronized关键字还是Lock接口它们都能够实现互斥性保证多线程对共享资源的访问是同步的避免了数据冲突和不一致的问题。但Lock接口相比synchronized关键字更加灵活可以更精细地控制锁的获取和释放提供了更多的功能。 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class Counter {private int count 0;private Lock lock new ReentrantLock();public void increment() {lock.lock();try {count;} finally {lock.unlock();}}public int getCount() {lock.lock();try {return count;} finally {lock.unlock();}} } 十八 . synchronized底层如何实现什么是锁的升级降级 前面博客看了后相信你对线程安全和如何使用基本的同步机制有了基础接下了进入synchronized底层机制。 18.1 典型回答 sychronized 代码块是由monitorenter/monitorexit指令实现的Monitor对象是同步的基本实现单元。 18.1.1 monitorenter和monitorexit解释 monitorenter和monitorexit是Java字节码指令用于实现Java对象的同步锁机制。具体来说monitorenter指令用于获取对象的监视器锁而monitorexit指令用于释放对象的监视器锁。 在Java虚拟机中每个对象都与一个监视器关联可以使用synchronized关键字或者Object类的wait()、notify()和notifyAll()方法来对对象的监视器进行操作。在字节码层面monitorenter和monitorexit指令就是实现这些操作的。 18.1.2 Monitor实现 Java6前Monitor的实现完全依靠操作系统内部的互斥锁因为需要从用户态切换到内核态同步操作是一个无差别的重量级操作。 现代的JDK中JVM进行了很大的改进提供了三种不同的Monitor实现 如偏斜锁Biased Locking),轻量级锁重量级锁进行了性能的改进。 18.1.3 锁的升级降级 JVM进行优化synchronized运行的机制当JVM检测到不同的竞争状况时会自动切换到适合的锁实现这种切换就是锁的升级降级。 当没有竞争出现时默认使用偏斜锁JVM会利用CAS操作在对象头上的Mark Word部分设置线程ID以表示这个对象偏向于当前线程所以并不涉及真正的互斥锁。 这样做的假设是基于很多应用场景中大部分对象生命周期中最多会被一个线程锁定使用偏斜锁可以降低无竞争开销。 18.1.4 偏向锁 当没有竞争出现时使用偏斜锁可以提供更好的性能表现。 18.1.4.1 偏向锁的原理 当一个线程访问一个对象时JVM会首先在对象的头部中的 Mark Word 字段记录当前线程的 ID并将对象标记为“偏向锁”。如果其他线程尝试获取该对象的锁时会发现该对象已经被偏向于某个线程此时它们会进行自旋等待而不会立即阻塞。如果其他线程一直自旋等待而偏斜锁拥有者的线程也不断访问该对象保持偏斜状态JVM会消除偏斜锁使得对象变为无锁状态。如果其他线程成功获取了偏斜锁或者偏斜锁拥有者的线程退出同步块JVM会撤销偏斜锁的状态将对象重新恢复为可重入锁或非重入锁。 18.1.4.2 偏向锁例子 一个多线程程序其中有一个共享的计数器对象。在绝大多数情况下只有一个线程会访问该计数器对象进行自增操作其他线程很少去改变它。这时候使用偏斜锁会带来明显的性能优势。 初始状态计数器对象未被任何线程访问处于无锁状态。线程 A 访问计数器对象线程 A 首先将对象头部中的 Mark Word 设置为自己的线程 ID并将对象标记为“偏向锁”。线程 A 自增计数器并完成操作。其他线程尝试访问计数器对象线程 B、C、D 等尝试获取计数器对象的锁但发现该对象已经被偏向于线程 A。它们会进行自旋等待但不会立即阻塞。偏斜锁保持状态线程 A 再次访问计数器对象JVM会发现该对象已经是偏斜锁状态并且访问线程和持有偏斜锁线程是同一个。因此线程 A 可以直接访问对象而不需要进行互斥操作。竞争出现如果线程 B 尝试获取计数器对象的锁并且与偏斜锁拥有者不是同一个线程那么偏斜锁就会被撤销变为可重入锁或非重入锁进而线程 B 可以成功获取锁。 通过偏斜锁的优化当只有一个线程访问计数器对象时不会产生真正的互斥操作避免了线程切换和锁开销提高了性能。只有在其他线程尝试获取锁时才会进行额外的操作从而减少了无竞争的开销。这种设计基于大部分对象生命周期中只被一个线程访问的假设并且可以适用于很多应用场景提升程序的执行效率。 18.1.4.2.1例子详细解释 当线程 A 再次访问计数器对象时JVM会检查对象的偏斜锁状态和持有偏斜锁的线程是否与当前访问线程一致。如果是一致的表示线程 A 仍然是该对象的主要访问者JVM会直接允许线程 A 访问该对象而无需进行任何互斥操作。这样可以避免线程切换和锁竞争提高程序的执行效率。 然而当其他线程例如线程 B尝试获取计数器对象的锁时JVM会检测到存在竞争。竞争发生的条件是尝试获取锁的线程与持有偏斜锁的线程不一致。在这种情况下JVM会撤销偏斜锁的状态将对象转换为可重入锁或非重入锁。 具体而言线程 B 尝试获取计数器对象的锁时JVM会在对象头部中更新 Mark Word 的信息 将原先记录持有偏斜锁的线程 ID 清空表示偏斜锁的状态被撤销。将锁的状态标记为可重入锁或非重入锁。 此时线程 B 成功获取了计数器对象的锁并可以执行相应的操作。这个过程称为偏斜锁撤销对象从偏斜锁状态转换为可重入锁或非重入锁状态。 这种竞争的出现使得原先持有偏斜锁的线程需要重新进行锁争用而新的竞争线程能够成功获取锁。这种机制保证了当多个线程同时需要访问计数器对象时能够按照先到先得的原则进行互斥操作避免数据被错误修改。 总而言之偏斜锁允许单线程对对象进行快速访问提高了程序的执行效率。但当其他线程尝试获取锁时偏斜锁会被撤销以保证多线程环境下的数据安全性。 18.1.4.3 偏斜锁如何保证多线程环境下数据安全 假设有一个账户对象包含账户余额信息。初始状态下该账户对象处于无锁状态。 线程 A 获取偏斜锁 线程 A 访问账户对象JVM将对象头部中的 Mark Word 设置为自己的线程 ID并将对象标记为“偏斜锁”并且记录线程 A 是偏斜锁的拥有者。线程 A 对账户余额进行修改操作完成后释放锁。 此时账户对象仍然是偏斜锁状态访问线程和持有偏斜锁的线程是同一个线程线程 A因此线程 A 可以直接访问账户对象而不需要进行互斥操作。 线程 B 尝试获取锁 线程 B 也需要对账户对象进行修改操作并尝试获取锁。JVM检测到线程 B 和持有偏斜锁的线程 A 不一致表示存在竞争。 撤销偏斜锁 JVM会撤销账户对象的偏斜锁状态将其转换为可重入锁或非重入锁。对象头部的 Mark Word 会被更新不再记录持有偏斜锁的线程 ID。 此时线程 B 成功获取了账户对象的锁并可以执行相应的操作。通过撤销偏斜锁保证了在多个线程竞争下只有一个线程能够持有锁并修改数据避免了数据的错误修改和不一致性。 偏斜锁允许单线程线程 A对账户对象进行快速访问提高了程序的执行效率。而当另一个线程线程 B尝试获取锁时偏斜锁会被撤销确保多线程环境下的数据安全性。这种机制保证了同一时间只有一个线程能够修改数据避免了竞争条件和数据一致性问题的产生。 18.1.4.4 可重入锁或非重入锁解释 可重入锁Reentrant Lock是一种线程同步机制也称为递归锁。它允许一个线程在持有锁的情况下再次请求获取同一个锁而不会造成死锁。 当一个线程获取到可重入锁后可以多次重复获取而不会被自己所持有的锁所阻塞。这意味着线程可以进入由同一个锁保护的代码块而不会对整个系统的状态造成死锁。 可重入锁通过维护一个持有计数器来实现。线程首次获取锁时计数器加一每次释放锁时计数器减一。只有计数器为零时锁才会完全释放其他线程才能获取该锁。 相比之下非重入锁Non-Reentrant Lock则不允许同一线程多次获取同一个锁。如果一个线程已经持有一个非重入锁再次请求获取同一个锁时会导致自己被阻塞形成死锁。 18.1.4.4.1 可重入锁的例子 有一个对象obj它有两个方法method1和method2其中method2需要在获取obj对象的锁后才能被调用。同时我们希望在method1中调用method2而不会导致死锁。 使用可重入锁可以很好地解决这个问题, import java.util.concurrent.locks.ReentrantLock;public class Example {// 定义可重入锁private ReentrantLock lock new ReentrantLock();public void method1() {lock.lock(); // 获取锁try {// do somethingmethod2(); // 调用method2// do something} finally {lock.unlock(); // 释放锁}}public void method2() {lock.lock(); // 再次获取锁try {// do something} finally {lock.unlock(); // 释放锁}} } ReentrantLock类提供了可重入锁的实现。method1先获取锁再通过调用method2获取同一个锁并在最后释放锁。虽然method2也获取了锁但由于是在同一个线程内部因此不会发生死锁。 相反如果使用非重入锁则会在第二次尝试获取锁时产生死锁问题。 18.1.4.5 互斥操作 互斥操作指的是一种通过对共享资源的访问进行限制以确保在同一时间内只有一个线程可以对该资源进行操作的机制。也就是说当一个线程获得了对某个资源的访问权时其他线程必须等待该线程释放资源后才能继续执行。 互斥操作的目的是避免多个线程同时对共享资源进行修改导致数据不一致或竞争条件的发生。在多线程环境下如果没有互斥操作多个线程可能同时读取或修改共享资源的值从而引发意料之外的错误和不一致性。 常见的互斥操作包括使用互斥锁Mutex或信号量Semaphore。互斥锁是一种排他锁它只允许一个线程在特定时刻获得锁资源其他线程需要等待。当一个线程完成对共享资源的操作后再释放锁其他线程才能获得锁并继续操作。 互斥操作的实现通常依赖于底层的操作系统提供的原子操作、临界区或其他同步机制。这样可以保证在并发环境中多个线程无法同时对关键资源进行操作确保了数据的一致性和线程的安全性。 互斥操作是一种通过限制并发访问共享资源来确保数据的一致性和线程安全的机制。它能够有效避免多个线程对共享资源的竞争和冲突提升多线程程序的正确性和可靠性。 18.1.4.6 偏向锁的优化点小结 偏斜锁的初衷是针对只有一个线程频繁访问同步块的场景而设计的偏斜锁允许该线程连续地获得锁而不需要进行互斥操作。这种连续获取锁的过程不会引起竞争和冲突所以不需要额外的互斥操作。 通过偏斜锁机制JVM可以避免频繁地进入和退出同步块所带来的性能损失。当只有一个线程在访问同步块时JVM会将该对象的锁状态设置为偏斜锁并将持有偏斜锁的线程ID记录下来。之后该线程再次访问该对象时会直接允许访问而无需进行互斥操作。 需要注意的是当其他线程尝试获取被偏斜锁占用的对象锁时偏斜锁会自动升级为轻量级锁或重量级锁从而引入互斥操作以保证线程安全。 当一个线程再次访问持有偏斜锁的对象时JVM会直接允许访问因为此时并没有其他线程与之竞争。这种情况下不需要互斥操作可以提升性能和效率。 18.1.5  轻量级锁 轻量级锁Lightweight Locking是Java虚拟机JVM中一种用于实现线程同步的机制旨在提高多线程并发性能。 当一个线程尝试获取一个对象的锁时JVM会将对象的锁状态切换为轻量级锁状态。轻量级锁的核心思想是尝试使用CASCompare and Swap操作对对象头中的Mark Word进行加锁。以下是轻量级锁的具体解释 初始状态对象的锁状态为无锁状态Unlocked对象头中的Mark Word存储了一些额外的信息比如指向当前线程栈中锁记录Lock Record的指针。 加锁操作当一个线程希望获取该对象的锁时它会尝试使用CAS操作将对象头的Mark Word设置为自己的线程ID表示该线程获取到了锁。这个CAS操作是为了确保只有一个线程能够成功修改Mark Word。 CAS操作成功如果CAS操作成功表示当前线程成功获取到了对象的轻量级锁。此时线程可以继续执行临界区代码不需要进一步同步操作。 CAS操作失败如果CAS操作失败表示有其他线程竞争同一个锁。这时候当前线程会尝试自旋Spin来等待锁的释放。自旋是一种忙等待的策略线程会反复检查对象头的Mark Word是否变为无锁状态。 自旋失败如果自旋超过了一定的次数或者达到了阈值表示自旋失败。这时JVM会将对象的锁状态升级为重量级锁Heavyweight Lock。升级为重量级锁涉及到线程阻塞和内核态的线程切换比较耗费系统资源。 通过使用轻量级锁JVM避免了无竞争情况下的阻塞与唤醒并减少了系统资源的消耗。只有在出现竞争的情况下才需要进行降级为重量级锁以保证线程安全性。 轻量级锁的具体实现和行为可能因不同的JVM版本和配置而有所差异。此外轻量级锁只适用于短期的同步对于长时间持有锁的情况JVM仍会将其升级为重量级锁以避免资源浪费。 18.1.6 重量级锁 当一个线程获取到对象的轻量级锁后如果它需要长时间持有该锁比如执行时间较长的临界区代码JVM会将其升级为重量级锁。这是因为长时间持有锁可能会导致其他线程长时间等待造成资源浪费。 理解这一点可以从以下几个方面考虑 自旋消耗资源轻量级锁使用自旋来等待锁的释放自旋是一种忙等待的策略线程反复检查对象头的Mark Word是否变为无锁状态。如果持有锁的线程长时间不释放锁那么其他线程会不断自旋等待这会导致CPU资源的浪费。 防止饥饿现象在长时间持有锁的情况下其他线程将无法获得锁这可能导致其他线程长时间等待甚至发生饥饿现象。为了避免这种情况JVM会将轻量级锁升级为重量级锁使用阻塞等待的方式确保其他线程能够公平地获得锁的机会。 重量级锁提供更强的互斥性重量级锁使用操作系统提供的底层机制如互斥量、信号量等来实现线程同步确保只有一个线程能够获取到锁。相比之下轻量级锁仅使用CAS操作进行加锁无法提供像操作系统级互斥那样的严格互斥性。对于长时间持有锁的情况为了避免竞争和数据不一致的问题JVM会将其升级为重量级锁。 轻量级锁适用于短期的同步对于长时间持有锁的情况JVM会将其升级为重量级锁以避免资源浪费和提供更强的互斥性保证线程之间的公平竞争和顺畅执行。 18.1.7 轻量级锁和重量级锁的比较 轻量级锁和重量级锁都是用于实现线程同步的机制但它们在性能和实现方式上存在差异。 在轻量级锁中当一个线程获取到锁时它会将对象头中的Mark Word修改为指向自己线程栈中锁记录的指针并使用CAS操作进行加锁。这种方式避免了线程阻塞和内核态的线程切换对于短期持有锁的情况下具有较好的性能表现。 然而当一个线程需要长时间持有锁时也就是执行时间较长的临界区代码时其他线程可能会长时间等待锁的释放进而导致饥饿现象的发生。这是因为其他线程持续自旋等待锁的释放而得不到执行的机会。 为了避免饥饿现象和资源浪费JVM会将轻量级锁升级为重量级锁。重量级锁是使用操作系统提供的底层机制如互斥量、信号量等实现的通过阻塞等待的方式确保其他线程能够公平地获得锁的机会。当一个线程持有重量级锁时其他线程将被阻塞不会再执行自旋等待从而避免了饥饿现象的发生。 重量级锁的实现方式可能涉及到线程的阻塞与唤醒、操作系统的内核态切换等因此会比轻量级锁产生更多的开销。所以在长时间持有锁的情况下使用重量级锁可以确保其他线程能够公平竞争锁的机会但也会导致一定的性能损失。 轻量级锁适用于短期持有锁的情况对于长时间持有锁的情况为了避免饥饿现象和资源浪费JVM会将轻量级锁升级为重量级锁使用阻塞等待的方式来保证公平竞争。重量级锁虽然确保了公平性但会带来一定的性能损失。 18.1.8 Java是否会进行锁的降级 Java 中锁的升级是指从轻量级锁升级为重量级锁的过程而锁的降级则指从重量级锁降级为轻量级锁或无锁状态。 Java 并没有提供直接的锁降级机制。一旦锁升级为重量级锁就不会再自动降级为轻量级锁或无锁状态。 这是因为重量级锁是通过操作系统提供的底层机制实现的与 Java 对象头中的标记字段无关。 只有当持有重量级锁的线程释放锁后其他线程才能获取锁不会再回到轻量级锁或无锁状态。 然而在某些特定的情况下我们可以手动进行锁的降级操作。 比如 如果一个线程在执行临界区代码时发现临界区的代码执行时间很短那么它可以选择将重量级锁降级为轻量级锁或无锁状态以减少性能开销。具体的做法是线程在临界区代码执行完毕后将对象头中的标记字段修改为指向自己线程栈中的锁记录进而实现锁的降级。 需要注意的是锁的降级需要程序员手动控制和管理必须保证在临界区代码执行期间没有其他线程竞争同一个锁。否则降级操作可能会导致数据不一致或并发问题。 Java 并没有内置的锁降级机制一旦锁升级为重量级锁就无法自动降级为轻量级锁或无锁状态。但在特定情况下可以手动进行锁的降级操作以减少性能开销。但需要注意保证降级操作的正确性和线程安全性。 例子 共享资源 counter 表示计数器多个线程需要并发地对其进行操作。我们使用一个重量级锁来保护这个计数器初始状态下所有线程都无法获取这个锁。 class Counter {private int count;private final Object lock new Object();public void increment() {synchronized (lock) {// 进入临界区域count;// 临界区域代码执行完毕可以尝试锁降级// 将锁降级为轻量级锁或无锁状态// 需要手动修改对象头中的标记字段lock.notifyAll(); // 唤醒等待该锁的线程}}public int getCount() {return count;} }使用了一个 synchronized 同步块来实现重量级锁其中对 counter 进行了自增操作并通过 lock.notifyAll() 来唤醒其他等待该锁的线程。 现在假设线程 A 获取到了锁并执行 increment 方法对 count 自增完毕后它选择将锁降级为轻量级锁或无锁状态 public class Main {public static void main(String[] args) {Counter counter new Counter();// 线程 AThread threadA new Thread(() - {synchronized (counter.lock) {// 进入临界区域counter.increment();// 临界区域代码执行完毕可以尝试锁降级// 将锁降级为轻量级锁或无锁状态// 需要手动修改对象头中的标记字段// 假设此时没有其他线程竞争同一个锁counter.lock.notifyAll(); // 唤醒等待该锁的线程}});// 线程 BThread threadB new Thread(() - {synchronized (counter.lock) {try {counter.lock.wait(); // 等待线程 A 完成临界区域代码} catch (InterruptedException e) {e.printStackTrace();}// 执行其他操作}});threadA.start();threadB.start();try {threadA.join();threadB.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Count: counter.getCount());} } 线程 A 获取到了锁并执行 increment 方法后它选择将对象头中的标记字段修改为指向自己线程栈中的锁记录这里是 counter.lock。然后调用 lock.notifyAll() 唤醒其他等待该锁的线程。 而线程 B 在获取到锁之后调用 lock.wait() 进入等待状态等待线程 A 执行完临界区域代码并唤醒它。 线程 A 将锁降级后线程 B 能够在没有竞争的情况下获取到锁进行后续操作。 需要注意的是锁的降级操作必须保证在临界区域代码执行期间没有其他线程竞争同一个锁否则可能会导致数据不一致或并发问题。 实际应用中需要仔细考虑锁的升级和降级策略并确保线程安全性。 18.1.9 临界区域Critical Section解释 指一段代码其中涉及对共享资源的访问或操作。在多线程编程中当多个线程并发地访问共享资源时为了保证数据的一致性和正确性需要将对共享资源的访问限制在临界区域内。 临界区域代码是指用于对共享资源进行访问或操作的代码片段。它是一个被保护起来的区域同一时刻只能有一个线程进入并执行其中的代码。其他线程需要等待当前线程执行完毕并退出临界区域后才能进入。 临界区域的目的是确保多个线程不会同时对共享资源进行写操作避免出现数据竞争和不一致的情况。通过限制对临界区域的互斥访问可以保证在同一时间只有一个线程在执行对共享资源的操作从而维护数据的有效性。 例子代码中count 的操作就是一个临界区域代码。在 increment 方法中使用 synchronized 关键字将这段代码标记为临界区域以保证同一时间只有一个线程可以执行该操作。其他线程在执行此段代码之前会被阻塞直到当前线程执行完毕并释放锁后才能继续执行。 所以临界区域代码指的是多线程并发访问共享资源时需要保护的、只允许一个线程进入执行的代码片段。它起到了保护共享资源的作用确保并发操作的正确性和数据的一致性。
http://www.dnsts.com.cn/news/69752.html

相关文章:

  • 网站建设的基本步骤和过程asp.net 跳转别的网站
  • 泰安商城网站开发设计莱芜网
  • ps做网站对齐技巧重庆装修公司口碑最好的是哪家
  • 网站建设好么wordpress地址修改
  • 网站标题应该怎么做SEO优化国内优秀企业网站设计欣赏
  • 建站平台入口7游网页游戏平台
  • 申请免费网站主页空间黑龙江 网站建设
  • php网站开发是学什么的加盟做网站
  • 商务网站建设体会程序员做网站类网站
  • 湘西网站建设公司网页上做网会员网站备案怎么写
  • 短网址网站做图表的网站知乎
  • 大气环保网站模板网站关键字如何选择
  • 深圳外贸网站建设网站开发是做什么的
  • 如何建网站模板wordpress取消categore
  • 怎么在百度上建立网站苏州制作公司网站的
  • 网站开发要什么软件有哪些如何打开网页源代码
  • 企业建设网站的目的和意义关键词优化话术
  • 网站规划的公司wordpress超级大菜单如何使用
  • 济南建设工程业绩公示的网站外贸公司取名字参考大全
  • 常州门户网站建设四川成都现在可以去吗
  • 昆明企业网站设计网站建设及安全规范
  • 国外做旅游攻略的网站好西安千秋网络科技有限公司
  • 产品设计网上接单河北seo人员
  • 西安网站开发培训WordPress 主题选项框架
  • 一个外国人做的汉子 网站现有电商平台
  • 开企网站建设长沙做最好网站
  • 三水建设局网站嵌入式软件开发工程师工作内容
  • 多城市网站设计企业年金保险是一种什么保险
  • 电子商务企业网站建设计划书html5网站模板源码
  • 青岛专业网站开发公司wordpress设置固定链接伪静态