Java 进阶7 并发优化 5 并发控制板方法 20131114
前言:
Java 中多线程并发程序中存在线程安全的问题,之前学习 Java的同步机制,掌握的同步方法只有一种就是使用 synchronized关键字,解决线程之间的同步问题。同时在操作系统 C++多线程中也了解到其他的多线程同步机制:比如信号量、临界区、互斥锁等等
在这里系统的整理一下在 Java中实现线程同步的机制:内部锁,重入锁,读写锁,信号量等等。
volatile 关键字,保证读写的共享对象时在共享内存中的,而不是线程中的副本,但是不保证线程安全。
Synchronized关键字,可以锁住当前对象,或者是类的对象,同时还有代码块等等,用法之前介绍过,同时还有配合使用的 obj.wait() obj.notify()
ReentrantLock重入锁:相比synchronized可以同步
ReentrantReadWriteLock读写锁,获取读写锁到两个对象
Condition对象:通过Lock 中newCondition获取一个 Condition对象实例。其中的await() signal()类似 wait()和notify() 方法。
Semaphore信号量机制:方法有acquire和 release
ThreadLocal机制,使用空间换时间的理念,为每一个线程存储一个副本。
1.Java 内存模型和 volatile
Java 中每一个线程都会有自己的内存工作区,其中存放着被所有线程共享的主内存变量的值得拷贝。当线程执行的时候,他在自己的工作内存中操作变量。为了取得一个共享变量,一个线程通常是先获取锁定并且清除其他工作内存区,保证共享变量从所有的线程的内存共享区正确的装入到线程的内存工作区,当线程解锁的时候保证工作内存区中变量的值写回到共享内存区。
线程中可以执行的操作 use assign load store lock unlock.而主内存可以执行的操作有 read write lock unlock. 每一个操作都是原子的。
Java 进阶7 并发优化 5 并发控制板方法 20131114
前言:
Java 中多线程并发程序中存在线程安全的问题,之前学习 Java的同步机制,掌握的同步方法只有一种就是使用 synchronized关键字,解决线程之间的同步问题。同时在操作系统 C++多线程中也了解到其他的多线程同步机制:比如信号量、临界区、互斥锁等等
在这里系统的整理一下在 Java中实现线程同步的机制:内部锁,重入锁,读写锁,信号量等等。
volatile 关键字,保证读写的共享对象时在共享内存中的,而不是线程中的副本,但是不保证线程安全。
Synchronized关键字,可以锁住当前对象,或者是类的对象,同时还有代码块等等,用法之前介绍过,同时还有配合使用的 obj.wait() obj.notify()
ReentrantLock重入锁:相比synchronized可以同步
ReentrantReadWriteLock读写锁,获取读写锁到两个对象
Condition对象:通过Lock 中newCondition获取一个 Condition对象实例。其中的await() signal()类似 wait()和notify() 方法。
Semaphore信号量机制:方法有acquire和 release
ThreadLocal机制,使用空间换时间的理念,为每一个线程存储一个副本。
1.Java 内存模型和 volatile
Java 中每一个线程都会有自己的内存工作区,其中存放着被所有线程共享的主内存变量的值得拷贝。当线程执行的时候,他在自己的工作内存中操作变量。为了取得一个共享变量,一个线程通常是先获取锁定并且清除其他工作内存区,保证共享变量从所有的线程的内存共享区正确的装入到线程的内存工作区,当线程解锁的时候保证工作内存区中变量的值写回到共享内存区。
线程中可以执行的操作 use assign load store lock unlock.而主内存可以执行的操作有 read write lock unlock. 每一个操作都是原子的。
当一个线程使用一个变量的时候,不论程序是否争取的使用线程同步操作,他获得值一定是由它本身或者其他线程存储到变量的值。
每一个线程都有自己的工作内存区,因此当一个线程改变自己的内存中的数据的时候,对于其他线程是不可见的,因此可以使用关键字 volatile关键字强制多线程读写主内存中的数据,从而使得 volatile变量在多线程中是可见的。
声明变量时 volatile的时候,可以保证以下几点:
其他线程对于变量的修改可以及时反映在当前线程中;
确保当前线程对 volatile变量的修改,能够及时的写回主内存中,并且被其他的线程可见;
使用volatile可以确保编译的时候保证有序性。
但是volatile并不保证数据的线程安全
2. 同步关键字 synchronized
最常用的同步关键字 synchronized,相比其他的同步机制synchronized关键字更加简洁明了,便于代码的维护。
1)Synchronized 可以锁定一个方法,获得的是当前对象的锁
public synchronized void func(){}
2)Synchronized 同步代码块,同步更为精准,这样的话,缩小了同步的范围,提高了性能。
3) 同步静态方法,获得的是该类的对象上面,执行方法之前首先要获得当前类的对象的锁。同时为了实现多线程之间的交互,还应该使用 wait()/ notify() notifyAll()
synchronized(obj){
while(condition){
obj.wait();// 这样因为不满足继续执行的条件,所以 wait会释放当前对象的锁,供其他线程使用该对象。当等待的一个线程受到 notify的时候,就会唤醒被阻塞的线程,当有多个线程被同一个对象阻塞的时候,只会唤醒一个线程。
}
}
3.ReentrantLock 重入锁
ReentrantLock是重入锁,他比 synchronized内部锁拥有更加强大的功能,他可以中断,可以定时,在高并发的情况下,比 synchronized有着明显的优势。JDK6中差别不是太明显。同时 ReentrantLock提供了公平和不公平的两种锁机制。公平锁保证等待锁的线程
是公平的,不存在插队的情况,总是按照队列的方式先进先出;
但还不公平锁是可以插队的,在性能上来说,非公平锁的兴根更佳。
构造函数 public ReentrantLock(Boolean fair);决定重入锁是否是公平的。并且需要记得在使用完成之后一定要释放锁,一般是放在 finally中释放的。
ReentrantLock 提供的重要的方法:
lock() :获得锁,如果被占用,则等待;
lockInterruptibly(): 获得锁,但优先响应中断
tryLock() 尝试获得锁,如果成功返回 true,反之,返回false,该方法是不会等待的,立即返回
tryLock(long time, TimeUnit unit);
unlock(): 释放锁
4.ReadWriteLock 读写,有效的减少所锁的竞争机制,提升系统的性能。读写锁允许多个线程同时读,但是考虑到数据之间的完整性,写写操作和读写操作需要相互等待和持有锁。如果在系统中读的次数远远大于写的次数的话,则读写多会发挥很大的优势。
ReentrantReadWriteLock
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private static int value = 10;
public int handleRead() throws InterruptedException{
try{
readLock.lock();
Thread. sleep(2000);
return value ;
}finally{
readLock.unlock();
}
}
public void handleWrite(int v) throws InterruptedException{
try{
writeLock.lock();
Thread. sleep(2000);
value = v;
}finally{
writeLock.unlock();
}
}
JDK1.5 之后提供的读写锁分离,对于读操作较多的并发系统中,使用读写锁的机制会提高程序的性能。
5.Condition 对象
线程之间协调工作使用的是 Condition对象。Condition是于锁相关的,通过 Lock接口的Condition newCondition()方法可以生成一个和锁绑定的 Condition实例。Condition 对象和Lock的关系就是 Object.wait()和Object.notify() 的关系一样,它们可以配合使用完成多线程的协作任务。
Condition提供的函数有:
void await() ;是当前线程等待,同时释放 lock在其他的线程中使用signal() 或者是 signalAll(),才可以唤醒当前线程;
void awaitUninterruptibly(); 等待过程不被中断 ,功能是和await 相同
void signal() 唤醒一个的等待的线程,相对于 signalAll()方法,会唤醒所有等待的线程。
6.Semaphore 信号量
信号量为多线程写作提供了强大的控制方法,信号量是对锁的扩展,无论是内部锁( synchronized)还是重入锁(ReentrantLock),一次只允许一个线程访问资源,但是信号量确可以指定多个线程同时访问某一个资源。
public Semaphore(int permits);
public Semaphore(int permits, boolean fair);
在构造信号量对象的时候,必须指定信号量的准入线程数目,其主要的方法有:
public void acquire();
public void acquireUninterruptibly();
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void release();
一个对象池,其中对象的数量是 100,当他同时有超过100个对象的请求,资源池就会出现资源短缺,未获得资源的线程就必须等待,当某一个线程使用对象完毕的时候,就会将对象返回到资源池,此时会激活一个等待的线程。
7.ThreadLocal 线程局部变量
ThreadLocal 是一种多线程间并发访问变量的解决方案,和 synchronized等锁方式是不同的,他完全不提供锁机制,而是使用的是空间换时间的手段,为每一线程提供变量的独立副本,来保证线程的安全。但是这种方式不是具有绝对的优势,在并发量不高的情况下,使用锁机制会更好,但是在高并发的情况下, ThreadLocal可以减少锁的竞争。
ThreadLocal 接口:
public void set(T val); 将次线程局部变量的当前线程副本设置成指定的值
public T get(); 返回次线程局部变量在当前线程中的副本值
public void remove(); 移除此线程局部变量当前线程值
public class ThreadLocalExample implements Runnable {
public static final ThreadLocal<Date> localvar = new ThreadLocal<Date>();
private long time ;
public ThreadLocalExample( long time){
this.time = time;
}
@Override
public void run() {
// TODO Auto-generated method stub
Date d = new Date(time );
for(int i = 0; i< 100; i++){
localvar.set(d);
if(localvar .get().getTime() != time){
System. out.println("id= " + time + " localvar = " + localvar.get().getTime() );
}
}
}
}
并发的时候,可以保证多个线程间的 localvar是相互独立的,虽然没有同步操作,但是多个线程的数据是不会相互影响的。因此永远不会出现当前线程持有的时间和成员变量 time不一致。同时不同对象上的 Date对象副本并不是由ThreadLocal创建的,而且是必须在线程内创建,并保证不同线程间实例均不相同。如果多个线程使用的是同一个 Date对象实例,及时方法ThreadLocal中保护起来也是没有用的。
YangTengfei
2013.11.23
当一个线程使用一个变量的时候,不论程序是否争取的使用线程同步操作,他获得值一定是由它本身或者其他线程存储到变量的值。
每一个线程都有自己的工作内存区,因此当一个线程改变自己的内存中的数据的时候,对于其他线程是不可见的,因此可以使用关键字 volatile关键字强制多线程读写主内存中的数据,从而使得 volatile变量在多线程中是可见的。
声明变量时 volatile的时候,可以保证以下几点:
其他线程对于变量的修改可以及时反映在当前线程中;
确保当前线程对 volatile变量的修改,能够及时的写回主内存中,并且被其他的线程可见;
使用volatile可以确保编译的时候保证有序性。
但是volatile并不保证数据的线程安全
2. 同步关键字 synchronized
最常用的同步关键字 synchronized,相比其他的同步机制synchronized关键字更加简洁明了,便于代码的维护。
1)Synchronized 可以锁定一个方法,获得的是当前对象的锁
public synchronized void func(){}
2)Synchronized 同步代码块,同步更为精准,这样的话,缩小了同步的范围,提高了性能。
3) 同步静态方法,获得的是该类的对象上面,执行方法之前首先要获得当前类的对象的锁。同时为了实现多线程之间的交互,还应该使用 wait()/ notify() notifyAll()
synchronized(obj){
while(condition){
obj.wait();// 这样因为不满足继续执行的条件,所以 wait会释放当前对象的锁,供其他线程使用该对象。当等待的一个线程受到 notify的时候,就会唤醒被阻塞的线程,当有多个线程被同一个对象阻塞的时候,只会唤醒一个线程。
}
}
3.ReentrantLock 重入锁
ReentrantLock是重入锁,他比 synchronized内部锁拥有更加强大的功能,他可以中断,可以定时,在高并发的情况下,比 synchronized有着明显的优势。JDK6中差别不是太明显。同时 ReentrantLock提供了公平和不公平的两种锁机制。公平锁保证等待锁的线程
是公平的,不存在插队的情况,总是按照队列的方式先进先出;
但还不公平锁是可以插队的,在性能上来说,非公平锁的兴根更佳。
构造函数 public ReentrantLock(Boolean fair);决定重入锁是否是公平的。并且需要记得在使用完成之后一定要释放锁,一般是放在 finally中释放的。
ReentrantLock 提供的重要的方法:
lock() :获得锁,如果被占用,则等待;
lockInterruptibly(): 获得锁,但优先响应中断
tryLock() 尝试获得锁,如果成功返回 true,反之,返回false,该方法是不会等待的,立即返回
tryLock(long time, TimeUnit unit);
unlock(): 释放锁
4.ReadWriteLock 读写,有效的减少所锁的竞争机制,提升系统的性能。读写锁允许多个线程同时读,但是考虑到数据之间的完整性,写写操作和读写操作需要相互等待和持有锁。如果在系统中读的次数远远大于写的次数的话,则读写多会发挥很大的优势。
ReentrantReadWriteLock
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private static int value = 10;
public int handleRead() throws InterruptedException{
try{
readLock.lock();
Thread. sleep(2000);
return value ;
}finally{
readLock.unlock();
}
}
public void handleWrite(int v) throws InterruptedException{
try{
writeLock.lock();
Thread. sleep(2000);
value = v;
}finally{
writeLock.unlock();
}
}
JDK1.5 之后提供的读写锁分离,对于读操作较多的并发系统中,使用读写锁的机制会提高程序的性能。
5.Condition 对象
线程之间协调工作使用的是 Condition对象。Condition是于锁相关的,通过 Lock接口的Condition newCondition()方法可以生成一个和锁绑定的 Condition实例。Condition 对象和Lock的关系就是 Object.wait()和Object.notify() 的关系一样,它们可以配合使用完成多线程的协作任务。
Condition提供的函数有:
void await() ;是当前线程等待,同时释放 lock在其他的线程中使用signal() 或者是 signalAll(),才可以唤醒当前线程;
void awaitUninterruptibly(); 等待过程不被中断 ,功能是和await 相同
void signal() 唤醒一个的等待的线程,相对于 signalAll()方法,会唤醒所有等待的线程。
6.Semaphore 信号量
信号量为多线程写作提供了强大的控制方法,信号量是对锁的扩展,无论是内部锁( synchronized)还是重入锁(ReentrantLock),一次只允许一个线程访问资源,但是信号量确可以指定多个线程同时访问某一个资源。
public Semaphore(int permits);
public Semaphore(int permits, boolean fair);
在构造信号量对象的时候,必须指定信号量的准入线程数目,其主要的方法有:
public void acquire();
public void acquireUninterruptibly();
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void release();
一个对象池,其中对象的数量是 100,当他同时有超过100个对象的请求,资源池就会出现资源短缺,未获得资源的线程就必须等待,当某一个线程使用对象完毕的时候,就会将对象返回到资源池,此时会激活一个等待的线程。
7.ThreadLocal 线程局部变量
ThreadLocal 是一种多线程间并发访问变量的解决方案,和 synchronized等锁方式是不同的,他完全不提供锁机制,而是使用的是空间换时间的手段,为每一线程提供变量的独立副本,来保证线程的安全。但是这种方式不是具有绝对的优势,在并发量不高的情况下,使用锁机制会更好,但是在高并发的情况下, ThreadLocal可以减少锁的竞争。
ThreadLocal 接口:
public void set(T val); 将次线程局部变量的当前线程副本设置成指定的值
public T get(); 返回次线程局部变量在当前线程中的副本值
public void remove(); 移除此线程局部变量当前线程值
public class ThreadLocalExample implements Runnable {
public static final ThreadLocal<Date> localvar = new ThreadLocal<Date>();
private long time ;
public ThreadLocalExample( long time){
this.time = time;
}
@Override
public void run() {
// TODO Auto-generated method stub
Date d = new Date(time );
for(int i = 0; i< 100; i++){
localvar.set(d);
if(localvar .get().getTime() != time){
System. out.println("id= " + time + " localvar = " + localvar.get().getTime() );
}
}
}
}
并发的时候,可以保证多个线程间的 localvar是相互独立的,虽然没有同步操作,但是多个线程的数据是不会相互影响的。因此永远不会出现当前线程持有的时间和成员变量 time不一致。同时不同对象上的 Date对象副本并不是由ThreadLocal创建的,而且是必须在线程内创建,并保证不同线程间实例均不相同。如果多个线程使用的是同一个 Date对象实例,及时方法ThreadLocal中保护起来也是没有用的。
YangTengfei
2013.11.23