Java 并发编程(一)

谁说我不能喝 提交于 2020-03-02 13:13:08

#Java 并发编程(一)

##同步容器 1.Vector,Hashtable。 实现线程安全的方式是:将它们的状态封装起来,并对每个共有方法进行同步,使得每次只有一个线程能访问容器的状态。使用了Java监视器模式。

2.Vector代码分析 //根据下标获取数据,都是使用synchronized实现同步 public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }

//添加数据
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

//扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

3.Hashtable代码分析

//使用synchronized实现同步
public synchronized V get(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return (V)e.value;
        }
    }
    return null;
}
//添加元素
public synchronized V put(K key, V value) {
    
    
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    //key值存在于数组中
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }
	//key值不存在则进行添加
    addEntry(hash, key, value, index);
    return null;
}	

 private void addEntry(int hash, K key, V value, int index) {
    modCount++;

    Entry<?,?> tab[] = table;
    //如果超过了阈值
    if (count >= threshold) {
        rehash();
        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }
		//添加新元素
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

##并发容器 1.JDK5.0提供了多种并发容器来改进同步容器的性能。同步容器将所有对容器状态的访问都串行化,以实现他们的线程安全性。代价是严重降低并发性。并发容器是针对多个线程并发设计的。

ConcurrentHashMap用于代替基于散列的同步Map。

CopyOnWriteArrayList用于在遍历操作为主要操作的情况下代替同步的List。

2.Java8 ConcurrentHashMap 代码分析

public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
		//key,value不能为空
    if (key == null || value == null) throw new NullPointerException();
    //使Key值分散更均匀
    int hash = spread(key.hashCode());
    //记录Node数量
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //延迟初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //此bucket为空,不用锁,使用cas添加
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                  
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

3.CopyOnWriteArrayList适合用在“读多,写少”的“并发”应用中,换句话说,它适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作是加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。

Java8 CopyOnWriteArrayList 代码分析

	//get 读操作
	private E get(Object[] a, int index) {
    return (E) a[index];
	}

	//add 写操作
	
	public boolean add(E e) {
		//可重入锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    		//获得数组元素对象
        Object[] elements = getArray();
        int len = elements.length;
        //数组的copy
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //添加元素
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
    		//释放锁
        lock.unlock();
    }
}

##阻塞队列和生产者-消费者模式

1.阻塞队列提供了可供阻塞的put和take方法,以及支持定时的offer和poll方法,如果队列已经满了,那么put 方法将一直阻塞直到有空间可用;如果队列为空,那么take 方法将会阻塞直到有元素可用。队列可以是有界的也可以是无界的,无界队列永远不会被充满,因此无界队列上的put方法也永远不会阻塞。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。BlockingQueue简化了生产者-消费者设计模式的实现过程,他支持任意数量的生产者和消费者。一张最常见的生产者-消费者设计模式就是线程池和工作队列的组合,在Executor任务执行框架中就体现了这种模式。

