1.synchronized—对象加锁
synchronized方法包括两种,一是标注了synchronized关键字的方法,一种是synchronized代码块.而不论是同步代码块还是同步方法都具有了原子性和可见性.
1.1 原子性
原子性指的是一个时刻,只能有一个线程执行一段同步代码或一个同步方法,这个同步代码段或这个同步方法会通过一个monitor object保护.
作用:防止多个线程在更新共享时相互冲突.
原理:对象监视器(锁),只有获取到监视器的线程才能继续执行,否则线程会等待获取监视器,java中每个对象或者类都有一把锁与之对应,对于对象来说,监视的是这个对象的实例变量,对于类来说,监视的是类变量(一个类本身是类Class的对象,所以与类关联的锁也是对象锁),当线程执行到同步代码块或同步方法时,JVM会锁住这个引用对象,当离开时才会释放这个引用对象上的锁.对象锁是JVM内部机制,锁的获取黑盒释放是JVM完成的.
1.2 可见性
可见性会消除内存缓存和编译器优化的各种反常行为,也就是说它必须保证释放锁之前对共享数据做出的更改对于随后另一个获得此锁的线程是可见的.
作用:如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是另一个线程修改前的值或不一致的值.
原理:当对象获取锁时,它首先使自己的高速缓存无效,这样就可以保证直接从主存中读取变量的值,同样,在对象释放锁之前,它会刷新告诉缓存,强制使做的任何修改都出现在主存中,这样,就能保证在同一个锁上同步的两个线程在synchronized块修改的变量的相同值.
1.3为什么要使用同步
- 读取某个变量的值可能已经被另一个线程修改过.
- 写入的某个变量的值可能马上被另一个线程读取.
1.4synchronized的限制
synchronized虽然使用简单,但是它的功能有限制.
* 它无法中断一个正在等待获锁的线程.
* 也无法通过投票得到锁,如果不想等下去,也就没法得到锁;
* 同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
1.5synchronized例子
用synchronized实现1-9的打印,要求,线程A打印123,然后线程B打印456,然后线程A打印789. 具体代码如下:
package thread;
/**
* Created by yang on 16-7-10.
*/
public class WaitNotifyDemo {
private volatile int val = 1;
private synchronized void printAndIncrease() {
System.out.println(Thread.currentThread().getName() + " prints " + val);
val++;
}
// print 1,2,3 7,8,9
public class PrinterA implements Runnable {
@Override
public void run() {
while (val <= 3) {
printAndIncrease();
}
// print 1,2,3 then notify printerB
//WaitNotifyDemo.this代表的是WaitNotifyDemo实例
synchronized (WaitNotifyDemo.this) {
System.out.println("PrinterA printed 1,2,3; notify PrinterB");
// System.out.println("yang"+WaitNotifyDemo.this);
WaitNotifyDemo.this.notify();
}
try {
while (val <= 6) {
synchronized (WaitNotifyDemo.this) {
System.out.println("wait in printerA");
WaitNotifyDemo.this.wait();
}
}
System.out.println("wait end printerA");
} catch (InterruptedException e) {
e.printStackTrace();
}
while (val <= 9) {
printAndIncrease();
}
System.out.println("PrinterA exits");
}
}
// print 4,5,6 after printA print 1,2,3
public class PrinterB implements Runnable {
@Override
public void run() {
while (val < 3) {
synchronized (WaitNotifyDemo.this) {
try {
System.out.println("printerB wait for printerA printed 1,2,3");
WaitNotifyDemo.this.wait();
System.out
.println("printerB waited for printerA printed 1,2,3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
while (val <= 6) {
printAndIncrease();
}
System.out.println("notify in printerB");
synchronized (WaitNotifyDemo.this) {
WaitNotifyDemo.this.notify();
}
System.out.println("notify end printerB");
System.out.println("PrinterB exits.");
}
}
public static void main(String[] args) {
WaitNotifyDemo demo = new WaitNotifyDemo();
demo.doPrint();
}
private void doPrint() {
PrinterA pa = new PrinterA();
PrinterB pb = new PrinterB();
Thread a = new Thread(pa);
a.setName("printerA");
Thread b = new Thread(pb);
b.setName("printerB");
// 必须让b线程先执行,否则b线程有可能得不到锁,执行不了wait,而a线程一直持有锁,会先notify;
b.start();
a.start();
}
}
执行过程如下:
线程b首先执行,得到WaitNotifyDemo类对象的锁,由于此时val<3,线程B执行wait(),释放了锁,线程A获得锁执行,输出123,线程A通知线程B,线程A执行wait(),释放锁.线程B获得锁,输出456,通知线程A,然后线程B退出,释放锁,线程A获得锁,输出789,最后退出.
2.ReentrantLock
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上.)
3.线程件通信Condition
Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notify().
传统线程的通信方式,Condition都可以实现.Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法.
Condition的强大之处在于它可以为多个线程间建立不同的Condition
3.1用ReentrantLock和Condition实现1-9输出(要求如上)
代码如下:
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by yang on 16-7-10.
*/
public class APP{
static class NumberWrapper{
public int value=1;
}
public static void main(String[] args) {
//初始化可重入锁
final Lock lock=new ReentrantLock();
//第一条件当屏幕上输出到3
final Condition reachThreeCondition=lock.newCondition();
//第二个条件当屏幕上输出到6
final Condition reachSixCondition=lock.newCondition();
//NumberWrapper只是封装了一个数字,一边可以将数字对象共享,并可以设置程final
final NumberWrapper num=new NumberWrapper();
//初始化A线程
Thread threadA=new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try{
System.out.print("ThreadA start write:");
//A线程先输出3个数
while(num.value<=3){
System.out.print(num.value);
num.value++;
}
System.out.println();
reachThreeCondition.signal();
}finally {
lock.unlock();
}
lock.lock();
try{
//等待输出条件6的条件
reachSixCondition.await(); //会释放锁.
System.out.print("ThreadA start write:");
//输出剩余数字
while(num.value<=9){
System.out.print(num.value);
num.value++;
}
System.out.println();
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
Thread threadB=new Thread(new Runnable() {
@Override
public void run() {
try{
lock.lock();
while(num.value<=3){
reachThreeCondition.await();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
try{
lock.lock();
System.out.print("threadB start write:");
while(num.value<=6){
System.out.print(num.value);
num.value++;
}
System.out.println();
reachSixCondition.signal();
}finally {
lock.unlock();
}
}
});
threadA.start();
threadB.start();
}
}
执行过程如下:
线程A首先加锁lock(),输出123,然后通知线程B num.value已经到达3了,线程A释放锁,然后加锁,等待输出条件6的条件,释放锁,线程B获得锁,输出456,然后通知线程Anum.value已经到达6了,线程B释放锁,退出,线程A获得锁,输出789,释放锁,退出.
4.Synchronized和Lock的区别
- ReentrantLock拥有Synchronized相同的并发性和内存语义,此外还多了锁投票,定时锁等候和中断锁等候.
- 线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定
- 如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间后,中断等待,而干别的事,
4.1 ReentrantLock获取锁定与三种方式:
- Lock():如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁.
- tryLock():如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
- tryLock(long timeout,TimeUnit unit),如果获取了锁立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false.
- lockInterruptibly:如果获取了锁立即返回,如果没有获取锁,当前线程处于休眠状态,直到获得锁或者当前线程被别的线程中断.
4.2ReentranLock和Synchronized应用场景
在并发量比较小的情况下,使用synchronized.
在并发量比较高的情况下,其性能下降严重,使用ReentrantLock.