了解多线程锁 淩亂°似流年 2022-09-08 00:12 204阅读 0赞 # 原子类 # java 5中的java.util.concurrent包下面有一个atomic子包,其中有几个以Atomic打头的类,例如AtomicInteger和AtomicLong。它们利用了现代处理器的特性,可以用非阻塞的方式完成原子操作。 主要有: AtomicBoolean AtomicInteger AtomicIntegerArray AtomicIntegerFieldUpdater AtomicLong AtomicLongArray AtomicLongFieldUpdater AtomicMarkableReference AtomicReference AtomicReferenceArray AtomicReferenceFieldUpdater AtomicStampedReference DoubleAccumulator DoubleAdder LongAccumulator LongAdder ## AtomicBoolean ## public class AtomicBoolean implements java.io.Serializable { private static final long serialVersionUID = 4654671469794556979L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicBoolean.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * Creates a new {@code AtomicBoolean} with the given initial value. * * @param initialValue the initial value */ public AtomicBoolean(boolean initialValue) { value = initialValue ? 1 : 0; } 主要方法: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70] get,set方法因为不依赖于当前值,所以直接可以进行操作(有value的volatile保证可见性) public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); } //boolean底层还是通过int来实现的,所以,这里通过设置的int型的值 public final boolean getAndSet(boolean newValue) { boolean prev; do { prev = get(); } while (!compareAndSet(prev, newValue)); return prev; } 对于依赖当前值的操作,则通过unsafe来进行操作 public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); } //boolean底层还是通过int来实现的,所以,这里通过设置的int型的值 public final boolean getAndSet(boolean newValue) { boolean prev; do { prev = get(); } while (!compareAndSet(prev, newValue)); return prev; } ## AtomicInteger ## public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } 基本的结构和AtomicBoolean相同,volatile的value,get/set直接操作value的值 public final int get() { return value; } /** * Sets to the given value. * * @param newValue the new value */ public final void set(int newValue) { value = newValue; } public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * Atomically decrements by one the current value. * * @return the previous value */ public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } 其他的操作都是直接通过unsafe操作的,没什么大多复杂的实现 ## AtomicLong ## public class AtomicLong extends Number implements java.io.Serializable { private static final long serialVersionUID = 1927816293512124184L; // setup to use Unsafe.compareAndSwapLong for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; /** * Records whether the underlying JVM supports lockless * compareAndSwap for longs. While the Unsafe.compareAndSwapLong * method works in either case, some constructions should be * handled at Java level to avoid locking user-visible locks. */ static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); /** * Returns whether underlying JVM supports lockless CompareAndSet * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS. */ private static native boolean VMSupportsCS8(); static { try { valueOffset = unsafe.objectFieldOffset (AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile long value; 和atomicInteger不同的是,atomicLong因为是8字节长度的,(底层机器宽度可能是4个字节长度的,因此,还需要做另外的通过) 其他和atomicInteger差不多 # synchronized # synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号\{\}括起来的代码,作用的对象是调用这个代码块的对象; 2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。 3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象 ## 案例: ## 使用手机发短信或发邮件 注意两个点:锁的对象以及锁的范围 ### 案例1先打印短信还是邮件 ### 手机有发送短信和发邮件的功能,创建两个线程分别发送短信和邮件,是先打印短信还是邮件呢? package com.dongguo.synclock; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone1.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public synchronized void sendSMS() throws Exception { System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendSMS ------sendEmail sendSMS,sendEmail依次打印 ### 案例二:停4秒在短信方法内,先打印短信还是邮件 ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone1.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendSMS ------sendEmail 线程阻塞了4秒后sendSMS,sendEmail依次打印 案例1,2锁的都是当前对象Phone1 ### 案例三:新增普通的hello方法,是先打短信还是hello ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { // phone1.sendEmail(); phone1.getHello(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------getHello ------sendSMS getHello是非同步方法,在线程A阻塞时,线程B会执行getHello(),线程A阻塞4秒后执行sendSMS() ### 案例4:现在有两部手机,先打印短信还是邮件 ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone2.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendEmail ------sendSMS 线程A使用phone1发送短信,线程B使用phone2发送邮件,由于使用的不是同一个锁 线程B先执行sendEmail,线程A阻塞4秒后执行sendSMS 有两个Phone对象,两个线程锁的不是同一个Phone对象 ### 案例5:两个静态同步方法,1部手机,先打印短信还是邮件 ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone1.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public static synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public static synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendSMS ------sendEmail ### 案例6:两个静态同步方法,2部手机,先打印短信还是邮件 ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone2.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public static synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public static synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendSMS ------sendEmail 案例5,6都是线程阻塞4秒后依次打印 静态同步方法锁的是Phone.Class,而不是具体的Phone1,Phone1对象 ### 案例7:1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件 ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone1.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public static synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendEmail ------sendSMS sendEmail先打印,等待4秒打印出sendSMS ### 案例8:1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件 ### package com.dongguo.synclock; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-12:46 * @description: */ public class ThreadDemo { public static void main(String[] args) throws Exception { Phone phone1 = new Phone(); Phone phone2 = new Phone(); new Thread(()->{ try { phone1.sendSMS(); } catch (Exception e) { e.printStackTrace(); } },"ThreadA").start(); Thread.sleep(100); new Thread(()->{ try { phone2.sendEmail(); } catch (Exception e) { e.printStackTrace(); } },"ThreadB").start(); } } class Phone { public static synchronized void sendSMS() throws Exception { //停留四秒 TimeUnit.SECONDS.sleep(4); System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } 运行结果: ------sendEmail ------sendSMS 与案例7一样 两个线程锁的对象不一样 synchronized实现同步的基础:Java中的每一个对象都可以作为锁。 具体表现为以下3种形式。 ,对于普通同步方法,锁是当前实例对象。 对于静态同步方法,锁是当前类的Class对象。 对于同步方法块,锁是Synchonized括号里配置的对象 ## synchronized的缺陷 ## synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢? 如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有; 2)线程执行发生异常,此时JVM会让线程自动释放锁。 那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。 再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。 但是采用synchronized关键字来实现同步的话,就会导致一个问题: 如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。 因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。 另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。 总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点: 1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问; 2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。 # 公平锁和非公平锁 # ## ReentrantLock ## 在Lock锁中案例: 实现3个售票员卖出100张票的案例 package com.dongguo.lock; import java.util.concurrent.locks.ReentrantLock; /** * @author Dongguo * @date 2021/8/23 0023-19:19 * @description: 使用Lock */ public class Ticket { private int number = 100; //创建可重入锁ReentrantLock private final ReentrantLock lock = new ReentrantLock(); public void saleTicket() { //上锁 lock.lock(); if (number > 0) { number--; System.out.println(Thread.currentThread().getName() + "卖票成功,还剩:" + number + "张"); } //解锁 lock.unlock(); } } package com.dongguo.lock; /** * @author Dongguo * @date 2021/8/23 0023-19:03 * @description: 售票 */ public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); //使用lambda表达式 new Thread(()->{ for (int i = 0; i < 100; i++) { ticket.saleTicket(); } }, "ThreadA").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { ticket.saleTicket(); } }, "ThreadB").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { ticket.saleTicket(); } }, "ThreadC").start(); } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 1] 所有的票都被线程A卖掉了 ReentrantLock既可以是非公平锁,也可以是公平锁 创建ReentrantLock时可以传入一个boolean 值 true为公平锁,false为非公平锁,默认为false是非公平锁 非公平锁有可能出现线程饿死的现象 线程A一直运行,线程B、线程C没有运行 private final Sync sync; ... public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } package com.dongguo.sync.saleticket1; import java.util.concurrent.locks.ReentrantLock; /** * @author Dongguo * @date 2021/8/23 0023-19:19 * @description: 使用synchronized */ public class Ticket { private int number = 100; //创建可重入锁ReentrantLock private final ReentrantLock lock = new ReentrantLock(true); public void saleTicket() { //上锁 lock.lock(); if (number > 0) { number--; System.out.println(Thread.currentThread().getName() + "卖票成功,还剩:" + number + "张"); } //解锁 lock.unlock(); } } ReentrantLock修改为公平锁 ,每个线程都有运行的机会 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 2] 非公平锁:效率高,有可能会出现线程饿死的现象 公平锁:效率相对低,每个线程都会有运行的机会 ### ReentrantLock非公平锁 ### static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } ### ReentrantLock公平锁 ### static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } # 可重入锁 # synchronized 和Lock都是可重入锁,也叫递归锁,即一个线程可以重复获取同一把锁 synchronized 是隐式锁,Lock是显式锁 即synchronized 加锁解锁自动完成 Lock加锁解锁要手动完成 ## synchronized 可重入锁 ## package com.dongguo.sync; /** * @author Dongguo * @date 2021/8/24 0024-13:58 * @description:synchronized 可重入锁 */ public class SyncLockDemo { public static void main(String[] args) { Object object = new Object(); new Thread(() -> { synchronized (object) { System.out.println(Thread.currentThread().getName() + "-外层"); synchronized (object) { System.out.println(Thread.currentThread().getName() + "-中层"); synchronized (object) { System.out.println(Thread.currentThread().getName() + "-内层"); } } } }, "ThreadA").start(); } } 运行结果: ThreadA-外层 ThreadA-中层 ThreadA-内层 ## synchronized 递归锁 ## package com.dongguo.sync; /** * @author Dongguo * @date 2021/8/24 0024-13:58 * @description: synchronized 可重入锁 */ public class SyncLockDemo { public static void main(String[] args) { new SyncLockDemo().add(); } public long number = 0; //递归 public synchronized void add(){ number++; System.out.println(number); add(); } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 3] ## Lock可重入锁 ## package com.dongguo.sync; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author Dongguo * @date 2021/8/24 0024-13:58 * @description: Lock 可重入锁 */ public class SyncLockDemo { public static void main(String[] args) { Lock lock = new ReentrantLock(); new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "-外层"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "-中层"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "-内层"); } finally { lock.unlock(); } } finally { lock.unlock(); } } finally { lock.unlock(); } }, "ThreadA").start(); } } 运行结果: ThreadA-外层 ThreadA-中层 ThreadA-内层 ## Lock递归锁 ## package com.dongguo.sync; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author Dongguo * @date 2021/8/24 0024-13:58 * @description: 死锁产生 */ public class SyncLockDemo { public static void main(String[] args) { Lock lock = new ReentrantLock(); new Thread(() -> { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "-外层"); try { lock.lock(); System.out.println(Thread.currentThread().getName() + "-内层"); } finally { //不去释放锁 // lock.unlock(); } } finally { lock.unlock(); } }, "ThreadA").start(); new Thread(()->{ lock.lock(); System.out.println(Thread.currentThread().getName() + "-执行"); lock.unlock(); },"ThreadB").start(); } } ThreadA没有释放锁,ThreadB获取不到锁一直阻塞产生死锁 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 4] # 死锁 # 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。 ## 死锁的产生的一些特定条件: ## 1、互斥条件:即一个资源只能被一个线程占用,直到被该线程释放 。 2、请求和保持条件:一个线程因请求资源而发生阻塞时,对已获得的资源保持不放。 3、不剥夺条件:一个资源被一个线程占用时,其他线程都无法对这个资源剥夺占用。 4、循环等待条件:当发生死锁时,所等待的线程会形成一个环路(类似于死循环),造成永久阻塞。 造成死锁必须达到4个条件,避免死锁,只需不满足其中一个条件即可,前三个都是作为锁要符合的条件,所以要避免死锁就要打破第四个条件 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 5] package com.dongguo.sync; import jdk.internal.dynalink.beans.StaticClass; import java.util.concurrent.TimeUnit; /** * @author Dongguo * @date 2021/8/24 0024-14:25 * @description: 模拟死锁 */ public class DeadLock { static Object obj1 = new Object(); static Object obj2 = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (obj1) { System.out.println(Thread.currentThread().getName() + "持有锁1,试图获取锁2"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2) { System.out.println(Thread.currentThread().getName() + "持有锁1,获取锁2成功"); } } }, "ThreadA").start(); new Thread(() -> { synchronized (obj2) { System.out.println(Thread.currentThread().getName() + "持有锁2,试图获取锁1"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1) { System.out.println(Thread.currentThread().getName() + "持有锁2,获取锁1成功"); } } }, "ThreadB").start(); } } ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 6] ## 验证是否死锁 ## C:\Users\Administrator>jps 4288 RemoteMavenServer36 10968 Jps 17352 Launcher 13212 DeadLock 13436 C:\Users\Administrator>jstack 13212 2021-08-24 14:32:51 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.301-b09 mixed mode): "DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x000002b418b40000 nid=0x3fa0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "ThreadB" #12 prio=5 os_prio=0 tid=0x000002b42dc62800 nid=0x2ea4 waiting for monitor entry [0x000000c4c43ff000] java.lang.Thread.State: BLOCKED (on object monitor) at com.dongguo.sync.DeadLock.lambda$main$1(DeadLock.java:39) - waiting to lock <0x00000000eb4b5288> (a java.lang.Object) - locked <0x00000000eb4b5298> (a java.lang.Object) at com.dongguo.sync.DeadLock$$Lambda$2/1480010240.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "ThreadA" #11 prio=5 os_prio=0 tid=0x000002b42dc1a800 nid=0x2a00 waiting for monitor entry [0x000000c4c42ff000] java.lang.Thread.State: BLOCKED (on object monitor) at com.dongguo.sync.DeadLock.lambda$main$0(DeadLock.java:26) - waiting to lock <0x00000000eb4b5298> (a java.lang.Object) - locked <0x00000000eb4b5288> (a java.lang.Object) at com.dongguo.sync.DeadLock$$Lambda$1/2074407503.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000002b42d921800 nid=0x43c0 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000002b42ce5f800 nid=0xc0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000002b42ce5e000 nid=0xc8c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000002b42ce5c800 nid=0x2830 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000002b42ce59000 nid=0x41e4 runnable [0x000000c4c3cfe000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:171) at java.net.SocketInputStream.read(SocketInputStream.java:141) at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) - locked <0x00000000eb492430> (a java.io.InputStreamReader) at java.io.InputStreamReader.read(InputStreamReader.java:184) at java.io.BufferedReader.fill(BufferedReader.java:161) at java.io.BufferedReader.readLine(BufferedReader.java:324) - locked <0x00000000eb492430> (a java.io.InputStreamReader) at java.io.BufferedReader.readLine(BufferedReader.java:389) at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:48) "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000002b42cd7e800 nid=0x3e44 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000002b42cdd5000 nid=0xde0 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000002b42cd52800 nid=0x3004 in Object.wait() [0x000000c4c39fe000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000eb308ee0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144) - locked <0x00000000eb308ee0> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216) "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000002b42cd4b800 nid=0x884 in Object.wait() [0x000000c4c38ff000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000eb306c00> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x00000000eb306c00> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "VM Thread" os_prio=2 tid=0x000002b42cd21800 nid=0x324 runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000002b418b55000 nid=0xd9c runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000002b418b56800 nid=0x4034 runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000002b418b58000 nid=0x431c runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000002b418b59000 nid=0x3b54 runnable "VM Periodic Task Thread" os_prio=2 tid=0x000002b42d925000 nid=0x4108 waiting on condition JNI global references: 317 Found one Java-level deadlock: ============================= "ThreadB": waiting to lock monitor 0x000002b42cd4fa18 (object 0x00000000eb4b5288, a java.lang.Object), which is held by "ThreadA" "ThreadA": waiting to lock monitor 0x000002b42cd522a8 (object 0x00000000eb4b5298, a java.lang.Object), which is held by "ThreadB" Java stack information for the threads listed above: =================================================== "ThreadB": at com.dongguo.sync.DeadLock.lambda$main$1(DeadLock.java:39) - waiting to lock <0x00000000eb4b5288> (a java.lang.Object) - locked <0x00000000eb4b5298> (a java.lang.Object) at com.dongguo.sync.DeadLock$$Lambda$2/1480010240.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "ThreadA": at com.dongguo.sync.DeadLock.lambda$main$0(DeadLock.java:26) - waiting to lock <0x00000000eb4b5298> (a java.lang.Object) - locked <0x00000000eb4b5288> (a java.lang.Object) at com.dongguo.sync.DeadLock$$Lambda$1/2074407503.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock. C:\Users\Administrator> tips:配合好环境变量 当然也可以使用jvisualvm ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 7] 点击线程dump ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 8] 当然了企业中会使用更人性化的可视化工具 比如阿里巴巴的Arthas # 扩展 # ## 可中断锁 ## 可中断锁:顾名思义,就是可以相应中断的锁。 在Java中,synchronized就不是可中断锁,而Lock是可中断锁。 如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。 lockInterruptibly()的用法体现了Lock的可中断性 ## 读写锁 ## 读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。 正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。 ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。 可以通过readLock()获取读锁,通过writeLock()获取写锁 ## 偏向锁 ## Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用) 因此 流程是这样的 偏向锁->轻量级锁->重量级锁 锁存在Java对象头里。如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,一字宽等于四字节,即32bit。 机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线程再次请求这个锁时,请求计数器就会增加,当该线程退出syncronized块时,计数器减1,当计数器为0时,锁被释放(这就保证了锁是可重入的,不会发生死锁的情况)。 偏向锁,简单的讲,就是在锁对象的对象头中有个ThreaddId字段,这个字段如果是空的,第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提高了效率。 但是偏向锁也有一个问题,就是当锁有竞争关系的时候,需要解除偏向锁,使锁进入竞争的状态。 具体图片如下 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 9] ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 10] 偏向锁的抢占,其实就是两个进程对锁的抢占,在synchrnized锁下表现为轻量锁方式进行抢占 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 11] 注意这时只是偏向锁的释放,之后会进入到轻量级锁阶段,两个线程进入锁竞争状态,一个具体例子可以参考synchronized锁机制。 注:synchronized锁升级流程如下 第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁” 第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。 第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord, 第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋 第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败 第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己 总结: 偏向锁,其实是无锁竞争下可重入锁的简单实现 ## 乐观锁,悲观锁 ## 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 一个典型的倚赖数据库的悲观锁调用: select \* from account where name=”Erica” for update 这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。 乐观锁(Optimistic Locking)相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。 乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。 对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一version字段,当前值为1;而当前帐户余额字段(balance)为50)。 2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并 从其帐户余额中扣除100-$20)。 3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣 除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大 于数据库记录当前版本,数据被更新,数据库记录version更新为2。 4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数 据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作员A的操作结果的可能。 从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员A和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。 需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如 将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。 ## java8新增的乐观锁StampedLock ## java8新的API,用户补充reenteredlock(对于reenteredlock来说,如果读线程非常多,则有可能导致写请求饥饿) StampedLock有三种模式,如果获取锁成功,则会返回一个时间戳,该时间戳在后续的释放的时候必须当参数传入。 写模式: writeLock() 读模式: readLock() 乐观读模式: tryOptimisticRead() 返回非0的时间戳当且仅当这个锁当前不处于写模式下。如果从锁获取到调用validate(long) 方法时,该锁都没有被写请求获取过(即判断这段时间内读取的数据是否有效)。This mode can be thought of as an extremely weak version of a read-lock, that can be broken by a writer at any time。该模式用于代码非常短,且应用对吞吐量有很高的需求。 同时,该类支持锁的升级: tryConvertToWriteLock(long) attempts to “upgrade” a mode, returning a valid write stamp 如果满足这3个条件中的一个: (1) already in writing mode (2) in reading mode and there are no other readers or (3) in optimistic mode and the lock is available. The forms of these methods are designed to help reduce some of the code bloat that otherwise occurs in retry-based designs. 另外,注意:这个锁不是可重入的。 They are not reentrant, so locked bodies should not call other unknown methods that may try to re-acquire locks (although you may pass a stamp to other methods that can use or convert it). [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70]: /images/20220829/8faca3c9ce124e84a91d8ae49561fa73.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 1]: /images/20220829/5b0cdf43aab942c6876140ee36a2fa2e.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 2]: /images/20220829/4231db916bd2498d83888480f49795d1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 3]: /images/20220829/bf2305e4ff074bf5b276170770ac1ed1.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 4]: /images/20220829/c44d908cb3c343e0ab3d33b2a3f3ae5c.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 5]: /images/20220829/e2bcdbbadbff42319f255b148d943b6d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 6]: /images/20220829/3a3719cc492d4c5fbf64122aadaca011.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 7]: /images/20220829/c7c8de78526449c395b3e332b5dffb5a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 8]: /images/20220829/eb0dcda6eacb426db79fbaa52d00d51f.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 9]: /images/20220829/67ccb6e0cce0431382db1f7d25f34fc0.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 10]: /images/20220829/337a065232684735bda6f027de4883de.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NDUwMDg5_size_16_color_FFFFFF_t_70 11]: /images/20220829/4a1a815e2753457caed0b990fedb55c2.png
相关 多线程之锁 > 1.一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchornized方法了,其他的线程都只能等待,换句话说,某一个时刻 布满荆棘的人生/ 2023年08月17日 17:07/ 0 赞/ 43 阅读
相关 【Java多线程】了解线程的锁池和等待池概念 文章目录 一.内置锁 二.线程状态 线程的5种状态 线程状态图 线程释放锁的情况 向右看齐/ 2022年12月11日 10:27/ 0 赞/ 352 阅读
相关 多线程-互斥锁 include<stdio.h> include<stdlib.h> include<Windows.h> //HANDLE mute 男娘i/ 2022年09月25日 02:26/ 0 赞/ 337 阅读
相关 了解多线程锁 原子类 java 5中的java.util.concurrent包下面有一个atomic子包,其中有几个以Atomic打头的类,例如AtomicInteger和Atomi 淩亂°似流年/ 2022年09月08日 00:12/ 0 赞/ 205 阅读
相关 多线程死锁 / 死锁:二个线程同时锁住一个变量时。 锁住一个变量之后,尽快操作完成解锁,解锁之前不要再锁住其它变量,否则会互锁(死锁)。 / 川长思鸟来/ 2022年07月15日 14:28/ 0 赞/ 329 阅读
相关 多线程-死锁 / 死锁:常见情景之一:同步的嵌套。 / class Ticket implements Runnable { pr 向右看齐/ 2022年06月02日 07:57/ 0 赞/ 304 阅读
相关 多线程死锁 峨眉山月半轮秋,影入平羌江水流 Java线程的死锁一直都是经典的多线程问题;因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都不能继续执行; 示例代码: た 入场券/ 2022年05月24日 04:09/ 0 赞/ 419 阅读
相关 多线程锁 多线程锁主要有synchronized和lock 区别: synchronized粒度大,释放锁只有两种情况,1,被锁住的代码执行完毕,2抛异常JVM会主动开锁。 浅浅的花香味﹌/ 2022年04月15日 03:26/ 0 赞/ 263 阅读
相关 多线程死锁 同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。 雨点打透心脏的1/2处/ 2022年03月08日 07:50/ 0 赞/ 387 阅读
相关 Java -- 了解多线程 一,线程的概念 1. 进程 进程是程序的一次动态执行过程,他对应了从代码加载,执行到执行完毕的一个完整过程,这个过程也是进行本身从产生,发展至消亡的过程,操 野性酷女/ 2021年11月02日 05:01/ 0 赞/ 278 阅读
还没有评论,来说两句吧...