2.生产者-消费者模式代码示例

	public class Client {
public static class People{
	
	BlockingQueue<Object> peBlockingQueue=new ArrayBlockingQueue<Object>(3);
			
	public void putPeople(Object object){
		try {
			//如果此时队列已经满了将阻塞。
			peBlockingQueue.put(object);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("放入人员");
	}
	
	public Object takePeople(){
		Object object=null;
		try {
			//如果此时队列为空将阻塞。
			object=peBlockingQueue.take();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("取出人员");
		return object;
	}
}

static class putThread extends Thread{
	
	private  People people; 
	private Object object=new Object();
	public putThread(People people){
		this.people=people;
	}
	@Override
    public void run() {  
    		people.putPeople(object);  
    }  
}
static class takeThread extends Thread{
	private  People people; 
	public takeThread(People people){
		this.people=people;
	}
	@Override
    public void run() {  
    		people.takePeople(); 
    }  
}
public static void main(String args[]){
	People people=new People();
	for (int i = 0; i < 5; i++) {
		new Thread(new putThread(people)).start();
	}
	for (int i = 0; i < 5; i++) {
		new Thread(new takeThread(people)).start();
	}
}
}

输出结果

放入人员

放入人员

放入人员

取出人员

放入人员

放入人员

取出人员

取出人员

取出人员

取出人员

3.Java 里的阻塞队列

ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。

LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。默认大小为Integer.MAX_VALUE。

PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。

DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。创建公平的队列:

BlockingQueue<Object> peBlockingQueue=new ArrayBlockingQueue<Object>(3,true);

ArrayBlockingQueue代码分析:

//可重入锁	
final ReentrantLock lock;
//等待条件
private final Condition notEmpty;
//等待条件
private final Condition notFull;
//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();
}


public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    //获取可中断锁
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

我们可以将DelayQueue运用在以下应用场景:

缓存系统的设计。可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。

定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如TimerQueue就是使用DelayQueue实现的。

关闭空闲连接。

##阻塞方法与中断方法

##同步工具类

信号量

Semaphore 中管理着一组虚拟的许可,许可的初始数量可通过构造函数来指定,在执行操作时可以首先获得许可,并在使用之后释放许可,如果没有许可,那么acquire将阻塞直到有许可。

Semaphore 可以用于实现资源池,例如数据库连接池。我们可以构造一个固定长度的资源池,当池为空,请求资源将会阻塞直到有资源。也可以使用Semaphore将任何一种容器变成有界阻塞容器。

	public class TestSemaphore {
public static class SemaphoreDemo {
	private ReentrantLock lock = new ReentrantLock();
	private Semaphore semaphore;
	private final ArrayList<Object> resourceList = new ArrayList<Object>();

	public SemaphoreDemo(ArrayList<Object> list) {
		this.resourceList.addAll(list);
		semaphore = new Semaphore(3);
	}
	// 获取资源
	public Object acquire() throws InterruptedException {
		semaphore.acquire();
		lock.lock();
		try {
			return resourceList.get(resourceList.size() - 1);
		} catch (Exception InterruptedException) {
		} finally {
			lock.unlock();
		}
		return null;
	}
	// 释放资源
	public void release(Object resource) {
		lock.lock();
		try {
			resourceList.add(resource);
		} finally {
			lock.unlock();
		}
		semaphore.release();
	}
}
public static void main(String[] args) {
	ArrayList<Object> resourceList = new ArrayList();
	resourceList.add("Resource1");
	resourceList.add("Resource2");

	final SemaphoreDemo semaphoreDemo = new SemaphoreDemo(resourceList);
	Runnable task = new Runnable() {
		public void run() {
			Object reObject = null;
			try {
				// 获取资源
				reObject = semaphoreDemo.acquire();
				System.out.println(Thread.currentThread().getName() + ":" + reObject);
				//休眠
				Thread.sleep(1500);
				System.out.println(Thread.currentThread().getName() + "!" + reObject);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				semaphoreDemo.release(reObject);
			}
		}
	};
	ExecutorService executorService = Executors.newCachedThreadPool();
	for (int i = 0; i < 9; i++) {
		executorService.submit(task);
    }
	executorService.shutdown();
}

}

代码输出

pool-1-thread-1:Resource2

pool-1-thread-3:Resource2

pool-1-thread-2:Resource2

pool-1-thread-1!Resource2

pool-1-thread-3!Resource2

pool-1-thread-2!Resource2

pool-1-thread-4:Resource2

pool-1-thread-5:Resource2

pool-1-thread-5!Resource2

pool-1-thread-4!Resource2

闭锁

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁可以用来确保某些活动直到其他活动都完成以后才继续执行。

CountDownLatch是一种灵活的闭锁实现,可以使一个或多个线程等待一组事件发生。CountDownLatch有一个正数计数器,countDown方法对计数器做减操作,await方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时。

CountDownLatch源码

//初始化时给定计数器大小
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
//递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
public void countDown() {
    sync.releaseShared(1);
}
//使当前线程阻塞直到计数器为零
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

CountDownLatch使用示例

public class TestCountDownLatch {

public static CountDownLatch latch=null;
public static void main(String args[]) throws InterruptedException{
	try {
		latch= new CountDownLatch(5);
		for (int i = 0; i < 5; i++) {
			new TestThread().start();
		}
		latch.await();
		System.out.println("5个线程已经完成");
	} catch (Exception e) {
		
	}finally {
		
	}
}
static class TestThread extends Thread{
	@Override
	public void run() {
		try {
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
			latch.countDown();
		}catch (InterruptedException e) {
			e.printStackTrace();
	    }
	}
	
}
}

输出结果

Thread-1 sleep 1000ms.

Thread-4 sleep 1000ms.

Thread-3 sleep 1000ms.

Thread-0 sleep 1000ms.

Thread-2 sleep 1000ms.

5个线程已经完成

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!