并发的控制方法

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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值