-
2021-04-02 22:48:58
文章目录
线程同步有哪些机制
现在流行的进程线程同步互斥的控制机制,其实是由最原始的、最基本的4种方法(临界区、互斥量、信号量和事件)实现的
- 临界区:临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或是进程必须等待(例如:bounded waiting 等待法),有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用。通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问
- 互斥量:为协调对一个共享资源的单独访问而设计,只有拥有互斥量的线程才有权限访问系统的公共资源,因为互斥量只有一个,所以能保证资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源的访问,还能实现不同应用程序公共资源的安全共享
- 信号量:为控制一个具有有限数量的用户资源而设计。它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目
- 事件:用来通知线程有一些事件已经发生,从而启动后续任务的开始
更多相关内容 -
Android多线程之同步锁的使用
2021-01-04 02:32:38本文主要介绍了Android多线程之同步锁的使用,分享给大家,具体如下: 一、同步机制关键字synchronized 对于Java来说,最常用的同步机制就是synchronized关键字,他是一种基于语言的粗略锁,能够作用于对象、函数... -
线程的三种同步机制
2021-12-19 17:28:321.同步代码块 2.同步方法 3.Lock锁 – JDK 5.0 新增由一个例子引入今天的内容
例子,创建三个窗口买票,总票数为100张,使用实现Runnable接口的方式
问题:卖票过程中,出现了重票、错票 --> 出现了线程安全问题
问题出现原因:当某个线程操作车票的过程中,尚未操作完成时,其它线程参与进来,也操作了该张票
如何解决:当一个线程在操作共享数据时,其他线程不能参与进来,直到线程A操作完共享数据,其他线程才可以开始操作
这种情况即使线程A出现了阻塞,也不能改变
在Java中,我们通过同步机制,来解决线程的安全问题1.同步代码块
synchronized(同步监视器){ //需要被同步的代码 }
public class WindowTest1 { public static void main(String[] args) { Window1 window1 = new Window1(); Thread thread = new Thread(window1); Thread thread1 = new Thread(window1); Thread thread2 = new Thread(window1); thread.setName("线程1"); thread1.setName("线程2"); thread2.setName("线程3"); thread.start(); thread1.start(); thread2.start(); } } class Window1 implements Runnable { private int ticket = 100; Object object = new Object(); @Override public void run() { while (true) { // synchronized (object) { synchronized (this) { if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } else { break; } } } } }
说明:
- 操作共享数据的代码即为需要被同步的代码
- 共享数据:多个线程共同操作的变量,比如:ticket就是共享数据
- 同步监视器:俗称锁,任何一个类的对象都可以充当锁
- 要求:多个线程必须要共用同一把锁
- 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
2.同步方法
在需要同步的方法上添加synchronized关键字
public class WindowTest3 { public static void main(String[] args) { Window3 window3 = new Window3(); Thread thread1 = new Thread(window3); Thread thread2 = new Thread(window3); Thread thread3 = new Thread(window3); thread1.setName("线程1"); thread2.setName("线程2"); thread3.setName("线程3"); thread1.start(); thread2.start(); thread3.start(); } } class Window3 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); if (ticket <= 0) break; } } private synchronized void show() {//同步监视器:this if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } } }
3.Lock锁 – JDK 5.0 新增
- 创建锁对象
- 在需要同步的代码块前调用lock()加锁
- 同步代码块后调用unlock()释放锁
class Window implements Runnable { private int ticket = 100; private ReentrantLock lock = new ReentrantLock(true);//1.创建锁对象 @Override public void run() { while (true) { try { lock.lock();//2.调用lock()加锁 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; } else { break; } } finally { lock.unlock();//3.调用unlock()释放锁 } } } } public class LockTest { public static void main(String[] args) { Window window = new Window(); Thread thread1 = new Thread(window); Thread thread2 = new Thread(window); Thread thread3 = new Thread(window); thread1.setName("线程1"); thread2.setName("线程2"); thread3.setName("线程3"); thread1.start(); thread2.start(); thread3.start(); } }
synchronized 与 lock的异同?
- 相同点:二者都可以解决线程安全问题
- 不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
- lock需要手动的启动同步lock(),同时结束同步也需要手动的实现unlock()
4.总结
好处:同步的方式,解决了线程安全问题
坏处:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
使用的优先顺序:Lock ----> 同步代码块(已经进入了方法体,分配了相应资源) ----> 同步方法(在方法体之外) -
Java多线程与同步机制
2020-12-10 14:32:57Java多线程基本概念;线程的创建和使用;线程安全问题与同步机制;线程通信等Java多线程
文章目录
基本概念
- 程序:为完成特定任务用某种语言编写的指令的集合,属于一段静态代码。
- 进程:是程序的依次执行过程,正在运行的程序,存在生命周期。进程为资源分配的单位。每个进程在内存中有独占一个方法区和堆空间,被多个线程共享。
- 线程:进程可以进一步细化为线程,是程序内部的一条执行路径。线程作为调度和执行的单位。每个线程拥有独立的虚拟栈空间和程序计数器。
- 一个java应用程序实际上至少有三个线程,main主线程,gc()垃圾回收机制的运行线程,异常处理线程。
- 线程分为两类:用户线程和守护线程。守护线程适用于服务用户线程的。用户线程结束,守护线程也就结束,所以守护线程是依赖于用户线程的。举个例子:java程序中,main是用户线程,垃圾回收就是守护线程。可以利用
thread.setDaemon(true)
将用户线程变成守护线程。
线程的创建和使用
线程的创建
- 方式一:创建继承
Thread
类的子类,需要重写父类的run()
方法,然后创建子类的对象,通过子类对象调用start()
方法(包括采用匿名子类)。
public class MultiThreadingTest1 { public static void main(String[] args) { MyThread1 myThread = new MyThread1(); myThread.start(); //如果直接调用run方法不属于多线程,因为没有开启新线程 //myThread.run(); //同一个新线程的对象不能重复start启动,如想重新启动新线程,需要创建一个新的对象 //myThread.start();//运行报错:java.lang.IllegalThreadStateException System.out.println("我是主线程"); //匿名子类写法创建多线程 new Thread() { @Override public void run() { // 此线程执行需要执行的操作声明在run中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是匿名子类新线程"); } }.start(); } } class MyThread1 extends Thread { @Override public void run() { // 此线程执行需要执行的操作声明在run中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是新线程"); } }
- 方式二:创建实现
Runnable
的类,实现Runnable
中的抽象方法run()
,创建类的对象,将此对象传入Thread
类的构造器中创建Thread
类的对象,调用Thread
类的对象的start()
方法(包括匿名写法)。
public class MultiTheadingTest2 { public static void main(String[] args) { MyThread2 myThread2 = new MyThread2(); Thread thread = new Thread(myThread2); thread.start(); System.out.println("我是主线程"); //匿名写法 new Thread( new Runnable() { @Override public void run() { // 此线程执行需要执行的操作声明在run中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是匿名新线程"); } }).start(); } } class MyThread2 implements Runnable{ @Override public void run() { // 此线程执行需要执行的操作声明在run中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是新线程"); } }
- 方式三:JDK5.0新特性。实现
Callable
接口。需要借助Future
接口的唯一实现类FutureTask
辅助线程的对象创建和返回值获取(FutureTask
还实现了Runnable
接口),再创建Thread
对象,将FutureTask
类的对象作为构造器参数传入,完成线程的创建,最后调用start()
方法完成线程启动。
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class MultiThreadTest3 { public static void main(String[] args) { MyThread3 myThread3 = new MyThread3(); FutureTask futureTask = new FutureTask(myThread3); Thread thread = new Thread(futureTask); thread.start(); try { Object sum = futureTask.get(); //System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("我是主线程"); // 匿名写法 new Thread(new FutureTask(new Callable() { @Override public Object call() throws Exception { // 此线程执行需要执行的操作声明在call中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是匿名新线程"); return sum;// int类型赋值给Object,自动装箱 } }) ).start(); } } class MyThread3 implements Callable { @Override public Object call() throws Exception { // 此线程执行需要执行的操作声明在call中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是新线程"); return sum;// int类型赋值给Object,自动装箱 } }
- 方式四:JDK5.0新特性。使用线程池,提前创建好多个线程放入线程池中,使用时直接获取,使用完放回线程池中。可以做到提高响应速度(减少线程创建的时间)和降低资源消耗(可重复利用线程)。利用
Executors
工具类创建线程池,然后提供Runnable
(excute()
)或Callable
(submit()
)接口的实现类的对象,执行指定线程的操作。最后,关闭线程池shutdown()
。
import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MultiThreadTest4 { public static void main(String[] args) { // 利用工具类Executors创建线程池 ExecutorService threadPool = Executors.newFixedThreadPool(10); threadPool.submit(new MyThread4());// 适用于实现Callable接口的线程 threadPool.execute(new MyThread5());// 适用于实现Runnable接口的线程 threadPool.shutdown(); } } class MyThread4 implements Callable{ @Override public Object call() throws Exception { // 此线程执行需要执行的操作声明在call中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是CALL新线程"); return sum; } } class MyThread5 implements Runnable { @Override public void run() { // 此线程执行需要执行的操作声明在call中 int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } System.out.println("我是RUN新线程"); } }
创建线程方式间的比较
JDK5.0前两种方式的比较(继承Thread类与实现Runnable接口)
- 继承方式的弊端:单继承约束下线程只能继承于
Thread
类,不能继承于其他的类 - 实现方式的优势:天然存在共享数据的情况,不需要将共享的数据设置为静态
Thread
类实际上是Runnable
接口的实现类
实现Runnable接口和Callable接口方式的比较
Runnable
接口实现方法需要重写run()
,但是run()
方法没有返回值,Callable
接口实现方式重写Call()
方法,可以有返回值;Runnable
接口实现方法不能抛出异常,只能try-catch
捕获异常,Callable
接口实现方式可以throws
抛出异常;Runnable
接口实现方法不支持泛型,Callable
接口实现方式支持泛型;Callable
接口实现方式返回值需要借助FutureTask
类获取返回值。
线程的常用方法
- 1.
start()
:启动当前线程,自动调用run()
方法; - 2.
run()
:通常需要子类重写,并将新线程需要执行的操作声明在run()
方法中; - 3.
currentThread()
:静态方法,返回当前执行的线程的对象(返回对象类型为Thread
); - 4.
getName()
:获取当前线程的名称,不设置名称情况下,默认调用Thread
的空参构造器Thread-0等等(线程名不等于类名); - 5.
setName()
:设置当前线程的名称,Thread.currentThread.setName("")
或者对象名.setName("")
; - 6.
yield()
:静态方法,线程让步,释放当前CPU的执行,可以让多个线程同时竞争资源; - 7.
join()
:在线程A中调用线程B的join()
方法,相当于让线程B直接插入线程A方法中运行(线程A阻塞),直至线程B结束,继续线程A; - 8.
stop()
:强制结束线程的生命周期,不推荐使用(Deprecated
); - 9.
sleep(long millis)
:静态方法,毫秒单位,睡眠一段时间之后重新加入CPU资源竞争,睡眠的时候仍然握锁; - 10.
isAlive()
:判断当前线程是否存活。
线程的优先级
MAX_PRIORITY
:默认线程最高优先级为10MIN_PRIORITY
:默认线程最低优先级为1NORM_PRIORITY
:不设置优先级情况下,默认线程优先级为5getPriority()
:final
,获取线程优先级setPriority(int)
:设置线程优先级- 注:高优先级并不意味着先执行,只是更高概率先抢占资源执行,所以还是存在交替输出的结果。
多线程共享数据
- 使用方式一
Thread
子类的方式创建对象,需要将共享数据或者锁等设置为静态。 - 使用方式二
Runnable
接口实现类方法创建多线程,可以不用设置静态。
经典例子:三个窗口卖票,采用方式一创建多线程,需要将票的总数需要设置为静态。存在线程不安全的问题。
线程的生命周期
JDK中
Thread.State
枚举类定义了线程的几种状态:- 新建:当一个
Thread
类或其子类声明并被创建,新生的线程对象处于新建状态; - 就绪:新建状态的线程被
start()
后,进入线程队列等待时间片,但还未分配cpu资源,处于就绪状态; - 运行:线程被cpu调度进入运行状态;
- 阻塞:在某些特殊情况下,被人为挂起或执行输入输出操作时,线程临时终止自己的执行,进入阻塞状态;
- 死亡:线程完成了所有工作或被提前强制性终止或出现异常导致结束。
线程安全与线程同步
线程安全问题产生原因:当一个线程在执行操作共享数据的多条代码过程中,其他线程也参与了运算,就有可能导致线程安全问题的产生。
解决方案:同步机制。同步机制
方式一:同步代码块
将共享数据资源的代码块“包”起来,加synchronized
关键字和同步锁。synchronized(同步监视器(锁)){ //需要被同步的代码块 }
说明:
- 操作共享数据的代码即为需要被同步的代码;
- 多个线程共同操作的变量即为共享数据;
- 任何一个类的对象都能作为锁;多个线程必须共用一把锁,比如用方法一创建线程需要设置锁对象为静态。
- 在方式二创建线程中,解决线程安全问题**,锁可以设置为当前对象**,即用关键字
this
表示,synchronized(this){同步代码块}
; - 在方式一创建线程中,解决线程安全问题,锁可以设置为类.class,即
synchronized(继承Thread的子类.class){同步代码块}
; - 在操作同步代码块时,只有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低。
方式二:同步方法
如果操作共享数据的代码完整声明在一个方法中,不妨将此方法声明为同步的。- 在方式一创建线程时,可以直接把操作共享数据的代码封装进一个方法里,并把该方法声明为静态****和同步,即
static
和synchronized
,这里的隐藏同步监视器即为当前类本身,类.class。 - 在方式二创建线程时,可以直接把操作共享数据的代码封装进一个方法里,并把该方法声明为
synchronized
,这里实际上仍然有隐藏同步监视器存在,即为this
。必要情况下,可以直接把run()
方法设置为synchronized
,但是相当于变成一个单进程过程,共享数据会被第一个进程全部执行。
方式三:同步锁(Lock)
- JDK5.0新特性,同步锁由
Lock
对象充当。ReentrantLock
类实现Lock
接口。 java.util.concurrent.locks.Lock
接口:控制多个线程对共享数据资源进行访问的工具。锁提供了对共享数据资源的独占访问,每次只有一个线程对
Lock对象加锁,线程开始访问共享数据资源之前需要先获取Lock
对象。- 同样,使用方式一创建线程需要主要
lock
对象的静态问题。
ReentrantLock reentrantLock = new ReentrantLock(); try { //加锁 reentrantLock.lock(); //需要同步的代码块 }finally { //释放锁 reentrantLock.unlock(); }
两大类解决线程安全问题方法的不同之处(synchronized和lock(ReentrantLock))
synchronized
机制属于在执行完同步代码后,会自动释放锁(隐式锁);Lock
机制需要手动的启动和释放锁(显示锁),且只有代码块锁,没有方法锁;
单例模式线程安全问题
单例模式的创建,一般分为两种方式:饿汉式和懒汉式。
- 饿汉式:在还没需要使用对象前,提前在开辟内存空间,创建对象;
- 懒汉式:在需要使用对象的时候,判断是否需要新建对象的时候考虑创建对象;
懒汉式单例设计模式,存在线程安全的问题,有可能多个线程“同时”判断是否需要创建新对象,导致创建的对象个数多于一个。下面代码是两种创建模式的简单代码示例,并直接利用同步方法解决懒汉式单例模式的线程安全问题。
public class singletonTest { public static void main(String[] args) { Bank bank1 = Bank.getBankInfo(); School school1 = School.getSchoolInfo(); } } //饿汉式单例模式:没用就直接造好了 class Bank{ //构造器私有化 private Bank(){ } //内部创建对象 private static Bank bank = new Bank(); //静态开放方法调用 public static Bank getBankInfo() { return bank; } } //懒汉式单例模式:用的时候造,开辟空间 class School{ private School() { } private static School school = null; public static synchronized School getSchoolInfo() { if(school == null) { school = new School(); } return school; } }
死锁
不同的线程分别占用对方需要的同步资源(锁)不放弃,都在等待对方放弃同步资源,形成线程的死锁。出现死锁后,不会出现异常,不会有提示信息,只是所有的线程都处于阻塞状态,无法继续。若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
public class ThreadDeadlock { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); //方式一 new Thread() { @Override public void run() { synchronized (s1) { s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2) { s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } }; }.start(); //方式二 new Thread(new Runnable(){ @Override public void run() { synchronized (s2) { s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1) { s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } }; }) .start(); } }
线程通信
线程通信常用方法
wait()
:定义在Object类,final
,线程进入阻塞状态,释放锁(和sleep不同);notify()
:定义在Object类,final
,唤醒正在等待锁的线程,进入就绪状态(有优先级按优先级,没有随机唤醒一个);notifyAll()
:定义在Object类,final
,唤醒所有正在等待锁的线程;- 这三种方法只能出现在同步代码块或同步方法里,且不能用在lock里,否则会报错
java.lang.IllegalMonitorStateException: current thread not owner
。 - 这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,默认情况下是this或者类.class(当前类的对象)
sleep()和wait()的异同?
同:一旦使用,均可使当前线程进入阻塞状态;
异:- 声明位置不同:
sleep()
声明在Thread类中,wait()
声明在Object类中; - 调用要求不同:
sleep()
可以使用在各种需要的地方,而wait()
只能用在同步代码块或同步方法里; sleep()
使用不释放锁,而wait()
使用后会释放锁。
线程通信经典案例:生产者消费者问题
/* * 线程通信经典问题:生产者与消费者 * 生产者生产产品给店员,消费者从店员消费产品,店员一次只能固定最多持有一定数量的产品(比如:20件), * 当店员满额产品,生产者试图多生产产品时,店员会让生产者停一停; * 当店员产品不足,消费者试图继续消费时,店员会让消费者等一等; * */ public class ThreadCommExe { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Customer customer = new Customer(clerk); Thread threadp = new Thread(productor); Thread threadc = new Thread(customer); threadp.setName("生产者线程1"); threadc.setName("消费者线程1"); threadp.start(); threadc.start(); } } //店员实际上是共享资源,生产者与消费者两个线程需要判断店员的存货情况 class Clerk { private int productCount = 0; public synchronized void prodeceRespone() { if(productCount<20) { productCount++; System.out.println(Thread.currentThread().getName()+"正在生产第"+productCount+"件产品"); notify(); }else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void customeRespone() { if(productCount>0) { System.out.println(Thread.currentThread().getName()+"正在消费第"+productCount+"件产品"); productCount--; notify(); }else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } //生产者线程 class Productor implements Runnable { private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println("疯狂生产中......"); while(true) { clerk.prodeceRespone(); } } } //消费者线程 class Customer implements Runnable { private Clerk clerk; public Customer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println("疯狂消费中......"); while(true){ clerk.customeRespone(); } } }
注意事项
- 单元测试需要特殊设置,不然不支持多线程验证。如果采用单元测试测试多线程,可能出现新线程执行不完整的情况,因为单元测试在main线程执行完之后自动
System.exit()
关闭java虚拟机,导致新线程无法继续执行
Quiet and Quick, Salute!
-
多进程与多线程通信同步机制
2020-08-05 12:02:34多进程通信方式 文件映射:本地之间 共享内存:本地之间 匿名管道:本地之间 命名管道:跨服务器 邮件槽:一对多的传输数据,通常通过网络向一台Windows机器传输 剪切板:本地之间 socket:跨服务器 ...多进程通信方式
- 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
- 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
个人对进程与线程的同步、通信的理解:
同步 (互斥) 是为了多个进程完成同一个任务(使用同一块资源)而进行相互适应的操作,这期间可能涉及到通信;
通信是进程间信息的交换,可能某些交换方式需要在实现同步(互斥)的前提下才能完成。以下转自https://www.cnblogs.com/youngforever/p/3250270.html
这两天看进程的同步与通信,看了几本书上的介绍,也从网上搜了很多资料,越看越迷惑,被这几个问题搞得很纠结。
- 进程同步与互斥的区别?
- 进程的同步方式有哪些?
- 进程的通信方式有哪些?
- 进程同步与通信的区别是什么?
- 线程的同步/通信与进程的同步/通信有区别吗?
在好多教材上(包括国内与国外的)也没有明确这些概念,现在对每个问题还没有准确的答案,下面将自己的理解记下来,以后再补充。
参考资料:
《操作系统教程》 孙钟秀主编 费翔林 骆斌 谢立参编 高等教育出版社
《计算机操作系统》 何炎祥 李飞 李宁 编著 清华大学出版社(进程管理部分与《操作系统教程》中的类似)
进程互斥、同步的概念
进程互斥、同步的概念是并发进程下存在的概念,有了并发进程,就产生了资源的竞争与协作,从而就要通过进程的互斥、同步、通信来解决资源的竞争与协作问题。
下面是根据《操作系统教程》3.1.4 中的介绍,整理的进程互斥、同步的概念。
在多道程序设计系统中,同一时刻可能有许多进程,这些进程之间存在两种基本关系:竞争关系和协作关系。
进程的互斥、同步、通信都是基于这两种基本关系而存在的,为了解决进程间竞争关系(间接制约关系)而引入进程互斥;为了解决进程间松散的协作关系( 直接制约关系)而引入进程同步;为了解决进程间紧密的协作关系而引入进程通信。
第一种是竞争关系
系统中的多个进程之间彼此无关,它们并不知道其他进程的存在,并且也不受其他进程执行的影响。例如,批处理系统中建立的多个用户进程, 分时系统中建立的多个终端进程。由于这些进程共用了一套计算机系统资源,因而, 必然要出现多个进程竞争资源的问题。当多个进程竞争共享硬设备、存储器、处理器 和文件等资源时,操作系统必须协调好进程对资源的争用。
资源竞争出现了两个控制问题:一个是死锁 (deadlock )问题,一组进程如果都获得了部分资源,还想要得到其他进程所占有的资源,最终所有的进程将陷入死锁。另一个是饥饿(starvation )问题,这是指这样一种情况:一个进程由于其他进程总是优先于它而被无限期拖延。
操作系统需要保证诸进程能互斥地访问临界资源,既要解决饥饿问题,又要解决死锁问题。
进程的互斥(mutual exclusion )是解决进程间竞争关系( 间接制约关系) 的手段。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。第二种是协作关系
某些进程为完成同一任务需要分工协作,由于合作的每一个进程都是独立地以不可预知的速度推进,这就需要相互协作的进程在某些协调点上协 调各自的工作。当合作进程中的一个到达协调点后,在尚未得到其伙伴进程发来的消息或信号之前应阻塞自己,直到其他合作进程发来协调信号或消息后方被唤醒并继续执行。这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。
进程间的协作可以是双方不知道对方名字的间接协作,例如,通过共享访问一个缓冲区进行松散式协作;也可以是双方知道对方名字,直接通过通信机制进行紧密协作。允许进程协同工作有利于共享信息、有利于加快计算速度、有利于实现模块化程序设计。
进程的同步(Synchronization)是解决进程间协作关系( 直接制约关系) 的手段。进程同步指两个以上进程基于某个条件来协调它们的活动。一个进程的执行依赖于另一
个协作进程的消息或信号,当一个进程没有得到来自于另一个进程的消息或信号时则需等待,直到消息或信号到达才被唤醒。不难看出,进程互斥关系是一种特殊的进程同步关系,即逐次使用互斥共享资源,也是对进程使用资源次序上的一种协调。
进程通信的概念
下面是根据《操作系统教程》3.5 中的介绍,整理的进程通信的概念。
并发进程之间的交互必须满足两个基本要求:同步和通信。
进程竞争资源时要实施互斥,互斥是一种特殊的同步,实质上需要解决好进程同步问题,进程同步是一种进程通信,通过修改信号量,进程之间可建立起联系,相互协调运行和协同工作。但是信号量与PV操作只能传递信号,没有传递数据的能力。有些情况下进程之间交换的信息量虽很少,例如,仅仅交换某个状态信息,但很多情况下进程之间需要交换大批数据,例如,传送一批信息或整个文件,这可以通过一种新的通信机制来完成,进程之间互相交换信息的工作称之为进程通信IPC (InterProcess Communication)(主要是指大量数据的交换)。进程间通信的方式很多,包括:
- 信号(signal )通信机制;
- 信号量及其原语操作(PV、读写锁、管程)控制的共享存储区(shared memory )通信机制;
- 管道(pipeline)提供的共享文件(shared file)通信机制;
- 信箱和发信/ 收信原语的消息传递(message passing )通信机制。
其中前两种通信方式由于交换的信息量少且效率低下,故称为低级通信机制,相应地可把发信号/ 收信号及PV之类操作称为低级通信原语,仅适用于集中式操作系统。消息传递机制属于高级通信机制,共享文件通信机制是消息传递机制的变种,这两种通信机制,既适用于集中式操作系统,又适用于分布式操作系统。
进程同步的方法
前面提到,进程互斥关系是一种特殊的进程同步关系,下面给出常见的进程同步的方法,实际上也可用于进程的互斥(个人理解)。
在何炎祥的《计算机操作系统》 3.2 节,将进程同步的机制与解决进程互斥方法看做是一样的,的明确指出互斥的软件解决方法为Dekker算法与Peterson算法,互斥的硬件解决方法为中断方法、以及使用机器指令的方法,后面又给出了信号量、管程、消息传递三种方法。
实际应用中,不同的系统有不同的进程同步方法,CSDN帖子http://bbs.csdn.net/topics/80156687中有一些讨论,Linux 与Windows的主要同步、通信机制如下:
Linux 下:
Linux 下常见的进程同步方法有:SysVIPC 的 sem(信号量)、file locking / record locking(通过 fcntl 设定的文件锁、记录锁)、futex(基于共享内存的快速用户态互斥锁)。针对线程(pthread)的还有 pthread_mutex 和 pthread_cond(条件变量)。
Linux 下常见的进程通信的方法有 :pipe(管道),FIFO(命名管道),socket(套接字),SysVIPC 的 shm(共享内存)、msg queue(消息队列),mmap(文件映射)。以前还有 STREAM,不过现在比较少见了(好像)。Windows下:
在Windwos中,进程同步主要有以下几种:互斥量、信号量、事件、可等计时器等几种技术。在Windows下,进程通信主要有以下几种:内存映射、管道、消息等,但是内存映射是最基础的,因为,其他的进程通信手段在内部都是考内存映射来完成的。
线程的同步/通信与进程的同步/通信有区别吗?
对于该问题,教材上没有明确的回答,教材上给出的一般是进程而非线程的同步、通信方式。但网络上很多说法将两者混为一谈。根据教材,以及网上的说法,个人的理解为:
同步机制:
信号量、管程、互斥是进程的同步机制,而信号量、互斥也可用于线程的同步,但管程只在进程同步中被用到;
线程的同步除了信号量、互斥外,还有临界区、事件,没有看到教材上将这两种方式作为进程的同步方式;
通信机制:
管道、FIFO、消息队列、信号量、共享内存是进程的同步机制,教材上没有线程的通信机制这样的说法,但可以肯定这几种方法是进程的通信方式,且其中的信号量既可用于进程的同步,又可用于进程的通信,在网络上还有说可以用于线程同步的。
管道与管程是不同的,管程是进程同步的方式,而管道则是进程通信的方式。
进程的同步/通信
下面是常见的线程之间的同步方式的详细介绍。
(注:下面转自网络,下面的同步、通信方式对于进程与线程分的不是很清楚,关于进程还是线程的解释见上面——线程的同步/通信与进程的同步/通信有区别吗?)
一、进程/线程间同步机制。
临界区、互斥区、事件、信号量四种方式
临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
2、互斥量:采用互斥对象机制。只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享 .互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目 .信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。
P操作申请资源:
(1)S减1;
(2)若S减1后仍大于等于零,则进程继续执行;
(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
V操作 释放资源:
(1)S加1;
(2)若相加结果大于零,则进程继续执行;
(3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。
4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作 .总结:
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。二、进程间通信方式
由于比较容易混淆,我们把进程间通信方法也列在这里做比较。
进程通信也就是所谓的IPC问题,主要是指进程间交换数据的方式。进程通信包括高级通信与低级通信,其中进程同步与互斥属于低级通信,主要用于插U农地控制信号;高级通信包括三种:共享存储系统(有的地方称作共享内存区)、消息传递系统(有的地方称作消息队列)、管道。
信号量是进程同步与互斥的常用方法,也可以作为低级的进程通信方法,用于传递控制信号。
简而言之,进程间通信方式主要包括管道、FIFO、消息队列、信号量、共享内存。
1.管道,还有命名管道和非命名管道(即匿名管道)之分,非命名管道(即匿名管道)只能用于父子进程通讯,命名管道可用于非父子进程,命名管道就是FIFO,管道是先进先出的通讯方式
2.消息队列,是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!!!!
3.信号量,它与WINDOWS下的信号量是一样的,所以就不用多说了
4.共享内存,类似于WINDOWS下的DLL中的共享变量,但LINUX下的共享内存区不需要像DLL这样的东西,只要首先创建一个共享内存区,其它进程按照一定的步骤就能访问到这个共享内存区中的数据,当然可读可写
以上几种方式的比较:
1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;(2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减1;(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几乎是完全一样的三、进程/线程同步机制与进程间通信机制比较
很明显2者有类似,但是差别很大
同步主要是临界区、互斥、信号量、事件
进程间通信是管道、内存共享、消息队列、信号量、socket
共通之处是,信号量和消息(事件)
小结:
- 进程互斥、同步与通信的关系:进程竞争资源时要实施互斥,互斥是一种特殊的同步,实质上需要解决好进程同步问题,进程同步是一种进程通信,由此看来,进程互斥、同步都可以看做进程的通信;
-
信号量是进程同步与互斥的常用方法,也可以作为低级的进程通信方法,用于传递控制信号;
-
管道与管程是不同的,管程是进程同步的方式,而管道则是进程通信的方式;
-
多进程/多线程间通信和同步互斥机制
2020-12-04 15:41:03而当“ 数据传输 ” 使用的是共享内存的方式,那么在多进程使用中又必然会涉及到同步和互斥的问题(举个例子:一个进程正在写一块共享内存时,另一个进程同时读取该共享内存,没有同步互斥机制,就可能产生脏数据)... -
【Java多线程】线程同步机制
2018-06-08 18:11:41如果多线程程序运行结果和单线程运行的结果是一样的,且相关变量的值与预期值一样,则是线程安全的。Java中与线程同步有关的关键字/类包括:volatile、synchronized、Lock、AtomicInteger等concurrent包下的原子类。... -
linux线程间通信及同步机制总结
2018-07-25 16:14:02线程间的通信有两种情况: 1、一个进程中的线程与另外一个进程中的线程通信,由于两个线程只能访问自己所属进程的地址空间和资源,故等同于进程间的通信。 2、同一个进程中的两个线程进行通信。本文说的就是第二种... -
多线程——多线程同步的三种实现方法
2018-12-01 23:56:31当使用多线程访问同一个资源的时候,非常容易出现线程安全的问题(例如,当多个线程同时对一个数据进行修改的时候,会导致某些线程对数据的修改丢失)。 因此,需要采用同步机制来解决这种问题。而Java主要提供了三... -
线程同步机制
2019-02-14 17:52:46从广义上说,Java平台提供的线程同步机制包括锁、volatile关键字、final关键字、static关键字和一些相关的API,如Object.wait( )/.notify( )等 1、锁的概述和概念: a 线程安全问题的产生: 多个线程并发访问... -
java多线程同步问题
2011-06-19 18:27:22多线程注意:wait()方法的调用要有判定条件常用 while () obj.wait(timeout, nanos); ... // Perform action appropriate to condition } synchronized会影响共享数据,但对其他语句的执行不会有规律了! -
多线程机制
2012-09-19 16:46:474、 线程的同步机制 8 5、 死锁 11 6、 线程间通信,也叫生产者与消费者问题 15 7、 浅析 Java Thread.join() : java多线程实现主线程等待所有子线程执行完毕 16 8、 线程运行中抛出异常的处理 19 9、 Callable 有... -
Java多线程之线程同步
2022-03-26 10:05:41为什么要创建多线程? 在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。 为什么要线程同步 多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,... -
多线程——线程实现、线程状态、线程同步、线程通信、线程池
2021-11-22 09:19:09多线程一、线程1.普通方法调用和多线程2.程序、进行、线程二、线程创建1.继承Thread类2.实现Runable接口3.实现Callable接口4.静态代理模式5、Lamda表达式三、线程状态1.线程状态2.线程方法3.线程停止4.线程休眠sleep... -
Java常见面试题汇总-----------Java多线程(多线程同步机制)
2019-04-20 20:35:151、确保线程互斥访问同步代码; 2、保证共享变量的修改能够及时可见; 3、有效解决指令重排序问题。 synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层... -
Java多线程|同步与锁机制
2016-06-03 20:06:43总结 Java「锁与同步」机制。 -
C++多线程-第五篇-同步机制
2016-11-12 16:46:25使用call_once包装的函数在多线程中只会被执行一次。 Void call_once(once_flag&flag, Callable && func,Args &&.....args); 其中once_flag 声明必须是线程安全的,可采用static.且不可使用临时变量给Call_... -
进程的同步机制有哪几种?
2021-02-24 17:17:391.什么是同步? 先说互斥,互斥:是指散布在不同任务之间...2.同步机制有哪几种? 互斥锁: 1)在访问共享资源后临界区域前,对互斥锁进行加锁。 2)在访问完成后释放互斥锁导上的锁。 3)对互斥锁进行加锁后,任何其他 -
【死磕线程】线程同步机制_java多线程之线程锁
2018-04-27 17:57:311、线程各种状态间的切换,用图表示的话简单清晰:图出处:https://www.cnblogs.com/bhlsheji/p/5099362.html(博主对每个状态解释的清晰明了)2、为什么需要同步: java允许多线程并发控制,当多个线程同时操作一... -
Java多线程之间的唤醒机制
2020-11-17 22:00:55线程之间的通信可以有效的利用资源,让线程之间的活动尽然有序,在线程之间的通信常用的方法有: 对象锁.wait();//让线程进入等待Block状态 对象锁.notify();//唤醒Block中的线程 对象锁.notifyAll();//唤醒所有的... -
Java多线程(详细了解java多线程机制)
2022-04-30 14:35:44每天进步一点点一、程序、进程、线程1.1 什么是程序1.2 什么是进程1.3 什么是线程1.4 进程和线程的区别二、创建线程的三种方式2.1 继承Thread...状态3.3 阻塞状态四、线程常用的方法4.1 线程休眠(sleep)4.2 线程放弃 -
Android-线程常用方法-线程同步
2019-02-16 15:45:52线程常用方法: 1.start():线程调用该方法将启动线程从新建状态进入就绪,一旦轮到享用CPU资源时,就开始自己的生命周期 2.run():Thread类的run()方法与Runnable接口的run()方法的功能和作用相同,都用来定义线程对象... -
Qt多线程同步
2018-09-15 12:06:04多线程的同步就是使多个线程在同时执行同一段代码的时候,有顺序的执行,不会出现同时有两个或者多个线程执行同一段代码的情况,特别是在对变量或者文件执行写操作的时候。也就是所谓的线程安全,线程安全指的是这段... -
进程同步及线程同步的几种机制
2018-09-14 09:09:35进程中线程同步的四种常用方式: 1. 互斥量: 采用互斥对象机制,只有拥有互斥对象的线程才有访问...3. 事件(信号):通过通知操作的方式来保持多线程同步,还可以方便实现多线程优先级的比较作。 4.临界区:临界... -
Java多线程同步和通信
2019-12-07 10:15:47多线程同步 回顾 1 进程:正在运行的程序,操作系统通过进程Id区分不同进程。 2 线程:进程中的一条执行路径。一个进程中可以包含多个线程,至少有一个。 3 区别: a.一个程序运行后至少有一个进程 b.一个进程可以... -
线程同步的四种常用方式
2020-09-17 11:03:21当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。具体应用方式: 1、 定义临界区... -
多线程:线程同步的几种方式
2019-01-13 19:44:051.synchronized同步方法 (静态方法锁住类对象,其它方法锁住实例对象) 即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在... -
线程同步概念及常用同步方法
2020-06-13 20:34:56线程同步概念及常用同步方法 ...线程间缺乏必要的同步机制 2.互斥锁mutex 使用步骤 pthread_mutex_t lock; //创建锁 pthread_mutex_init(); //初始化锁 pthread_mutex_lock(); //加锁 //访问共享的 -
Python多线程—线程同步
2019-03-25 23:05:17这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。 线程同步的真实意思和字面意思恰好相反。 线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个... -
线程同步常用方式与区别
2017-06-20 14:02:12在引入多线程后,由于线程执行的异步性,会给系统造成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一起,难于区分。当多个线程急用共享变量,表格,链表时,可能会导致数据处理...