本人今年参加了很多面试,也有幸拿到了一些大厂的offer,整理了众多面试资料,后续还会分享众多面试资料。
整理成了面试系列,由于时间有限,每天整理一点,后续会陆续分享出来,感兴趣的朋友可关注+收藏
文章目录
- 1. 简介
- 2. sychronized关键字的用法
- 3. Lock的底层原理-CAS(Compare and Swap)
- 4. sychronized底层原理
1. 简介
(1)Synchronized
synchronized 是 Java 中的关键字,用于实现线程同步和互斥,只能是非公平锁。它可以用于方法或代码块中,确保在同一时间只有一个线程可以访问同步代码。使用 synchronized 可以保证线程安全,但可能会引起性能问题,因为它会导致线程阻塞等待锁的释放。
1.public synchronized void synchronizedMethod() {
2. // 同步的代码块
3.}
(2)Volatile
volatile 是用于修饰变量的关键字,它确保了可见性和禁止指令重排序。当一个变量被声明为 volatile 时,对该变量的写操作会立即被刷新到主内存,对该变量的读操作会从主内存中获取最新的值。volatile 可用于保证多线程环境下的变量的一致性,但它不能解决复合操作问题。
1.private volatile int counter = 0;
(3)Lock 接口及其实现
Java提供了 Lock 接口及其实现类,如 ReentrantLock,用于实现更灵活的线程同步机制,可实现公平锁和非公平锁。与 synchronized 相比,Lock 可以支持更多的特性,如可重入性、公平性、条件等待等。Lock 的使用需要显式地获取锁和释放锁,可以更细粒度地控制线程的同步。
它的底层是使用CAS+自旋实现的。
1.import java.util.concurrent.locks.Lock;
2.import java.util.concurrent.locks.ReentrantLock;
3.public class LockExample {
4. private Lock lock = new ReentrantLock();
5. public void lockedMethod() {
6. lock.lock();
7. try {
8. // 临界区代码
9. } finally {
10. lock.unlock();
11. }
12. }
13.}
注:
公平锁: 多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁;
非公平锁: 多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
2. sychronized关键字的用法
(1)sychronized修饰方法/代码块
1.// 两种修饰方法的方式等效
2.public synchronized void test1(){
3. //do something
4. }
5.public void test2(){
6. synchronized (this){
7. //do something
8. }
9. }
作用对象都是单个实例,作用范围都是相应的代码块(方法)。示例如下:
1.public class SychronizedTest implements Runnable{
2. @Override
3. public void run() {
4. synchronized(this){
5. for (int i = 0; i < 5; i++) {
6. System.out.println(Thread.currentThread().getName() + " " + i);
7. }
8. }
9. }
10.}
1.public class Main {
2. public static void main(String[] args) {
3. SychronizedTest testThis = new SychronizedTest();
4. new Thread(testThis, "one").start();
5. new Thread(testThis, "two").start();
6. }
7.}
输出结果如下:
分析如下:
因为第一个线程先访问到run()方法,所以它先拿到锁,two线程则阻塞,所以输出是先one线程输出,two线程后输出。
(2)sychronized修饰 static方法
当synchronized修饰静态方法时,锁的作用对象是类,作用范围是该类的所有对象的static synchronized 方法。
即,当一个线程执行到某个对象被synchronized修饰的静态方法时, 该对象的类下所有对象的static synchronized 方法都被该线程加锁了,都不能动了,但其他的不受影响 。示例如下:
1.public class TestStatic implements Runnable{
2. @Override
3. public void run() {
4. String threadName = Thread.currentThread().getName()+"";
5. if (threadName.equals("A")){
6. testA();
7. }else if (threadName.equals("B")){
8. testB();
9. }else if (threadName.equals("C")){
10. testC();
11. }else if (threadName.equals("D")){
12. testD();
13. }else{
14. testE();
15. }
16.
17. }
18. // 类锁
19. public synchronized static void testA(){
20. for(int i=0; i<5; i++){
21. System.out.println(Thread.currentThread().getName()+" "+i);
22. }
23. }
24. // 类锁
25. public synchronized static void testB(){
26. for(int i=0; i<5; i++){
27. System.out.println(Thread.currentThread().getName()+" "+i);
28. }
29. }
30. // 对象锁
31. public synchronized void testC(){
32. for(int i=0; i<5; i++){
33. System.out.println(Thread.currentThread().getName()+" "+i);
34. }
35. }
36. // 对象锁
37. public synchronized void testD(){
38. for(int i=0; i<5; i++){
39. System.out.println(Thread.currentThread().getName()+" "+i);
40. }
41. }
42.
43. public void testE(){
44. for(int i=0; i<5; i++){
45. System.out.println(Thread.currentThread().getName()+" "+i);
46. }
47. }
48.}
调用:
1.public class SychronizedMain {
2. public static void main(String[] args) {
3. TestStatic testStatic = new TestStatic();
4. new Thread(testStatic, "A").start();
5. new Thread(testStatic, "B").start();
6. new Thread(testStatic, "C").start();
7. new Thread(testStatic, "D").start();
8. new Thread(testStatic, "E").start();
9. }
10.}
结果:
对象锁、类锁的总结:
从上面的示例中,可以得出:
· 类锁就是 sychronized static, 其他就是对象锁; ·
· 对象锁和类锁是独立的,即一个线程持有了对象锁并不影响其他线程获取类锁,反之亦然。
(3)sychronized与wait()和notify()的使用
-
wait()与notify()都是Object的方法
-
wait()与notify()都只能在synchronized方法或代码块中使用
-
当一个线程执行到wait()方法时,该线程会释放锁(同时释放CPU资源),并对执行
点进行标记,当该线程被重新唤醒时,会从标记点继续执行代码 -
由于线程执行wait()时会释放锁,这意味着:
当一个线程执行到了wait(),其他线程则可以进入:当该线程被唤醒时,它需要重新对
相应的对象进行加锁,如果某对象的全部线程被唤醒了(notifyAll())则它们需要竞争来确定谁先加锁。 -
当一个线程执行到notify()时,它不会立即释放锁,而是要把sychronized()代码块执行完后再进行释放锁;
-
notify()选择唤醒的线程是随机的,但是依赖与具体实现的jvm, 如再hotspot中就是先wait()先唤醒。
3. Lock的底层原理-CAS(Compare and Swap)
lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
lock释放锁的过程:修改状态值,调整等待链表。
可以看到在整个实现过程中,lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)。因此在非必要的情况下,建议使用synchronized做同步操作。
4. sychronized底层原理
简单来说就是有两个标识位,当代码走到monitorenter的时候,虚拟机就知道后面的代码是需要保证线程安全的,只能让一个线程同时操作,当代码走到monitorexit的时候,虚拟机就知道同步结束,后面的代码无需保证安全。
本节完!