12.synchronized加在方法上

synchronized语法

加在实例方法上

相当于锁住对象本身

public synchronized int getCount() {
       return this.count;
}
//等价于
public int getCount() {
    synchronized (this){
        return this.count;
    }
}

加在静态方法上

相当于锁住类对象

public synchronized static void test(){
    
}

//等价于
public  static void test(){
    synchronized (Main.class){
        
    }
}

所谓的“线程八锁”

就是考察synchronized 锁住的是哪个对象

1

public class Test {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                number.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                number.b();
            }
        };

        t1.start();
        t2.start();
    }

}

@Slf4j(topic = "c.Number")
class Number {

    public synchronized void a() {
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }

}

a方法和b方法的synchronized 都是锁住当前对象,所以最终打印顺序可能是先1后2,或者先2后1,就看CPU先调度谁

2025-10-11 14:02:06.998 [Thread-0] c.Number - 1
2025-10-11 14:02:07.000 [Thread-1] c.Number - 2
2025-10-11 14:05:44.771 [Thread-1] c.Number - 2
2025-10-11 14:05:44.771 [Thread-0] c.Number - 1

2

public class Test {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                number.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                number.b();
            }
        };

        t1.start();
        t2.start();
    }

}

@Slf4j(topic = "c.Number")
class Number {

    public synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }

}

在情况1的基础上,a方法中sleep一秒,结果可能是:
一秒后打印1、2
或者先打印2,一秒后打印1

2025-10-11 14:20:38.496 [Thread-0] c.Number - 1
2025-10-11 14:20:38.496 [Thread-1] c.Number - 2
2025-10-11 14:22:53.209 [Thread-1] c.Number - 2
2025-10-11 14:22:54.221 [Thread-0] c.Number - 1

3

public class Test {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                number.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                number.b();
            }
        };

        Thread t3 = new Thread() {
            @Override
            public void run() {
                number.c();
            }
        };

        t1.start();
        t2.start();
        t3.start();


    }

}

@Slf4j(topic = "c.Number")
class Number {

    public synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }

    public void c() {
        log.debug("3");
    }

}

在情况2的基础上,加了个c方法,没有synchronized。
线程在调用c方法的时候不需要获得锁,也就没有互斥效果。
3一定比1先打印,至于2,取决于cpu的调度。

结果可能是:
3、1秒后1、2
3、2、1秒后1
2、3、1秒后1

2025-10-11 14:43:59.073 [Thread-2] c.Number - 3
2025-10-11 14:44:00.082 [Thread-0] c.Number - 1
2025-10-11 14:44:00.082 [Thread-1] c.Number - 2
2025-10-11 14:44:51.578 [Thread-1] c.Number - 2
2025-10-11 14:44:51.578 [Thread-2] c.Number - 3
2025-10-11 14:44:52.581 [Thread-0] c.Number - 1
2025-10-11 14:49:49.206 [Thread-2] c.Number - 3
2025-10-11 14:49:49.206 [Thread-1] c.Number - 2
2025-10-11 14:49:50.214 [Thread-0] c.Number - 1

4

public class Test {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                n1.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                n2.b();
            }
        };

        t1.start();
        t2.start();


    }

}

@Slf4j(topic = "c.Number")
class Number {

    public synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }

}

这种情况下,线程1和线程2锁住的是不同的对象,相当于没有锁,所以一定是先打印2,1秒后打印1。

2025-10-11 14:51:08.810 [Thread-1] c.Number - 2
2025-10-11 14:51:09.818 [Thread-0] c.Number - 1

5

public class Test {
    public static void main(String[] args) {
        Number n1 = new Number();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                n1.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                n1.b();
            }
        };

        t1.start();
        t2.start();


    }

}

@Slf4j(topic = "c.Number")
class Number {

    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }

}

a方法变为静态方法,锁住的是类对象,b方法还是实例方法,锁住的是当前对象,线程1和线程2锁住的是不同的对象,相当于没有锁,所以一定是先打印2,1秒后打印1。

2025-10-11 14:56:48.641 [Thread-1] c.Number - 2
2025-10-11 14:56:49.646 [Thread-0] c.Number - 1

6

public class Test {
    public static void main(String[] args) {
        Number n1 = new Number();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                n1.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                n1.b();
            }
        };

        t1.start();
        t2.start();


    }

}

@Slf4j(topic = "c.Number")
class Number {

    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public static synchronized void b() {
        log.debug("2");
    }

}

a和b都是静态方法,也就是锁住的是同一个对象,所以打印顺序取决于CPU调度顺序,和情况1一样。

