Java 并发 总结

Java 并发 总结

Thread

控制策略

  • 直接控制线程
  • 交给 Executor 管理线程

线程启动

  • 实现 Runnable 接口run 方法,调用 new Thread(Runnable).start() 运行线程;灵活,可以继承其他类
  • 继承 Thread 类,覆盖 run 方法;不可继承其他类,不灵活

Thread.sleep(time)

暂停当前方法的运行,进入睡眠状态,让出CPU资源,此方法会抛出 InterruptException。

Thread.interrupt()

打断线程的运行状态。Thread 类有一个状态量 status,用来表明当前线程的打断状态。

调用静态方法 Thread.interrupted() 返回 status 会重置 status

调用实例方法 thread.isInterrupted() 直接返回 status 状态。

thread.join()

等待线程实例结束运行。可以指定等待时间,会抛出 InterruptedExection.

synchronized (同步)

目的

多线程运行可以提高程序的运行效率。但线程运行的时候访问同一个资源,那么会影响对资源的使用。 同步的目的是为了降低或消除线程之间的影响或干扰。

例如: 线程之间互相干扰,内存一致性错误。

线程干扰

如两个非常简单的 java 语句自增和自减操作: c ++;c--; 自增操作在jvm虚拟机里会分成若干个步骤去执行:

  • 获取 c 当前的值
  • 对取的值加1
  • 把结果放回 c 中 自减操作也可以用相同的方法进行分解。

假设 c=0, 当线程 A 执行自增操作,同时 线程 B 进行自减操作是,就有可能发生一下这种情况:

  • 线程 A: 获取 c的值
  • 线程 B: 获取 c的值
  • 线程 A: 对取的值加 1,结果为 1
  • 线程 B: 对取的值减 1,结果为 -1
  • 线程 A: 保存结果,c = 1
  • 线程 B: 保存结果,c = -1 最终的结果是,A操作被B覆盖了。这只是其中的一种可能,也有可能 B 的操作被 A 覆盖,也可能 正常运行。但是,这些行为都不可预测。

内存一致性错误

避免内存一致性错误的关键策略是弄懂 happen-before 关系: 这种关系保证 java语句对内存的写操作可以被其他java语句看见。

有几种操作可以创建 happend-before 关系,其中一个就是 synchronization

目前我们已经知道有两种操作可以创造 happend-before 关系(HB):

  • 跟语句 Thread.start() 有 HB 关系的每条语句 ,这每条语句也就跟 线程执行的每条语句有 HB 关系
  • 当前进程因为 Thread.join() 陷入等待的在恢复运行后,被等待的进程所执行的每条语句跟 当前进程 join() 后面的语句构成 HB 关系。也就是说 被 join 的线程的操作可被 当前线程看见。

synchronized 方法 (同步方法)

给一个实例方法增加 synchronized 关键词后,此方法就变成了同步方法。synchronized 关键词有两种效果:

  • 避免交错的调用一个声明为 *synchronized *的方法。当一个线程调用一个实例对象的同步方法时,其他线程想要调用同一个对象的同一个同步方法时,必须等待之前的线程执行完成。
  • 当一个同步方法执行完成退出时,当前线程的跟后续的线程自动建立了 happend-before 关系,这就保证了一个线程的对内存的修改可以被后续的线程所知。

同步方法使用了一个简单的策略保证不会出现线程相互干扰和内存的一致性问题