同步和并发是计算机科学中用于描述多任务处理的两个重要概念,它们在多线程或多进程编程中尤为重要:
并发 (Concurrency):
并发是指在一段时间内,多个任务或者计算过程看起来像是在同时进行,即使实际上它们可能是在单个处理器上交替执行,或者是通过时间片轮转、多核心/多处理器等硬件支持下的真正并行执行。并发着重于描述多个任务的宏观行为——即多个操作似乎在同一时刻发生或在短时间内快速交替发生,从而提高了系统的整体效率和对外部用户的响应速度。
例如,在一个单核CPU上,操作系统会使用调度算法快速地在多个线程间切换上下文,使得从外部观察来看,所有线程都在同时执行。而在一个多核CPU上,确实可以实现一些线程的同时执行(即并行)。
同步 (Synchronization):
同步则是并发环境中,针对共享资源或特定流程顺序控制的一种协调机制。当多个线程需要按照一定的顺序或者依赖关系执行时,就需要引入同步机制。例如,一个线程必须等待另一个线程完成某个操作后才能继续,或者一组线程需要以互斥的方式访问同一块临界区资源,以避免数据竞争和其他错误。
举个例子,想象一个生产者-消费者模型,其中生产者线程生成数据而消费者线程消耗这些数据。为了保证有效的工作,生产者不能在缓冲区满时继续生产数据(否则会导致数据丢失),同样,消费者也不能在缓冲区为空时尝试消费数据。因此,这里需要同步机制(如锁、信号量、条件变量等)来确保正确的交互顺序和资源访问规则得以遵循。
死锁 是指在一个多任务或多线程环境中,两个或更多的进程或线程相互等待对方所持有的资源而无法继续执行的现象。通常,这种情况发生在满足以下几个条件时:
-
互斥条件: 至少有一个资源是不能被多个进程同时使用的,即一次只有一个进程能占有该资源。
-
占有并等待条件: 进程已经占有至少一个资源,但又在等待其它尚未被占有的资源。
-
不可抢占条件: 已经获得资源的进程不能被迫释放其已占有的资源,除非它完成自己的工作或因异常退出。
-
循环等待条件: 存在一个进程集合 {P1, P2, …, Pn},其中 P1 等待 P2 所持有的资源,P2 等待 P3 的资源,依此类推直到 Pn 等待 P1 的资源,形成了一个等待环路。
如何避免死锁:
-
破坏互斥条件:
-
使用可共享的资源,例如读写锁,允许多个进程同时进行读操作,但仅允许一个进程进行写操作。
-
使用高级同步原语,如信号量、条件变量等,它们可以在一定程度上避免互斥锁导致的死锁。
-
-
破坏占有并等待条件:
-
要求进程在开始执行前一次性申请所有所需的资源,不允许执行过程中再申请资源。
-
或者,如果进程中发现需要更多资源而无法立即获取时,释放已持有的所有资源,然后重新尝试获取全部所需资源。
-
-
破坏不可抢占条件:
-
实行资源预分配策略,在分配资源前就检查是否会导致死锁。
-
如果发现某进程正在等待资源且可能导致死锁,则可以强行从其他进程那里抢占一部分资源分配给它。
-
-
破坏循环等待条件:
-
强制规定资源申请的全局顺序,比如按资源编号顺序申请,使得不可能形成等待环路。
-
或者利用资源分配图分析,找到可能的环路并消除。
-
此外,还可以采用以下一些策略来防止死锁:
-
资源数量限制:限定每个进程可持有的最大资源数,以及每种类型资源的最大总量,从而降低发生死锁的概率。
-
超时与重试:设置锁的持有时间和超时机制,当超过设定时间还无法获取资源时,进程放弃并重新尝试或者回滚操作。
-
死锁检测与恢复:操作系统可以通过定期检查是否存在死锁情况,并采取相应的措施,如中断其中一个或几个进程并释放其持有的资源,或者撤销部分事务来打破循环等待。
在实践中,往往结合多种策略共同作用来有效地避免死锁的发生。
synchronized
关键字在Java中用于实现线程同步,它的主要作用如下:
-
线程互斥:当一个线程进入一个对象的被
synchronized
修饰的方法或代码块后,其他试图进入该同步代码的线程将会被阻塞,直到第一个线程执行完毕并释放锁。这意味着在同一时刻,对于同一对象而言,只会有一个线程能执行那个被同步的代码区域。 -
内存可见性:当一个线程在同步块或同步方法中修改了共享变量的状态后,离开该同步代码块时会自动将这些修改写回到主内存中。因此,后续获得锁的线程能够看到前一个线程对这些变量所做的更新。
-
避免重排序:
synchronized
关键字还能阻止编译器和处理器进行特定的指令重排序,从而确保了在多线程环境下代码的正确执行顺序。
总结来说,synchronized
是一种内置的锁机制,它提供了一种方式来控制对共享资源的并发访问,以防止因并发导致的数据不一致性和线程安全问题。