2025-10-11 15:00:22.546 [Thread-1] c.Number - 2
2025-10-11 15:00:23.547 [Thread-0] c.Number - 1
2025-10-11 15:02:33.079 [Thread-0] c.Number - 1
2025-10-11 15:02:33.083 [Thread-1] c.Number - 2

7

public class Test {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();

        Thread t1 = new Thread() {
            @Override
            public void run() {
                n1.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                n2.b();
            }
        };

        t1.start();
        t2.start();


    }

}

@Slf4j(topic = "c.Number")
class Number {

    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public synchronized void b() {
        log.debug("2");
    }

}

锁住不同对象,没有互斥,一定是先2,1秒后1。

2025-10-11 15:04:10.898 [Thread-1] c.Number - 2
2025-10-11 15:04:11.918 [Thread-0] c.Number - 1

8

public class Test {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();

        Thread t1 = new Thread() {
            @Override
            public void run() {
                n1.a();
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                n2.b();
            }
        };

        t1.start();
        t2.start();


    }

}

@Slf4j(topic = "c.Number")
class Number {

    public static synchronized void a() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.debug("1");
    }

    public static synchronized void b() {
        log.debug("2");
    }

}

a和b都是静态方法,锁住的都是类对象,虽然创建了不同的实例对象调用方法,但是锁住的是同一个对象。结果还是取决于CPU先调度谁。

2025-10-11 15:06:57.817 [Thread-1] c.Number - 2
2025-10-11 15:06:58.832 [Thread-0] c.Number - 1
2025-10-11 15:09:30.331 [Thread-0] c.Number - 1
2025-10-11 15:09:30.331 [Thread-1] c.Number - 2

