Java中一些线程安全的问题

目录

1.什么是线程安全?为什么需要关注?1.1 线程安全概念
2. 实现线程安全的常用方法3. synchronized关键字的用法4.volatile关键字的作用5. ReentrantLock与synchronized的区别6. 什么是死锁?如何避免

本文解释了我的博文
《Android线程与线程池面试题总结》相对应的线程安全方便的问题解析。

1.什么是线程安全?为什么需要关注?

1.1 线程安全概念

线程安全: 当多个线程同时访问一个对象时,如果不需要额外的同步机制,这个对象的行为仍然正确的(符合预期),那么就称这个对象是线程安全的。
**核心是“正确性”,即在并发环境下,能保证数据的一致性和逻辑的正确性。
为什么需要关注:

数据竞争: 多个线程同时写一个共享变量,导致数据丢失、覆盖或不一致。**内存可见性:**一个线程修改了共享变量,其他线程无法立即看到修改后的值,仍然使用旧的缓存值。**性能与可靠性:**非线程安全的代码在并发下会导致程序崩溃、计算结果错误、业务逻辑混乱等严重问题。尤其是现代多核CPU架构下,并发是常态,必须重视。

2. 实现线程安全的常用方法

synchronized关键字
Java中最基本的护持同步锁,保证同一时刻只有一个线程能进入同步代码或方法。它提供了原子性可见性ReentrantLock:
JDK 5.0提供的显示锁,功能比synchronized更丰富(如可中断,公平锁、尝试获取锁,多个条件变量)。volatile 关键字:
保证变量的可见性禁止指令重排序。但它不保证操作的原子性。适用于“一写多读”的场景或作为状态标志。原子类(如AtomicInteger):
基于CAS(Compare-And-Swap)操作,提供了一组线程安全的、高性能的操作。例如incrementAndGet(),它保证了单个操作的原子性。
5.不可变对象:
最根本的线程安全方法。如果一个对象在构造后期状态就不能被改变,那么它天生就是线程安全的,因为不存在“写”操作。例如: String、Integer等用final修饰的类。

3. synchronized关键字的用法

修饰实例方法:


public synchronized void method{ ... }

修饰静态方法:


public static synchronized void staticMethod() {...}

**锁对象:**当前类的Class对象(MyClass.class).

修饰代码块:


synchronized (lockObject){ ... }

**锁对象:**括号中指定的任意对象(lockObject)。可以更灵活的控制锁的粒度。

4.volatile关键字的作用

**保证可见性:**当一个线程修改了volatile变量,新值会立即被刷新到主内存。当其他线程读取该变量时,会强制从内存重新加载最新值。禁止指令重排序:防止JVM和CPU为了优化新
重要限制: volatile不保证复合操作的
原子性
。例如:count++(读-改-写)操作即使count时volatile的,也不是线程安全的。

5. ReentrantLock与synchronized的区别

特性 synchronized ReentrantLock
实现层面 JVM级别,内置关键字 JDK级别,是一个类
锁的获取 隐式获取和释放 显示调用lock()和unlock()
可中断性 等待锁时不可中断 提供了lockInterruptibly(),可以响应中断
公平锁 默认非公平 可以选择创建公平或费公平锁(构造函数传true)
尝试获取锁 不能 提供tryLock(),可以尝试获取或带超时获取
条件变量 单一条件(wait/notify) 可以绑定多个Condition对象,实现更精确的线程唤醒。

总结:synchronized简单易用,JVM会自动管理锁。ReentrantLock功能更强大、更灵活,但需要手动释放锁,否则会导致死锁。

6. 什么是死锁?如何避免

**死锁:**两个或更多的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们都将无法进行下去。
死锁产生的四个必要条件(必须同时满足):

互斥条件:一个资源每次只能被一个线程使用。请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。不剥夺条件:线程已获得的资源,在未使用完之前,不能强行被剥夺。循环等待条件:若干线程之间形成一种头尾相接的循环等资源关系。
如何避免死锁(破坏上述条件之一即可):
避免嵌套锁:尽量只获取一个锁。如果必须获取多个锁,在设计上就避免这种情况。顺序加锁:强制所有线程以相同的顺序获取锁。这是最常用、最有效的办法。
例子:线程A先锁L1再锁L2,线程B也必须先锁L1再锁L2。使用定时锁(超时机制):使用ReentrantLock的tryLock(long timeout,TimeUnit unit)方法。在指定时间内获取不到所有锁,就释放已获得的锁并进行回退、重试。使用更高级的并发工具:ConcurrentHashMap、Semaphore等,减少直接使用锁的场景。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...