@@ -68,9 +68,11 @@ volatile 关键字可以保证了变量的修改对所有线程立即可见,
68
68
69
69
#### 如何理解协程?
70
70
71
- 协程通常被视为比线程更轻量级的并发单元,它们主要在一些支持异步编程模型的语言中得到了原生支持,如 Kotlin、Go 等 。
71
+ 协程被视为比线程更轻量级的并发单元,可以在单线程中实现并发执行,由我们开发者显式调度 。
72
72
73
- 不过,我们可以使用 CompletableFuture 来模拟协程式的异步执行任务。
73
+ 我们可以使用 CompletableFuture 来模拟协程式的异步执行任务。比如说我们创建两个 CompletableFuture 对象来异步执行两个简单的数值返回任务。这两个任务都会休眠 1 秒钟来模拟耗时计算。
74
+
75
+ 然后我们使用 thenCombine 方法来合并这两个任务的结果。最后,我们通过 get 方法等待最终结果的完成,并打印出来。
74
76
75
77
``` java
76
78
class CompletableFutureExample {
@@ -105,9 +107,7 @@ class CompletableFutureExample {
105
107
}
106
108
```
107
109
108
- 在这个示例中,我们创建了两个 CompletableFuture 对象来异步执行两个简单的数值返回任务。这两个任务都会休眠 1 秒钟来模拟耗时计算。
109
-
110
- 然后我们使用 thenCombine 方法来合并这两个任务的结果。最后,我们通过 get 方法等待最终结果的完成,并打印出来。
110
+ 协程在用户态进行调度,避免了线程切换时的内核态开销。
111
111
112
112
#### 说说线程的共享内存?
113
113
@@ -136,6 +136,7 @@ class CompletableFutureExample {
136
136
> 8 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的招商银行面经同学 6 招银网络科技面试原题:进程和线程的区别?
137
137
> 9 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的用友面试原题:线程和进程的区别
138
138
> 10 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的vivo 面经同学 10 技术一面面试原题:线程的概念,线程有哪些状态
139
+ > 11 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的海康威视同学 4面试原题:对协程的了解,为什么协程比线程还有更低的资源消耗
139
140
140
141
### 3.说说线程有几种创建方式?
141
142
@@ -1663,33 +1664,29 @@ GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https
1663
1664
1664
1665
## 锁
1665
1666
1666
- ### 26.synchronized 用过吗?怎么使用?
1667
+ ### 26.synchronized 用过吗?
1667
1668
1668
- 在 Java 中,synchronized 是最常用的锁,它使用简单,并且可以保证线程安全,避免多线程并发访问时出现数据不一致的情况 。
1669
+ 在 Java 中,使用 synchronized 是最常用的上锁方式,直接在方法上加关键字就可以保证线程安全 。
1669
1670
1670
- 随着 JDK 版本的进化,synchronized 的性能也得到了进一步的提升,不再像以前样重量级了 。
1671
+ 并且随着 JDK 版本的进化,synchronized 的性能也得到了进一步的提升,比如 JDK 1.6 中引入的偏向锁和轻量级锁 。
1671
1672
1672
- synchronized 可以用在方法和代码块中。
1673
-
1674
- ①、修饰方法
1673
+ synchronized 可以用在方法上,表示该方法是同步的,线程在执行这个方法的时候,其他线程不能同时执行,需要等待锁释放。
1675
1674
1676
1675
``` java
1677
1676
public synchronized void increment() {
1678
1677
this . count++ ;
1679
1678
}
1680
1679
```
1681
1680
1682
- 当在方法声明中使用了 synchronized 关键字,就表示该方法是同步的,也就是说,线程在执行这个方法的时候,其他线程不能同时执行,需要等待锁释放。
1683
-
1684
- 如果是静态方法的话,锁的是这个类的 Class 对象,因为静态方法是属于类级别的。
1681
+ 如果 synchronized 修饰的是静态方法,上锁的是这个类的 Class 对象,因为静态方法是属于类级别的。
1685
1682
1686
1683
``` java
1687
1684
public static synchronized void increment() {
1688
1685
count++ ;
1689
1686
}
1690
1687
```
1691
1688
1692
- ②、修饰代码块
1689
+ synchronized 关键字还可以用在代码块上,表示对这个代码块上锁。
1693
1690
1694
1691
``` java
1695
1692
public void increment() {
@@ -1705,8 +1702,6 @@ public void increment() {
1705
1702
1706
1703
### 27.synchronized 的实现原理?
1707
1704
1708
- #### synchronized 是怎么加锁的呢?
1709
-
1710
1705
synchronized 是 JVM 帮我们实现的,因此在使用的时候不用手动去 lock 和 unlock,JVM 会帮我们自动加锁和解锁。
1711
1706
1712
1707
①、synchronized 修饰代码块时,JVM 会通过 ` monitorenter ` 、` monitorexit ` 两个指令来实现同步:
@@ -1818,12 +1813,10 @@ synchronized 之所以支持可重入,是因为 Java 的对象头包含了一
1818
1813
1819
1814
> 1 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的快手面经同学 5 面试原题:synchronized 可重入锁怎么实现的
1820
1815
1821
- ### 29.锁升级? synchronized 优化了解吗 ?
1816
+ ### 29.synchronized 锁升级了解吗 ?
1822
1817
1823
1818
推荐阅读:[ 偏向锁、轻量级锁、重量级锁到底是什么?] ( https://javabetter.cn/thread/synchronized.html )
1824
1819
1825
- #### 什么是锁升级?
1826
-
1827
1820
锁升级是 Java 虚拟机中的一个优化机制,用于提高多线程环境下 synchronized 的并发性能。锁升级涉及从较轻的锁状态(如无锁或偏向锁)逐步升级到较重的锁状态(如轻量级锁和重量级锁),以适应不同程度的竞争情况。
1828
1821
1829
1822
Java 对象头里的 ` Mark Word ` 会记录锁的状态,一共有四种状态:
@@ -1860,7 +1853,7 @@ Java 对象头里的 `Mark Word` 会记录锁的状态,一共有四种状态
1860
1853
1861
1854
如果成功,该线程持有锁;如果失败,表示有其他线程竞争,锁会升级为重量级锁。
1862
1855
1863
- ** ③、自旋锁 ** :当线程尝试获取轻量级锁失败时,它会进行自旋,即循环检查锁是否可用,以避免立即进入阻塞状态。
1856
+ ** ③、自旋 ** :当线程尝试获取轻量级锁失败时,它会进行自旋,即循环检查锁是否可用,以避免立即进入阻塞状态。
1864
1857
1865
1858
自旋的次数不是固定的,而是根据之前在同一个锁上的自旋时间和锁的状态动态调整的。
1866
1859
@@ -1909,6 +1902,7 @@ Java 对象头里的 `Mark Word` 会记录锁的状态,一共有四种状态
1909
1902
> 3 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的去哪儿面经同学 1 技术二面面试原题:锁升级,synchronized 底层,会不会牵扯到 os 层面
1910
1903
> 4 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的快手同学 2 一面面试原题:锁升级的过程?
1911
1904
1905
+
1912
1906
### 30.synchronized 和 ReentrantLock 的区别?
1913
1907
1914
1908
[ synchronized] ( https://javabetter.cn/thread/synchronized-1.html ) 是一个关键字,[ ReentrantLock] ( https://javabetter.cn/thread/reentrantLock.html ) 是 Lock 接口的一个实现。
@@ -2490,6 +2484,61 @@ class DeadLockDemo {
2490
2484
- ** 自旋锁** :自旋锁是一种锁的实现方式,它不会让线程进入睡眠状态,而是一直循环检测锁是否被释放。自旋锁适用于锁的持有时间非常短的情况。
2491
2485
- 信号量:信号量([ Semaphore] ( https://javabetter.cn/thread/CountDownLatch.html ) )本质上是一个计数器,用于为多个进程提供共享数据对象的访问。
2492
2486
2487
+ #### 说说自旋锁?
2488
+
2489
+ 自旋锁是指当线程尝试获取锁时,如果锁已经被占用,线程不会立即阻塞,而是** 通过自旋** ,也就是循环等待的方式不断尝试获取锁,通常依赖于 CAS 来实现。
2490
+
2491
+ ```
2492
+ 线程1 线程2
2493
+ | |
2494
+ | 获取锁成功 | 尝试获取锁
2495
+ |------------>|(锁已被占用,自旋等待)
2496
+ | 释放锁 |
2497
+ |<------------| 获取锁成功
2498
+ | |
2499
+ ```
2500
+
2501
+ 自旋锁的优点是避免线程切换,缺点是如果锁被占用时间过长,会导致线程空转,浪费 CPU 资源。
2502
+
2503
+ ``` java
2504
+ class SpinLock {
2505
+ private AtomicBoolean lock = new AtomicBoolean (false );
2506
+
2507
+ public void lock () {
2508
+ while (! lock. compareAndSet(false , true )) {
2509
+ // 自旋等待,不断尝试获取锁
2510
+ }
2511
+ }
2512
+
2513
+ public void unlock () {
2514
+ lock. set(false );
2515
+ }
2516
+
2517
+ public static void main (String [] args ) {
2518
+ SpinLock spinLock = new SpinLock ();
2519
+
2520
+ Runnable task = () - > {
2521
+ spinLock. lock();
2522
+ try {
2523
+ System . out. println(Thread . currentThread(). getName() + " 获取到锁" );
2524
+ } finally {
2525
+ spinLock. unlock();
2526
+ }
2527
+ };
2528
+
2529
+ Thread t1 = new Thread (task);
2530
+ Thread t2 = new Thread (task);
2531
+
2532
+ t1. start();
2533
+ t2. start();
2534
+ }
2535
+ }
2536
+ ```
2537
+
2538
+ 默认情况下,自旋锁会一直等待,直到获取到锁为止。但是,在实际开发中,通常会设置自旋次数或者超时时间。如果超过阈值,线程可以选择放弃锁或者进入阻塞状态。
2539
+
2540
+
2541
+
2493
2542
#### 互斥和同步在时间上有要求吗?
2494
2543
2495
2544
互斥和同步在时间上是有一定要求的,因为它们都涉及到对资源的访问顺序和时机控制。
@@ -2528,6 +2577,7 @@ class SyncExample {
2528
2577
2529
2578
> 1 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的科大讯飞非凡计划研发类面经原题:聊聊线程同步
2530
2579
> 2 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的拼多多面经同学 4 技术一面面试原题:java多线程,同步与互斥,互斥和同步在时间上有要求吗?
2580
+ > 3 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的海康威视同学 4面试原题:自旋锁是什么,自旋锁会一直等待吗?自旋锁的劣势是什么?
2531
2581
2532
2582
### 42.聊聊悲观锁和乐观锁?(补充)
2533
2583
@@ -2537,16 +2587,63 @@ class SyncExample {
2537
2587
2538
2588
悲观锁的代表有 [ synchronized 关键字] ( https://javabetter.cn/thread/synchronized-1.html ) 和 [ Lock 接口] ( https://javabetter.cn/thread/reentrantLock.html ) 。
2539
2589
2540
- 乐观锁,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。一旦多个线程发生冲突,乐观锁通常使用一种称为 [ CAS] ( https://javabetter.cn/thread/cas.html ) 的技术来保证线程执行的安全性。
2590
+ 悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
2591
+
2592
+ 乐观锁,是个乐观派,总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待,通常使用 [ CAS] ( https://javabetter.cn/thread/cas.html ) 的技术来保证线程执行的安全性。
2593
+
2594
+ 多用于“读多写少“的环境,避免频繁加锁影响性能。
2595
+
2596
+ #### Java中有几种锁?
2597
+
2598
+ 按照锁的使用方式来分类的话,Java 中的锁可以分为两大类:悲观锁和乐观锁。悲观锁的代表就是 synchronized 关键字,乐观锁的代表就是 CAS。
2599
+
2600
+ #### 乐观锁时有线程过来修改数据,怎么办?
2601
+
2602
+ 此时可以重新读取数据并再次尝试更新,直到成功为止或达到最大重试次数。
2603
+
2604
+ ```
2605
+ 读取数据 -> 尝试更新 -> 成功(返回成功)
2606
+ |
2607
+ -> 失败 -> 重试 -> 达到最大次数 -> 返回失败
2608
+ ```
2609
+
2610
+ 类似这样:
2611
+
2612
+ ``` java
2613
+ class CasRetryExample {
2614
+ private static AtomicInteger counter = new AtomicInteger (0 );
2615
+ private static final int MAX_RETRIES = 5 ;
2616
+
2617
+ public static void main (String [] args ) {
2618
+ boolean success = false ;
2619
+ int retries = 0 ;
2541
2620
2542
- 由于乐观锁假想操作中没有锁的存在,因此不太可能出现死锁的情况,换句话说,乐观锁天生免疫死锁。
2621
+ while (retries < MAX_RETRIES ) {
2622
+ int currentValue = counter. get();
2623
+ boolean updated = counter. compareAndSet(currentValue, currentValue + 1 );
2624
+
2625
+ if (updated) {
2626
+ System . out. println(" 更新成功,当前值: " + counter. get());
2627
+ success = true ;
2628
+ break ;
2629
+ } else {
2630
+ retries++ ;
2631
+ System . out. println(" 更新失败,进行第 " + retries + " 次重试" );
2632
+ }
2633
+ }
2634
+
2635
+ if (! success) {
2636
+ System . out. println(" 达到最大重试次数,操作失败" );
2637
+ }
2638
+ }
2639
+ }
2640
+ ```
2543
2641
2544
- - 乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;
2545
- - 悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
2546
2642
2547
2643
> 1 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的阿里面经同学 5 阿里妈妈 Java 后端技术一面面试原题:说说 Java 的并发系统(从悲观锁聊到乐观锁,还有线程、线程池之类的,聊了快十分钟这个)
2548
2644
> 2 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的阿里面经同学 1 闲鱼后端一面的原题:乐观锁、悲观锁、ABA 问题
2549
2645
> 3 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的腾讯云智面经同学 20 二面面试原题:乐观锁和悲观锁怎么理解的?
2646
+ > 4 . [ Java 面试指南(付费)] ( https://javabetter.cn/zhishixingqiu/mianshi.html ) 收录的海康威视同学 4面试原题:java中锁种类,什么场景下用乐观锁,什么场景下用悲观锁?使用乐观锁时有线程过来修改数据,此时应该怎么做
2550
2647
2551
2648
GitHub 上标星 10000+ 的开源知识库《[ 二哥的 Java 进阶之路] ( https://github.com/itwanger/toBeBetterJavaer ) 》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[ 太赞了,GitHub 上标星 10000+ 的 Java 教程] ( https://javabetter.cn/overview/ )
2552
2649
0 commit comments