线程同步基础
文章结构:
一、同步是什么?为什么要有同步?
当不同的线程对于同一个对象中的field进行操作时,由于线程调度的原因,某一个线程的操作如果不是只有一条原子操作,则并不一定能够完整地进行,而在中途被其他线程打断。
这样数据在逻辑上就产生了错误;同步就是为了解决这种错误产生的机制,能够保证对于一个方法或是一个代码块,每次只有一个线程且只有当该方法返回or代码块执行完之后,才能由另一个线程执行。
二:实现同步的方式
- 使用synchronized关键字
- 使用ReentrantLock
java核心编程思想列出了这两种机制,其中synchronized关键字使用较为简洁而后者因为需要显示地对Reentrant对象进行操作而略微复杂。
具体的使用方式不再详细说明,只说明几个在看的时候顺带搜索过的几个相关问题。
Q1:什么是内部锁?
A1:每个对象都会有一个intrinsic lock,当
Q2:什么是重入锁?(ReentrantLock的意思就是“重入锁”)
Q3:两种同步方法的选择?
Q4:死锁、活锁、饥饿的含义
A4:死锁会造成线程阻塞,并且不能自行解开;比如线程A、B的执行需要资源r1、r2,但某一时刻r1被A占用而r2被B占用,此时A、B均占用一部分资源并等待对方释放资源,会一直僵持下去、造成阻塞。
活锁不会造成线程阻塞,有可能自行解开; 关于活锁的例子http://stackoverflow.com/questions/1036364/good-example-of-livelock ,资源有限而两个线程会不断地释放资源,并一直进行下去;一个形象地比喻是“在一条很窄的走廊里,A、B每次想让路给对方都发现对方同时也给自己让路了,导致A、B永远都在让路。
饥饿的含义是由于线程的优先级等原因,某些线程可能永远也分配不到执行时间。
- 对象锁、方法锁、类锁:
- 这三种说法...有些混乱,并不是官方的、标准的定义,只是不同情况下的一种代称
- 方法锁就是对象锁
- 对象锁:不论是同步方法还是同步代码块,调用方法都需要先获得方法or代码块中中涉及的到的对象的内部锁;称为对象锁,是因为锁仅和对象实例相关,某个类中的一个方法A同步了并不能影响其他不相关的域和方法的访问(访问不需要获取锁)。
- 类锁:对于静态同步方法,因为静态方法不属于任何实例而只属于类本身,所以访问静态同步方法需要获取该类的Class对象的对象锁。也就是说类锁本质上也是对象锁,只不过是类对应的Class对象而已。
- http://stackoverflow.com/questions/4453349/what-is-the-class-object-java-lang-class(Class类是什么)
- http://stackoverflow.com/questions/437620/java-synchronized-static-methods-lock-on-object-or-class (类锁&对象锁)
- 内部锁:
- 所有对象均含有一个内部锁
- 线程必须获得(acquire)对象的内部锁才能够对其进行操作
- 在对象的内部锁被某一个线程释放之前,无法被其他线程获得
- synchronize方法
- 直到方法返回;未捕获异常造成的return也包括在内
- 对于静态同步方法,会获得与类相关的“类对象”的锁,这与对象锁不同
- 同步statement
- 必须传入一个对象以提供内部锁(可以传入this)
- 原子操作
- 除去long/double之外的基本数据类型的操作
- 不能排除memory consistency error(对同一个int型变量,两个线程依次start,分别执行++、--,最终结果不一定不变)
- volatile
- 使修饰的变量的操作变成原子操作
- happened-before relationships
- 一条语句的执行对另一条语句是可见的---即能够保证语句的先后执行顺序
- Guarded Lock
- 线程之间协作的常用方法
- 当线程A需要等待线程B中的某个条件发生改变再执行,如果使用在A中使用while循环检测这个条件会十分浪费时间;通常做法是将线程A中该方法同步,使用while判定条件后,调用wait()方法将该线程挂起,同时释放内部锁,进入等待状态;在线程B中等条件符合时,调用notify()/notifyAll()唤醒等待该锁的线程A,继续操作
- java的官方文档里使用了一个生产/消费者模式的传输、打印消息的实例来说明http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
- wait()/notify()/notifyAll()必须在同步方法or同步代码块里面执行