资源限制
资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。 例如服务器的带宽只有2Mb/s,某个资源的下载速度是1Mb/s每秒,系统启动10个线程下载资源,下载速度不会变成10Mb/s,所以在进行并发编程时,要考虑这些资源的限制。
常见资源限制有:
- 带宽的上传/下载速度
- 硬盘读写速度
- CPU的处理速度
- 数据库的连接数
- socket连接数等
在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源仍然在串行执行,这时候程序不仅不会加快执行反而会更慢,因为增加了上下文切换和资源调度的时间。
Java并发机制的底层实现原理
Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令。
volatile
Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步:
-
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
-
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量,并更新本地内存的值
Volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行:
-
在每个volatile写操作的前面插入一个StoreStore屏障
-
在每个volatile写操作的后面插入一个StoreLoad屏障
-
在每个volatile读操作的前面插入一个LoadLoad屏障
-
在每个volatile读操作的后面插入一个LoadStore屏障
synchronized
在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但是随着Java SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重了。
Java中的每一个对象都可以作为锁,具体表现 为以下3种形式:
- 对于普通同步方法,锁是当前实例对象,如
public synchronized void method() - 对于静态同步方法,锁是当前类的Class对象,如
public synchronized static void method() - 对于同步方法块,锁是Synchonized括号里配置的对象,如
synchronized(lock) { //… }
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在 Java SE 1.6中,锁一共有三种状态,这几个状态会随着竞争情况逐渐升级:
-
偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁
-
轻量级锁:线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁
-
重量级锁:轻量级锁失败后会膨胀为重量级锁。其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争
原子操作
在Java中可以通过锁和循环CAS的方式来实现原子操作。
从Java 1.5开始,JDK的并发包里提供了一些类来支持原子操作,如AtomicBoolean(用原子 方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更新的long值)。这些原子包装类还提供了有用的工具方法,比如以原子的方式将当前值自增1和自减1。CAS虽然很高效地解决了原子操作,但是存在以下问题:
- ABA问题:为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它 的值没有发生变化
- 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销
- 只能保证一个共享变量的原子操作:对多个共享变量操作时,循环CAS就无法保证操作的原子性
锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。JVM内部实现了很多种锁机制,有偏向锁、轻量级锁和互斥锁。
Java内存模型
在并发编程中,需要处理两个关键问题:线程之间如何通信及线程之间如何同步,线程之间的通信机制有两种:共享内存和消息传递:
-
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信
-
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信
Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。Java线程之间的通信由Java内存模型(JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。
锁是Java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中;当线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使得临界区代码必须从主内存中读取共享变量。




近期评论