并发-大多程序都无法避免的,因为我们所做的大部分事情都需要并发,而且并发也是能否从多核的处理器中获得好的性能的一个条件。
一,同步访问共享的可变数据
同步并不是单单指线程之间的互斥。如果没有同步,一个线程的变化就不能被其他线程看到。同步不仅可以阻止一个线程看到对象处于不一致的状态之中,它还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护的之前的所以修改效果。
Java语言规范保证读或写一个变量是原子的(atomic)long和double除外。但是它并不保证一个线程写入的值对于另一个线程将是可见的(解释看下面的代码)。
import java.util.concurrent.TimeUnit; public class StopThread { private static boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread =new Thread(new Runnable(){ public void run() { int i=0; while(!stopRequested){ //不要使用Thread.stop因为它本质是不安全的(unsafe)使用它会导致数据遭到数据破坏 i++; } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested=true; } }
你可能期待的这个程序大约运行一秒左右,之后主线程将stopRequested设置为true,从而导致后台线程终止。但是结果不是这样的!
问题在于,由于没有同步,就不能保证后台线程何时“看到”主线程对stopRequested的值所做的改变。没有同步,虚拟机这样处理这个代码:
while(!done){ i++; } /******转变为了********/ if(!done){ while(true); }
这种优化称作提升(hoisting)这是由于这种提升就导致了活性失败(liveness failure):这个程序无法前进。修正
import java.util.concurrent.TimeUnit; public class StopThread { private static boolean stopRequested; private static synchronized void requestStop(){ stopRequested=true; } private static synchronized boolean stopRequested(){ return stopRequested; } public static void main(String[] args) throws InterruptedException { Thread backgroundThread =new Thread(new Runnable(){ public void run() { int i=0; while(!stopRequested()){ //不要使用Thread.stop因为它本质是不安全的(unsafe)使用它会导致数据遭到数据破坏 i++; } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); //stopRequested=true; requestStop(); } }
写入方法(requestStop())和读取(stopRequest())方法作都被同步了。
StopThread中方法的同步是为了它的通信效果,而不是为了互斥访问。一种更加简洁,性能也可能更好的方法是将stopRequested声明为volatile。虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该field的时候都将看到最近刚刚被写入的值:
import java.util.concurrent.TimeUnit; public class StopThread { private static volatile boolean stopRequested; public static void main(String[] args) throws InterruptedException { Thread backgroundThread =new Thread(new Runnable(){ public void run() { int i=0; while(!stopRequested){ //不要使用Thread.stop因为它本质是不安全的(unsafe)使用它会导致数据遭到数据破坏 i++; } } }); backgroundThread.start(); TimeUnit.SECONDS.sleep(1); stopRequested=true; } }
二 避免使用过度同步
在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客服端以函数对象的形式提供的方法。从包含该同步区域的类的角度来看这样的方法是外来的(alien)。这个类不知道该方法会做什么事情,也无法控制它,根据外来方法的作业,从同步区域调用它会导致异常,死锁或者数据的损坏。
以上内容全部来自《effective java》第二版 若想更深的而了解请参考原书
相关推荐
- 使用不可变对象。 - 避免过度同步。 - 优先使用并发工具类而不是自建同步机制。 - 对于长时间运行的任务,考虑使用线程池。 以上内容涵盖了Java并发编程的多个关键点,从基础知识到高级技巧,希望对你理解和...
- 使用不可变对象:避免并发修改引发的问题。 - 正确使用同步:避免过度同步,防止死锁。 9. **第九章:并发调试与监控** - JMX:Java管理扩展,用于监控和管理应用程序。 - `ThreadMXBean`:获取线程信息,...
- **共享内存模型**:Java采用共享内存模型进行并发编程,其中多个线程共享相同的内存空间,通过同步机制控制访问。 ##### 2. 线程安全与同步 - **锁与互斥**:锁是实现线程安全的关键机制之一。书中详细讨论了各种...
不可变对象在并发环境下非常有用,因为一旦对象被创建后就不能改变其状态,这样就不存在并发修改的问题。在Java中可以通过以下方式创建不可变对象: - 将所有字段设为`final`。 - 提供无参构造器,并且禁止外部修改...
- **使用:** 在实际项目中用于保护共享资源,避免并发访问导致的数据不一致。 - **底层实现原理:** 基于对象监视器锁(Monitor Lock)。 **自旋** - **定义:** 线程在获取锁失败后继续循环尝试获取锁的过程。 -...
当无法避免共享状态时,需要使用同步机制来控制对共享资源的访问。Java提供了多种同步工具: 1. `synchronized`关键字:用于方法或代码块,确保同一时间只有一个线程能执行特定代码。 2. `java.util.concurrent....
在多线程环境中,多个线程可能会访问和修改共享资源,如果没有适当的同步机制,就可能导致数据竞争(race condition)和一致性问题。Linux操作系统提供了一套丰富的线程同步机制,以确保线程安全和数据一致性。 ###...
9. **性能调优**:并发程序的性能优化是个复杂的话题,书中提供了许多实用的建议,如合理配置线程池大小,避免过度同步等。 通过学习《Java并发编程实践》,开发者可以更深入地理解Java并发机制,提高代码的并发...
- 确保任务和数据块具有足够的并发性,同时避免过度分割导致的效率损失。 - **负载平衡** - 动态调整任务分配,确保不同处理器之间的负载均衡。 - 采用工作窃取或指导调度算法,主动寻找空闲处理器并分配任务。 ...
- 减少线程间的数据共享,使用不可变对象或线程局部变量。 - **1.5.4 多线程和你的用户界面** - 用户界面的更新应在线程安全的方式下进行,通常是在主线程上。 - **1.5.5 了解线程退出时的行为** - 理解线程如何...
- 使用不可变对象:不变对象天然线程安全,易于理解和调试。 - 减少锁的粒度:细粒度锁可以提高并发性能,但会增加锁竞争和死锁风险。 - 正确地使用并发工具:避免滥用synchronized,合理选择并发工具。 9. **...
通过 channels,goroutines 可以安全地共享数据,避免了传统并发编程中的数据竞争问题。Channels 有两种类型:无缓冲和有缓冲,它们分别适用于同步和异步通信场景。 3. Select:在并发编程中,select 语句允许你...
`CSemaphore`类在MFC中提供此功能,它可以控制同时访问的线程数量,避免资源过度消耗。 4. **互斥对象(Mutex)**:与临界区类似,互斥对象也确保了同一时间只有一个进程可以访问资源。`CMutex`类是MFC对互斥对象的...
它们用于控制对共享资源的访问,防止数据竞争,确保线程安全。 4. **互斥量与锁**:`std::mutex`是线程间同步的基础,可以防止多个线程同时访问同一资源。`std::lock_guard`和`std::unique_lock`是智能锁,可以自动...
- 使用`synchronized`关键字或`Lock`实现线程安全,但避免过度同步。 - 利用并发工具类,如`ConcurrentHashMap`、`CountDownLatch`等提高多线程效率。 8. **IO操作优化** - 使用NIO(New IO)代替传统的BIO,...
- **ThreadLocal的使用场景**:在第44讲中,讨论了ThreadLocal在实际生产中的应用,如解决线程间数据隔离的问题,避免了共享数据带来的并发问题。 5. **性能优化** - **多线程带来的性能问题**:第08讲揭示了过度...
10. **并发性能优化**:如何通过调整线程池大小、合理分配任务、避免过度同步等方式优化并发程序的性能。 11. **Java内存模型(JMM)**:理解Java内存模型对线程间通信的规定,包括可见性、有序性和原子性。 12. *...
对共享的可变数据进行正确的同步 在多线程环境中,对共享数据的访问必须加以控制,以避免出现数据不一致的情况。常见的做法包括使用`synchronized`关键字或`ReentrantLock`等显式锁。 ##### 2.2. 正确的使用锁 ...
SimpleDateFormat在Java中并不是线程安全的,因为它内部存在可变状态,可能导致线程间的竞态条件。为了解决这一问题,我们可以使用线程局部变量(ThreadLocal)或者在每次使用时实例化新的SimpleDateFormat对象,...
然而,这也可能导致资源竞争和数据不一致,因此进程同步变得尤为重要。 二、创建与管理进程 在Linux下,我们可以使用fork()函数来创建新的进程。这个函数复制当前进程的所有状态,包括内存映射、打开的文件描述符等...