概念

同步异步


消息通信机制


  • 同步:发出调用之后,在没有得到结果之前,该调用不返回。调用者主动等待调用结果。
  • 异步:发出调用之后,该调用直接返回,无结果。被调用者通过状态、通知进行反馈,或者通过回调函数处理这个调用。

阻塞非阻塞


调用者等待调用返回值时的状态


  • 阻塞:调用结果返回之前,当前线程[ 调用者]会被挂起
  • 非阻塞:不能立刻得到结果之前,调用者线程不会被阻塞


锁机制的意义

Java 允许多线程并发控制,当多个线程同时操作(增删改查)一个被共享的资源变量时,可能导致数据出现不正确的结果。加入锁保证了变量的唯一性,准确性。

Java 中的锁

公平锁、非公平锁


公平锁要求多个等待锁的线程,必须按照申请锁的顺序获得锁,而非公平锁允许抢占锁。公平锁的优点是稳定,等待锁的线程不会饿死;非公平锁的优点是性能优,因为某些线程可以避开Blocking,Runnable 之间的状态转换。


自旋锁


线程在进行状态转换(操作系统层面)过程要耗费很多时间,并且很多时候共享数据的锁定状态只会持续很短一段时间,为了节省这段很短的时间去挂起、恢复现场并不值得。如果机器上有两个以上的处理器,可以让两个线程并行执行,我们可以后面的线程“稍等片刻”,让他执行一个“自旋”。自旋等待不能代替阻塞。自旋占用处理器时间,如果等待时间长,还是要让线程阻塞。自旋只是在轻量级锁中使用,在重量级锁中,线程不能使用自旋。


锁消除


锁消除是虚拟机JIT在运行时,检测一些被同步的代码,不可能存在共享数据竞争的情况,锁将进行消除的操作。


锁粗化


锁范围扩展被称之为锁粗化。


可重入锁


可重入锁,也叫递归锁。线程A 持有一个锁,线程B 不持有锁,B 访问被这个锁保护的代码就会被阻塞,而A 则可以利用其持有的这个锁多次访问这段代码。Java 内置锁(synchronize)和Lock(ReentrantLock)都是可重入的。


类锁和对象锁


public class LockStrategy
{
public Object object1 = new Object();
// 类锁
public static synchronized void method1(){}
public void method2(){
synchronized(LockStrategy.class){}
}
// 对象锁
public synchronized void method4(){}
public void method5()
{
synchronized(this){}
}
public void method6()
{
synchronized(object1){}
}
}




例题,有一个类这样定义:

public class SynchronizedTest
{
public synchronized void method1(){}
public synchronized void method2(){}
public static synchronized void method3(){}
public static synchronized void method4(){}
}


那么,有SynchronizedTest的两个实例a和b,对于一下的几个选项有哪些能被一个以上的线程同时访问呢?


A. a.method1() vs. a.method2()


B. a.method1() vs. b.method1() // a,b,对象锁


C. a.method3() vs. b.method4()


D. a.method3() vs. b.method3()


E. a.method1() vs. a.method3() //对象锁,类锁


答案是什么呢?BE


偏向锁、轻量级锁和重量级锁


Java 对象内存布局中,对象头的Mark Word:



Java - 线程同步方式_公平锁



整个 synchronized 锁流程如下:

  1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁(偏向于第一个获得它的线程)
  2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
  3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
  4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
  5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
  6. 如果自旋成功则依然处于轻量级状态。
  7. 如果自旋失败,则升级为重量级锁

悲观锁、乐观锁


悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
乐观锁:假定不会发生并发冲突,只在提交操作时检测是否违反数据完整性。(使用版本号或者时间戳来配合实现)


共享锁,排它锁


共享锁:读操作。事务A 对Data 加锁之后,事务B 依然可以对Data 加共享锁(排它锁不可以),读操作。


排它锁:读、修改操作。事务A 对Data 加锁之后,事务B 不可以对Data 加任何锁。


读写锁


资源能够被多个读线程访问,或者,只被一个写线程访问(不能存在读线程)。Java当中的读写锁通过ReentrantReadWriteLock实现。


互斥锁


所谓互斥锁就是指一次最多只能有一个线程持有的锁。synchronized和Lock就是互斥锁。





线程同步的方式有哪些?


  1. 同步方法:synchronized 关键字修饰的方法
  2. 同步代码块:synchronized 关键字修饰的语句块
  3. 使用特殊域变量(volatile)实现线程同步
  4. 使用reentrantLock 实现线程同步
  5. 使用局部变量实现线程同步:如果使用ThreadLocal 管理变量,则每一个使用该变量的线程都获得一个该变量的副本,副本之间相互独立,这样每个线程都可以修改自己的变量副本,而不会对其他线程产生影响。