并发编程的引入

1.为什么要引入并发编程?

  因为我们要面对很多人同时访问服务,并且我们想尽可能减少服务时间,于是引入并发,把能同时做得事同时做。

2.并发编程带来了什么困难?

  并不是单纯开多个线程就能让程序最大限度并发执行,如果想让程序执行更快,问题有:

1.上下文切换问题。我需要知道上下文保存并返回。
2.死锁问题。单线程不存在死锁。
3.受限于硬件和软件的资源限制问题。

3.这挑战怎么解决?

1.减少上下文切换的方法:无锁化编程(是因为锁才有了切换,如果将数据ID取模分段,不同线程处理不同数据段就不会有锁了);CAS算法(使用CAS更新数据,采用比较版本号,不用锁);使用最少线程(避免创建不需要的线程,更改配置文件即可);协程(单线程里实现多任务的调度)。

2.出现死锁了,通过dump线程查看是哪个线程出了问题,然后解决。避免死锁:1.避免一个线程同时获得多个锁。 2.避免一个线程在锁内占用多个资源。 3.尝试使用定时锁。

3.在很多资源(网速、数据库IO速度、硬盘读写速度)受限的机器里,实用并发反而因为创建调换上下文会导致CPU利用率爆了,任务反而无法完成。 这时候可以考虑搭建集群进行并行计算,用一些配置较低的机器,通过ODPS、hadoop搭成集群,处理数据。

Java并发原理相关

并发机制的底层实现原理

Java -> 字节码 -> 汇编指令
Java中所使用的并发机制依赖于JVM实现和CPU的指令。

volatile的应用

1.volatile的定义

  如果一个字段被声明成volatile,那么Java线程内存模型保证所有线程看到这个变量的值是一致的。

2.volatile如何保证可见性?

  查看汇编指令,有volatile修饰的变量在写操作会多出一个lock命令。查看汇编手册,lock的指令会完成:

  1.将当期处理器缓存行的数据写回到系统内存。
  2.这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。

  为了提高速度,CPU是与缓存交互,多核CPU就有多个缓存,每个缓存交互完了什么时候重新写回内存?怎么保证写回了其他的缓存值不是旧的? 在多处理器下,为了保证各个处理器缓存一致,采用缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期,当发现自己缓存行对于的内存地址被修改了,就把自己处理器缓存区的值设为无效,当需要用到这个数据时,再重新从内存中读。

Synchronized的原理与应用

1.Java中的每一个 对象都可以作为锁,具体三种形式:

  对于普通同步方法:锁是当前实例对象。
  对于静态同步方法:锁是当前类的Class对象。
  对于同步方法块:锁是Synchronized括号里配置的对象。
  然后对于线程来说,如果想要访问同步代码块,必须先得到锁,当退出或者抛出异常时必须释放锁。并且以上三种情况下,锁的实现还有不同。

2.synchronized用的锁存放在Java对象头中,Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄、是否为偏向锁、锁标记位!

3.synchronized被称为重量级锁,而JDK6中为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁。

  于是JDK6中,锁的四种级别由低到高:无锁状态、偏向锁状态、轻量级锁、重量级锁,这几个状态会随着竞争状态而升级。

  偏向锁:大多数情况下,锁并发多线程竞争,而是同一进程多次获得。偏向锁即一个线程获得锁后,在对象头中存入锁偏向的线程ID,之后该线程再进出同步块不需要进行CAS操作,只需要测试对象头中是否存储着该线程的偏向锁。测试失败则继续CAS操作加锁。 JDK6、7默认的就是偏向锁,当你用JVM指令关闭或者竞争升级后,才会开启轻量级锁。

  轻量级锁:CAS修改Java对象头的Mark Word,成功则将其替换为轻量级锁,执行同步体;失败则进行自旋,等待其他进程完成。当然因为自旋会消耗CPU,所以不适合始终得不到锁竞争的线程,但是优点是响应时间快,同步执行速度快。

  重量级锁:线程竞争不会自旋、不会消耗CPU。但是线程阻塞,响应时间慢,追求吞吐量。

4.CPU如何实现原子操作?

  1.使用总线锁保证原子性。
  2.使用缓存锁保证原子性。

5.Java中如何实现原子操作?

  1.通过锁机制实现原子操作。锁机制保证了只有获得锁的线程才能操作锁指定的内存区域,并且除了偏向锁,其他锁都使用了循环CAS,当一个线程要进入同步块时,通过循环CAS来获取锁;当一个线程退出同步块时通过循环CAS释放锁。

  2.通过循环CAS实现原子操作。···。然而CAS也存在问题:1.ABA问题,改了两次改回来了,解决办法是在变量前最佳版本号,每次更新时版本号+1。 2.循环时间长开销大,自旋CAS如果一直不成功,会给CPU带来很大开销。 3.只能保证一个共享变量的原子性。取巧的办法是进行合并,i=1,j=2合并为ij=12,事实上java也是这么做的,提供了一个类把多个变量放在一个对象里。

内存模型

Java内存模型的基础

1.线程如何通信?

  在命令式编程中,线程通信可以通过:共享内容、消息传递。在共享内存模型中,线程可以通过读写公共状态隐式通信;在消息传递模型中,线程必须通过发送消息显式通信。

