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