文章目录
Java内存模型
在Java中,每一个线程都有自己的独立的一块工作内存区,其中存放着被所有线程共享的主存中的变量的拷贝值。当线程执行时,它在自己的工作内存中操作这些变量。为了让线程中私有的值与共享相同,线程需要进行一系列的操作,并且每个操作均为原子的。如下图:
由于每个线程都有自己的工作内存区,因此当一个线程改变自己内存中的数据时,对其他线程来说,可能时不可见的。为此,可以使用volatile关键字迫使所有线程均读写主存中的对应变量,从而使得volatile变量在多线程间可见。
声明为volatile的变量可以做以下保证:
- 其他线程对变量的修改,可以即时反应在当前线程中;
- 确保当前线程对volatile变量的修改,能即时写回共享主内存中,并被其他线程所见;
- 使用volatile声明的变量,编译器会保证其有序性。
同步关键字synchronized
同步关键字synchronized是Java语言中最为常用的同步方法之一。synchronized最常用的方法如下:
public synchronized void method(){
}
/**
* 等于
**/
public void method(){
synchronized(this){
}
}
虽然synchronized可以保证对象或者代码段的线程安全,但是仅使用synchronized还不足以控制复杂的逻辑的线程交互,为了实现多线程间的交互,还需要使用Object对象的wait()和notify()方法。函数wait()可以让线程等待当前对象上的通知(notify()被调用),在wait()过程中,线程会释放对象锁。一般用法如下:
synchronized(obj){
while(<等待条件>){
obj.wait();
}
}
实际案例,阻塞队列:
public class BlockQueue {
private List list = new ArrayList();
public synchronized Object pop() throws InterruptedException {
while (list.size() == 0) {
this.wait();
}
if (list.size() > 0) {
return list.remove(0);
} else {
return null;
}
}
public synchronized void put(Object o) {
list.add(o);
this.notify();
}
}
ReentrantLock重入锁
ReentrantLock称为ReentrantLock,它比synchronized拥有更加强大的功能,它可中断、可定时。
ReentrantLock还提供了公平和非公平两种锁。公平锁可以保证在锁的等待队列中的各个线程是公平的,因此不存在插队情况,对锁的获取总是先进先出,而非公平的ReentrantLock不做这个保证,申请锁的线程可能插队,后申请锁的线程可能先拿到锁。公平锁的实现代价比非公平锁大,因此在性能上分析,非公平锁的性能要好的多。
使用ReentrantLock时,还需注意在最后一定要释放锁。
ReentrantLock提供了以下重要的方法:
- lock():获得锁,如果锁已经被占用,则等待。
- lockInterruptibly():获得锁,但优先响应中断。
- tryLock():尝试获得锁,若成功返回true;否则,返回false。该方法不等待,立即返回。
- tryLock(long time, TimeUnit unit):给定时间内尝试获得锁。
- unlock():释放锁
ReadWriteLock读写锁
ReadWriteLock是JDK5中提供的读写分离锁,可以有效的帮助减少锁的竞争,从而提高性能。用锁分离的机制来提升性能非常容易理解,比如线程A1,A2,A3进行写操作,B1,B2,B3进行读操作,如果使用重入锁或者内部锁,则理论上说所有读之间,读写之间,写和写之间都是串性操作。但读写锁就允许多个线程同时读,使得B1,B2,B3之间真正并行,但写写操作和读写操作之间还是需要相互等待和持有锁。如果在系统中,读操作次数远远大于写操作,则读写锁可以发挥最大的功效,提升系统的性能。
Condition对象
线程间的协调工作光有锁是不够的,线程之间需要交互,Condition对象就可以用于协调多线程间的复杂协作。通过Lock接口的Condition newCondition()方法可以产生一个与锁绑定的Condition实例。Condition对象和锁的关系,就如同Object.wait()、Object.notify()两个函数以及synchronized关键字一样,他们都可以配合使用用以完成对多线程协作的控制。
Condition 接口提供的方法基本如下:
- await()方法会使当前线程等待,同时释放锁,其他线程中使用signal()或者signalAll()方法时,线程会重新获得锁,并继续执行。或者当前线程被中断时,也能跳出等待。
- awaitUninterruptibly()方法与await()用法基本相同,但是它并不会在等待过程中响应中断。
- singal()方法用于唤醒一个在等待中的线程,相对singalAll()方法会唤醒所有等待中的线程。
以ArrayBlockingQueue示例:
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
// put() 方法的实现
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
// take()方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
Semaphore信号量
Semaphore信号量为多线程提供了更为强大的控制方法。广义上说,信号量是对锁的扩展。无论是内部锁synchnorized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而心好累却可以指定多个线程同时访问某个资源。信号量提供了以下构造函数:
/**
* 指定信号量准入数,即同时能申请多少个许可
*/
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
// 尝试获取一个准入的许可,若无法获得则线程等待
public void acquire()
public void acquireUninterruptibly
// 尝试获得一个许可,不等待,成功返回true,失败返回false
public boolean tryAcquire()
public boolean tryAcquire(long timeout, TimeUnit unit)
// 用于在线程访问资源结束后释放一个许可
public void release()
示例代码:
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);//创建Semaphore信号量,初始化许可大小为3
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e2) {
e2.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
try {
sp.acquire();//请求获得许可,如果有可获得的许可则继续往下执行,许可数减1。否则进入阻塞状态
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"进入,当前已有" + (3 - sp.availablePermits()) + "个并发");
try {
Thread.sleep((long) (Math.random() * 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"即将离开");
sp.release();//释放许可,许可数加1
//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println("线程" + Thread.currentThread().getName() +
"已离开,当前已有" + (3 - sp.availablePermits()) + "个并发");
}
});
}
}
}
ThreadLocal线程局部变量
ThreadLocal是多线程间并发访问变量的解决方案。与synchronized等加锁方式完全不同,ThreadLocal不提供锁,而是使用以空间换时间的手段,为每个线程提供变量的独立副本,以保证线程的安全。提供的接口很简单:
// 将此线程局部变量的当前线程副本中的值设置为指定值
public void set(T value)
// 返回此线程局部变量的当前线程副本中的值
public T get()
// 移除此线程局部变量当前线程的值
public void remove()