2.线程如何同步?

  在共享内存模型中,同步是显式进行的,程序员必须指定某个方法或者代码段在线程之间互斥执行。消息传递模型中,同步是隐式进行的,即消息发送必须在消息接受之前。
Java的并发采用的是共享内存模型(即线程A向线程B发送消息,实际上是线程A更改共享内存数据,线程B读取共享内存数据,得知改变,通信过程必须经过主内存),即线程通信隐式进行,且通信过程对程序员来说是透明的。

3.Java内存模型的抽象结构

  即JVM相关,所有实例域、静态域和数组元素都存储在堆内存中,堆内存是线程共享的。局部变量、方法定义参数、异常处理器参数并不线程共享。Java线程通信由Java内存模型  (JMM)控制,JMM决定一个线程对共享内存的写入何时对另一个线程可见。
  抽象来说,共享变量存储在主内存中,每个线程有各自本地内存。本地内存只是逻辑概念,并不存在。

4.源代码到指令重排

  在执行程序时,为了提高性能,编译器和处理器经常会对指令进行重排序,大概可分三类:1.编译器优化的重排序(JMM会规定某些排序禁止重排)。 2.指令级并行的重排序。 3.内存系统的重排序(JMM在某些情况下,会插入内存屏障,禁止某些重排)。因为指令重排,处理器对内存的读写顺序,并不一定与实际读写顺序一致,所以需要内存屏障。

5.happens-before简介

  JDK5引入新的内存模型,使用happens-before概念来阐述操作之间的内存可见性。即一个操作执行的结果需要对另一个操作可见,则这两个操作需满足happens-before关系。
常见的happens-before规则:程序顺序规则、监视器锁规则、volatile规则、传递性。我们可以通过happens-before规则避免一些简单的指令重排。

6.指令重排

  如果有两个操作访问同一变量,且其中一个操作为写,则只要顺序改变,程序执行结果一定会改变,这叫做数据依赖性。而编译器和处理器在重排时不会改变存在数据依赖性的两个操作的顺序。
  as-if-serial语义:不管怎么重排序,单线程程序执行的结果不能被改变。编译器、runtime、处理器必须遵循as-if-serial语义,正式因为as-if-serial语义把单线程程序保护起来了,所以我们写单线程程序时会以为单线程没有指令重排,就是顺序执行,这是错误的。
  指令重排的意义在于,在不改变程序执行结果的情况下,尽可能提高并行度。

  但是在多线程程序中,指令重排可能会导致程序结果改变。

Java内存模型的顺序一致性

  顺序一致性模型可以视为一种理想模型,处理器和Java语言的内存模型都以其为参照,JMM按照其理念实现,但也无法完全实现。
  顺序一致性模型特点:1.单线程中的所以操作必须按照程序顺序执行。 2.所有线程都只能看到一个操作执行顺序,且该顺序都是原子操作,且执行立刻对所有线程可见。

  JVM在堆上分配对象时,首先对内存空间清零,然后再在其上面分配对象。因此在已清零的内存空间上分配对象时,域的默认初始化已经完成。

同步原语

1.volatile的内存语义、实现及更新(JDK5中增加了volatile、final的语义内存)

2.锁的内存语义

3.final的内存语义

4.happens-before的定语、规则、实现

5.双重检查锁与延迟初始化
  双检锁有两种实现,带volatile的那种才是正确的,可以实现线程安全的延迟初始化。

Java内存模型的设计

对读写顺序进行不同程度的放松,衍生了不同的内存模型:TSO、PSO、RMO、PowerPC
所有的处理器内存模型都运行写-读重排序,因为他们都有写缓存区。

Java内存模型的保障:
  1.单线程程序。单线程程序不会出现内存可见性的问题,上文的as-if-serial语义会规定···
  2.正确同步的多线程程序。正确同步的多线程程序执行将具有顺序一致性。
  3.为同步/未正确同步的多线程程序。JMM提供了最小安全性保障,线程执行时取到的值,要么是之前某个线程写入的值、要么是默认值(0、null、flase)

并发编程基础知识

1.什么是线程?

2.为什么要使用多线程?

3.线程优先级?

4.线程的状态?

5.Daemon线程

6.启动和终止线程?

7.线程间通信

  1.volatile、synchronized关键字

  2.等待、通知机制

  3.管道输入、输出流

8.Thread.join()的使用

9.ThrealLocal的使用

10.线程池技术

Java中的锁

1.Lock接口

2.队列同步器

3.重入锁ReentrantLock

4.读写锁

5.LockSupport工具类

6.Condition接口

Java给我们造好的轮子

并发容器和框架

1.ConcurrentHashMap的实现原理与使用

2.ConcurrentLinkedQueue的实现原理与使用

3.Java中的阻塞队列以及4种处理方式

4.Fork-Join框架

13个原子操作类

1.原子更新基本类型类(Atomic)

2.原子更新数组

3.原子更新引用类型

4.原子更新字段类

并发工具类

1.等待多线程完成的CountDownLatch

2.同步屏障CyclieBarrier

3.控制并发线程数的Semaphore

4.线程间交换数据的Exchanger

线程池

1.线程池实现原理

2.线程池的使用

Excutor框架

任务执行框架