关于线程并发的一些底层原理记录
进程:进程是是程序的一次执行过程,是程序在执行过程中分配和管理资源的基本单位。
线程:是CPU调度的最小单位。它比进程更小,一个进程可以包含多个线程,线程会共享进程的堆和方法区资源。每个线程也有自己的程序计数器,虚拟机栈和本地方法栈。
一个java程序的运行是main线程和其他线程同时运行。
因为多线程运行:
原因:单核时代:多线程主要提高单进程利用CPU和IO的效率。一个线程被IO阻塞,其他线程可以继续使用CPU。
多核时代:多线程主要为了提升利用多核CPU的能力。
进程之间通信:不同进程之间如何交换传播信息
1.管道:无名管道,半双工的。只用于父子进程和兄弟进程之间
有名管道:在数据读出时,FIFO管道同时清除数据,并且先进先出,可用于任何进程。
2.信号:比较复杂的通信方式,用于通知接收进程某一事件已经发生。
3.信号量:是一个计数器,用来控制多个进程对共享资源的访问,通常作为一种锁机制,防止某进程在访问共享资源时,其他进程也访问该资源。
4.消息队列:由消息组成的链表,存放在内核中,由消息队列标识符标识。其优势对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,可以接受特定类型的消息
5.共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
6.套接字:一种通信机制,可用于不同机器之间的进程间通信,例如客户端/服务器
进程之间如何同步
1.临界区:多线程串行化来访问公共资源,速度快,适合控制数据访问。但是只适合同步本进程内的线程。
2.互斥量:拥有互斥对象 的线程才具有访问资源的权限。不仅在同一应用程序中不同线程中实现资源的安全共享,还可以在不同应用程序的线程之间实现对资源的共享。
3.信号量:控制一个具有有限数量用户资源而设计。允许多个资源在同一时刻访问同一资源,但是有最大线程数目。
4.事件:用来通知线程有一些事情发生,从而启动后继任务的开始。
线程同步:
1.临界区:拥有临界区的线程可以访问资源,其他线程访问则被挂起,直到临界区线程释放
2.事件:允许一个线程在处理完一个任务后,主动唤醒另一个线程
3.互斥量:允许在进程之间使用
4.信号量:通过一个计数器来限制可以使用某共享资源的线程数目。
线程与并发:
多线程并发就是为了能够提高程序的执行效率,及运行速度
但是多线程编程会产一个问题:并发的线程安全的问题。
因此多线程访问共享资源的时候,需要同步(串行),来保证变量的唯一性和准确性。
synchronized 关键字:
保证原子性,可见性,有序性
原子性:保证语句块内操作时原子的
可见性:unlock之前必须把此变量同步回主内存
有序性:通过一个变量在同一时刻只允许一条线程对其进行lock操作
synchronized关键字修饰的方法或代码块在任意时刻只有一个线程执行。
主要有:
1.修饰实例方法,作用域当前对象实例加锁,需要获得当前对象实例的锁
2.修饰静态方法:给当前类加锁,作用域类的所有对象实例,要获得当前类的锁。
3.修饰代码块:指定加锁对象,给指定对象/类加锁。sychoronized(this|object),synchronized(类.class)
底层原理:
属于JVM层面
synchronized同步语句块的实现:使用的Monitorenter和moniterexit指令,代表同步代码块的开始位置和结束位置
当执行monitorenter指令时,线程试图获取锁,及获取对象监视器monitor的持有权。如果锁的计数器为0,则表示可以获取,获取锁之后将锁计数器+1。
在执行 monitorexit 指令后,将计数器-1。当锁计数器为 0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
synchronized修饰方法: 隐式的,无需通过字节码指令控制,实现在方法调用和返回操作中。
ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
不过两者的本质都是对对象监视器 monitor 的获取。监视器锁本质为操作系统的mutex lock(互斥锁),因此线程之间的切换需要从用户态和内核态,因此其效率低下。这种依赖于mutex lock实现的锁称之为重量级锁。
JVM对synchronized的优化
偏向锁 --> 轻量级锁 --> 重量级锁
只可升级不可降级
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入偏向锁。
当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程再进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。
如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置为1(表示指向当前进程):如果没有,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前进程。
CAS原理:
Compare and Swap,即