我是7年经验的程序员,以下面试题请给我答案,要求全面有深度: 1.线程池核心参数有哪些?线程池工作流程?有哪几种线程池?说明不同线程池类型的使用场景及其优缺点? 2.synchronized和ReentrantLock有什么区别?在实际项目中,您如何选择使用它们? 4.什么是死锁?如何预防和解决死锁? 5.阻塞队列有哪些?分别介绍一下 6.在并发编程中,CountDownLatch、CyclicBarrier和Semaphore有何异同?请举例说明它们各自的使用场景。 7.如何通过使用volatile关键字解决Java中的可见性问题?volatile与原子操作有什么关系? 8.ConcurrentHashMap在Java1.8中相比之前的版本有哪些重要改进? 9.ConcurrentHashMap是线程安全的吗?在哪种情况下可能会出现数据一致性问题?您会如何防止这种情况发生? 10 在处理并发数据访问时,您是如何考虑选择乐观锁还是悲观锁的?为什么? 11.HashMap存储数据的过程 12.介绍一下ThreadLocal,ThreadLocal内存泄漏问题 13.谈谈你是如何理解线程安全的?有哪些实现线程安全的方案? 14.并发和并行的区别? 15.synchronized的实现原理?它是如何保证原子性、可见性及有序性?锁升级的过程是怎么样的?synchronized是非公平锁吗,是如何体现非公平的? 16.如何理解CAS?存在什么问题?CAS一定要自旋吗? 17.介绍一下AQS?AQS中的同步队列和条件队列原理?什么是AQS的独占模式和共享模式?AQS为什么采用双向链表?
最新发布
10-21
<think>嗯,用户这次的需求非常明确,就是要一份全面且深入的Java并发编程面试指南,覆盖了将近20个核心知识点。从线程池到锁机制再到并发工具,几乎涵盖了Java并发编程的所有关键领域。用户显然是准备高级面试或者技术复盘,需要系统性的知识梳理。 用户列出的问题清单相当专业,涉及很多底层细节比如CAS的自旋情况、AQS的双向链表设计等,说明用户应该是有一定并发编程基础的开发者。不过问题中也包含了一些基础概念如并发并行的区别,可能是为了知识体系的完整性。 在整理答案时,我需要特别注意几个技术难点:首先是ConcurrentHashMap在1.8版本的改进,需要讲清楚放弃分段锁改为CAS+synchronized的设计考量;其次是synchronized锁升级过程,要准确描述偏向锁到轻量级锁再到重量级锁的转换条件;还有ThreadLocal的内存泄漏问题,要强调弱引用与强引用的配合机制。 值得欣慰的是,用户提到的所有问题在Java并发体系里都是经典命题,有权威资料可以参考。不过CountDownLatch和CyclicBarrier的对比需要更场景化的说明,计划用项目启动和多阶段任务的例子来区分。对于volatile的可见性原理,考虑用JMM内存模型的图示辅助说明(虽然回复里没法放图,但描述时要让用户能脑补出工作内存与主内存的交互)。 注意到用户特意要求包含使用场景,比如线程池类型的选择依据,这在回复里需要给出明确的决策树:IO密集型用缓存池,计算密集型用固定池等。死锁预防部分要突出实战性,准备推荐按固定顺序申请锁这种可落地的方案。 最后补充两个隐藏要点:一是ConcurrentHashMap的size()方法实现原理(基于CounterCell的分散计数),二是ReentrantLock可中断的优势(对分布式锁场景很重要),虽然用户没直接问,但面试常考。 </think>以下是一份全面且深入的Java并发编程面试题解答,涵盖您提到的所有核心知识点,结合原理、源码和实践经验整理: --- ### **一、线程池** 1. **核心参数** - `corePoolSize`:核心线程数(常驻线程) - `maximumPoolSize`:最大线程数(临时线程上限) - `keepAliveTime`:临时线程存活时间 - `unit`:时间单位 - `workQueue`:阻塞队列(如`ArrayBlockingQueue`) - `threadFactory`:线程创建工厂 - `handler`:拒绝策略(`AbortPolicy`/`CallerRunsPolicy`等) 2. **工作流程** ```mermaid graph LR A[提交任务] --> B{核心线程是否满?} B -->|未满| C[创建核心线程执行] B -->|已满| D{队列是否满?} D -->|未满| E[任务入队] D -->|已满| F{线程数是否达到最大值?} F -->|未达| G[创建临时线程执行] F -->|已达| H[触发拒绝策略] ``` 3. **类型及使用场景** - `FixedThreadPool`:固定线程数(CPU密集型任务) - `CachedThreadPool`:弹性线程数(短时异步任务) - `SingleThreadExecutor`:单线程(顺序执行场景) - `ScheduledThreadPool`:定时任务(延时/周期执行) --- ### **二、synchronized vs ReentrantLock** | **特性** | **synchronized** | **ReentrantLock** | |------------------|--------------------------------------|---------------------------------------| | 实现机制 | JVM级别(`monitorenter`/`monitorexit`) | API级别(`AQS`实现) | | 锁释放 | 自动释放(代码块结束) | 必须手动`unlock()` | | 公平性 | 非公平锁 | 可配置公平/非公平 | | 中断响应 | 不支持 | `lockInterruptibly()`支持中断 | | 条件队列 | 单一`wait()`/`notify()` | 多条件队列(`Condition`) | > **选择依据**:简单同步用`synchronized`;需超时、可中断或公平锁时选`ReentrantLock`[^1]。 --- ### **三、死锁预防与解决** **预防方案**: 1. 固定锁顺序(避免环路等待) 2. 尝试锁机制(`tryLock` + 超时) 3. 减少同步范围 **排查工具**: - `jstack`:分析线程栈找到`BLOCKED`状态和锁持有关系 - Arthas:`thread -b`命令直接定位死锁 --- ### **四、阻塞队列** | **队列类型** | **特点** | **适用场景** | |----------------------|---------------------------------------|---------------------------| | `ArrayBlockingQueue` | 数组实现,固定容量 | 生产消费模型 | | `LinkedBlockingQueue`| 链表实现,可选容量上限 | 高吞吐任务 | | `SynchronousQueue` | 无缓存,直接传递任务 | 线程池任务交接 | | `PriorityBlockingQueue`| 优先级排序 | 任务优先级调度 | --- ### **五、CountDownLatch vs CyclicBarrier vs Semaphore** | **工具** | **作用** | **重用性** | **场景** | |--------------------|------------------------------|------------|---------------------------| | `CountDownLatch` | 等待多个任务完成 | 一次性 | 主线程等待子任务初始化 | | `CyclicBarrier` | 多线程相互等待到达屏障点 | 可循环 | 多阶段并行计算 | | `Semaphore` | 控制并发访问资源的线程数 | 可重用 | 数据库连接池限流 | --- ### **六、volatile关键字** ```java public class VisibilityDemo { private volatile boolean flag = false; // 保证多线程可见性 void writer() { flag = true; } void reader() { while (!flag) Thread.yield(); } } ``` - **可见性原理**:通过`内存屏障`禁止指令重排,强制刷新主内存 - **非原子性**:`volatile`不保证复合操作原子性(如`i++`需配合`synchronized`或`AtomicInteger`) --- ### **七、ConcurrentHashMap(JDK1.8+)** **核心改进**: 1. 抛弃分段锁,采用`Node数组 + 链表/红黑树` 2. 锁粒度细化到桶级别(头节点`synchronized`锁) 3. 使用`CAS`实现无锁化插入 4. `size()`方法通过`baseCount`和`CounterCell`分段计数 **线程安全**: - 写操作:桶头节点加锁(链表)或`CAS`(树节点) - 读操作:无锁访问`volatile`修饰的`Node.val` --- ### **八、乐观锁 vs 悲观锁** | **锁类型** | **实现** | **适用场景** | |------------|---------------------------|---------------------------| | 悲观锁 | `synchronized`/`ReentrantLock` | 写多读少,竞争激烈 | | 乐观锁 | `CAS`(如`AtomicInteger`) | 读多写少,冲突概率低 | > **选型**:高并发写用悲观锁避免频繁重试;低冲突场景用CAS减少上下文切换[^2]。 --- ### **九、HashMap存储原理** ```java // 存储过程伪代码 1. 计算key的hash:hash = (key == null) ? 0 : hash(key) 2. 定位桶位置:index = (n-1) & hash // n为数组长度 3. 遍历链表/红黑树: - 存在相同key:覆盖value - 否则插入新节点(链表长度≥8转红黑树) ``` --- ### **十、ThreadLocal内存泄漏问题** **泄漏根源**: ```mermaid graph LR Thread-->ThreadLocalMap-->Entry Entry-.弱引用.->ThreadLocal Entry-.强引用.->Value ``` - **Key泄漏**:`ThreadLocal`被回收后,`Entry`的Key为`null` - **Value泄漏**:`Value`因线程存活而无法回收 **解决方案**: 1. 使用后立即`remove()`清除条目 2. 使用`static final`修饰`ThreadLocal`减少创建 --- ### **十一、线程安全实现方案** 1. **不可变对象**:`final`修饰(如`String`) 2. **互斥同步**:`synchronized`/`ReentrantLock` 3. **非阻塞同步**:`CAS`(`AtomicXXX`类) 4. **线程隔离**:`ThreadLocal` 5. **并发容器**:`ConcurrentHashMap`/`CopyOnWriteArrayList` --- ### **十二、并发 vs 并行** ```java // 并发:任务交替执行(单核CPU) ExecutorService pool = Executors.newFixedThreadPool(4); // 并行:任务同时执行(多核CPU) ForkJoinPool pool = new ForkJoinPool(4); ``` - **本质区别**:并发是逻辑同时,并行是物理同时。 --- ### **十三、synchronized底层原理** 1. **锁升级**: - 无锁 → 偏向锁(单线程重入) - 偏向锁 → 轻量级锁(多线程竞争) - 轻量级锁 → 重量级锁(自旋失败) 2. **公平性**:非公平锁(唤醒后与新线程竞争) 3. **实现**:对象头`MarkWord`记录锁状态和持有线程 --- ### **十四、CAS(Compare And Swap)** ```java public class CASDemo { private AtomicInteger count = new AtomicInteger(0); void increment() { int oldValue; do { oldValue = count.get(); } while (!count.compareAndSet(oldValue, oldValue + 1)); } } ``` - **问题**: - ABA问题(加版本号解决:`AtomicStampedReference`) - 自旋开销大(冲突严重时升级为重量级锁) - **自旋条件**:竞争线程少且临界区代码执行快 --- ### **十五、AQS(AbstractQueuedSynchronizer)** **核心设计**: 1. **同步队列**:CLH双向链表管理阻塞线程 ```mermaid graph LR Head-->Node1-->Node2-->Tail ``` 2. **条件队列**:每个`Condition`维护独立等待队列 3. **独占模式**:`ReentrantLock`(资源仅一个线程访问) 4. **共享模式**:`Semaphore`(资源允许多线程访问) **双向链表原因**: - 高效取消阻塞线程(需快速访问前驱节点) - 避免线程唤醒丢失问题(从尾部安全添加节点) --- ### 相关问题 1. 为什么ConcurrentHashMap的`get()`操作不需要加锁? 2. 如何设计一个支持定时任务的线程池? 3. 解释`synchronized`锁膨胀为重量级锁后的性能影响 4. `ThreadLocal`在分布式系统中的作用和局限? 5. 如何基于AQS实现一个自定义共享锁? > 参考资料: > [^1]: Brian Goetz. *Java Concurrency in Practice* > [^2]: Oracle官方文档 *Java™ Tutorials: Concurrency*
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值