Java高并发程序设计学习笔记(五):JDK并发包(各种同步控制工具的使用、并发容器及典型源码分析(Hashmap等))...


1. 各种同步控制工具的使用

1.1. ReentrantLock

在synchronize的基础上新加了功能,如果是特别简单的场景,两者是没有太大的区别的。

1.1.1.可重入

单线程可以重复进入,但要重复退出
对于同一个线程,自己是可以重复进入的,否则会把自己卡死。
结论:
重入锁是可重复获得资源的锁,已经获得锁的线程可以对当前的资源重入加锁而不会引起阻塞;不可重入锁是不可重复获得资源的锁,当已经获得锁的线程对当前资源再次加锁时,会把自己阻塞

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁,下面是一个用synchronized实现的例子:

public class ReentrantTest implements Runnable {

    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }

    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }

    public void run() {
        get();
    }

    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        for(;;){
            new Thread(rt).start();
        }
    }
}

整个过程没有发生死锁的情况,截取一部分输出结果如下:

Thread-8492
Thread-8492
Thread-8494
Thread-8494
Thread-8495
Thread-8495
Thread-8493
Thread-8493

set()和get()同时输出了线程名称,表明即使递归使用synchronized也没有发生死锁,证明其是可重入的。

以上原文:https://blog.csdn.net/rickiyeat/article/details/78314451



import java.util.concurrent.locks.ReentrantLock;

public class Lock implements Runnable{
	public static ReentrantLock lock = new ReentrantLock();
	public static int i = 0;
	public void run() {
		for (int j = 0; j < 100000; j++) {
			lock.lock();
			lock.lock();
			try {
				i++;
			} finally {
				//不论如何都进行unlock
				//加两次锁必须解锁两次。
				lock.unlock();
				lock.unlock();
			}
		
		}
	}
	//synchronize只要在大括号外面,出了大括号,虚拟机会自动释放锁,可是lock是通过unlock来控制什么时候释放锁

	public static void main(String[] args) throws InterruptedException {
		Lock tl = new Lock();
		//同时开两个一样的线程
		Thread t1 = new Thread(tl);
		Thread t2 = new Thread(tl);
		t1.start();t2.start();
		t1.join();t2.join();
		System.out.println(i);
	}
}

在这里插入图片描述

1.1.2. 可中断 lockInterruptibly()

在发生死锁或者其他的导致长期等待的情况,希望锁停下来的功能,synchronize没有这个功能,ReentrantLock提供了可中断。

import java.util.concurrent.locks.ReentrantLock;

public class Interruptible  implements Runnable{
	public static ReentrantLock lock1 = new ReentrantLock();
	public static ReentrantLock lock2 = new ReentrantLock();
	int lock;
	public Interruptible(int lock){
		this.lock = lock;
	}

	public void run() {
		try{
			//lock=1的时候lock1上锁,lock=2的时候lock2上锁,刚才的lock1上了锁
			//之后还要给lock2上锁,lock2上了锁之后还要给lock1上锁,
			// 可是lock2在lock=2的时候被locl=2拿到了,lock1在lock=1的时候
			//被locl=1拿到了所以形成了死锁
			if (lock == 1){
				lock1.lockInterruptibly();
				try{
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				lock2.lockInterruptibly();
			}else{
				lock2.lockInterruptibly();
				try{
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				lock1.lockInterruptibly();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
		//如果lock1还拿着这把锁的话,就解掉。
			if (lock1.isHeldByCurrentThread())
				lock1.unlock();
			if (lock2.isHeldByCurrentThread())
				lock2.unlock();
			System.out.println(Thread.currentThread().getId()+":线程退出");
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Interruptible interruptible1 = new Interruptible(1);
		Interruptible interruptible2 = new Interruptible(2);
		Thread t1 = new Thread(interruptible1);
		Thread t2 = new Thread(interruptible2);
		t1.start();t2.start();
		Thread.sleep(1000);
		DeadlockChecker.check();
	}
}

DeadlockChecker.check();如果将这句话注释,将会进行无限期的死锁。

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class DeadLockChecker {
	private final static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
	final  static  Runnable deadLockCheck = new Runnable() {
		public void run() {
			while (true) {
				long [] deadLockedThreadIds = mbean.findDeadlockedThreads();
				if (deadLockedThreadIds != null) {
					ThreadInfo[] threadInfos = mbean.getThreadInfo(deadLockedThreadIds);
					for (Thread t : Thread.getAllStackTraces().keySet()) {
						for (int i = 0; i<threadInfos.length ; i++) {
							if (t.getId() == threadInfos[i].getThreadId()) {
								t.interrupt();
							}
						}
					}
				}
				try{
					Thread.sleep(5000);
				}catch (InterruptedException e){
					
				}
			}
		}
	};
	
	public static void check(){
		Thread t = new Thread(deadLockCheck);
		t.setDaemon(true);
		t.start();
	}
}

1.1.3. 可限时

超时不能获得锁,就返回false,不会永久等待构成死锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TimeLock implements  Runnable{
	public static ReentrantLock lock = new ReentrantLock();

	public void run() {
		try{
			//如果线程在5秒之内没有拿到锁就走else里面的内容
			if (lock.tryLock(5, TimeUnit.SECONDS)) {
				Thread.sleep(6000);	
			}else{
				System.out.println("get lock failed");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			if (lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
	}

	public static void main(String[] args) {
		TimeLock tl = new TimeLock();
		Thread t1 = new Thread(tl);
		Thread t2 = new Thread(t1);
		t1.start();
		t2.start();
		
	}
}

1.1.4. 公平锁

什么是公平锁?
先来先得,避免产生饥饿现象,但是性能差很多。所以不是特殊情况下,不要使用公平锁。
public ReentrantLock(boolean fair)
public static ReentrantLock fairLock = new ReentrantLock(true);

1.2. Condition

1.2.1. 概述

类似于 Object.wait()和Object.notify() 与ReentrantLock结合使用

1.2.2. 主要接口

void await() throws InterruptedException;//等待
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();//通知继续往下走
void signalAll();

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReenTerLockCondition implements Runnable {
	public static ReentrantLock lock = new ReentrantLock();
	public static Condition condition = lock.newCondition();

	public void run() {
		try{
			lock.lock();
			condition.await();
			System.out.println("Thread is going on ");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ReenTerLockCondition tl = new ReenTerLockCondition();
		Thread t1 = new Thread(tl);
		t1.start();
		Thread.sleep(2000);
		lock.lock();
		condition.signal();
		lock.unlock();
	}
}
//必须在lock和unlock执行完了之后才会到上面将t1的锁解开。
//类似于之前的Synchronize代码块。

1.2.3. API详解

await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线
程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。

awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。不会被中断。
singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obej ct.notify()方法很类似。

1.3. Semaphore

信号量
互斥的、排他的
可以有多个线程去共享这个临界区,广义上的锁,比如每个信号量指定十个许可,每个许可分配若干个线程,当然每个线程也可以持有多个许可,如果有多余的许可就可以进入,如果没有许可,后面的线程就必须等待,不能进入。
信号量允许多个线程进入临界区,信号量的许可等于一的时候就相当于一把锁。

1.3.1. 概述

共享锁 运行多个线程同时临界区

1.3.2. 主要接口

public void acquire()//获得信号量

 public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    //也可以拿到多个许可
 public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }

public void acquireUninterruptibly()//不支持中断的获得信号量
public boolean tryAcquire()//不会等待,只是试一试,拿不到就返回false
public boolean tryAcquire(long timeout, TimeUnit unit)//try的时间,等待的时间,和上线功能差差不多
public void release()释放。
例子:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class  SemapDemo implements Runnable {
	final Semaphore semp = new Semaphore(5);

	public void run() {
		try{
		//也可以semp.acquire(2);下面将会每两个线程输出一次
			semp.acquire();
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getId()+":done!");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			semp.release();
		}
	}

	public static void main(String[] args) {
		ExecutorService exec = Executors.newFixedThreadPool(20);
		final SemapDemo demo = new SemapDemo();
		for (int i = 0; i <20 ; i++) {
			exec.submit(demo);
		}
	}
}

在这里插入图片描述
可以看到每五个线程输出一次,每次停留两秒,同样的如果semp.acquire(2);每个线程拿到两个许可,一共有五个许可,release(2),所以每次只有两个线程输出。

1.4. ReadWriteLock

写会修改数据,读不会修改数据,从性能上来看,如果不管读还是写都加锁,会十分影响性能,synchronized并行度只有一,一次只允许一个线程经过。如果没有写操作,这个所有的read线程必然是无等待的。

1.4.1. 概述

ReadWriteLock是JDK5中提供的读写分离锁

1.4.2. 访问情况

读-读不互斥:读读之间不阻塞。
读-写互斥:读阻塞写,写也会阻塞读。
写-写互斥:写写阻塞。
在这里插入图片描述

1.4.3. 主要接口

private static ReentrantReadWriteLock readWriteLock=newReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
获得readLock,然后通过readLock.lock()惊醒操作
private static Lock writeLock = readWriteLock.writeLock();
获得readLock,然后通过readLock.lock()惊醒操作

1.5. CountDownLatch

1.5.1. 概述

倒数计时器 一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程 ,等待所有检查线程全部完工后,再执行

1.5.2. 主要接口

static final CountDownLatch end = new CountDownLatch(10); //加入有十个检查项(十个线程)
end.countDown();//没通过一个就减一
end.await();//见到最后就返回,执行比如发射火箭的操作

1.5.3. 示意图

在这里插入图片描述
主线程在这里等待,等到所有的检查任务都到达临界点之后,主线程就继续执行。

例子

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchDemo implements Runnable {
	static final CountDownLatch end = new CountDownLatch(10);//开启十个线程来进行检查
	static final CountDownLatchDemo demo = new CountDownLatchDemo();
	public void run() {
		try {
			Thread.sleep(new Random().nextInt(10)*1000);
			System.out.println("check complete");
			end.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		ExecutorService exec = Executors.newFixedThreadPool(10);
		for (int i = 0; i <10 ; i++) {
			exec.submit(demo);
		}
		//等待检查
		end.await();
		//发射火箭
		System.out.println("Fire");
		exec.shutdown();
	}
}

在这里插入图片描述

1.6. CyclicBarrier

1.6.1. 概述

循环栅栏 Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程
它和CountDownLatch的区别是CountDownLatch的计数器只能有一次,而CyclicBarrier可以再重复利用。

1.6.2. 主要接口

public CyclicBarrier(int parties(几个参与者相当于上一节中的10), Runnable barrierAction(所有的线程到了之后,栅栏(系统)执行的动作))
barrierAction就是当计数器一次计数完成后,系统会执行的动作
await() //都到了之后再往下执行

1.6.3. 示意图

在这里插入图片描述

例子

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
	public static  class Soldier implements Runnable{
		private String soldier;
		private final CyclicBarrier cyclic;

		Soldier(CyclicBarrier cyclic,String soldierName) {
			this.soldier = soldierName;
			this.cyclic = cyclic;
		}

		public void run() {
			try {
				cyclic.await();
				doWork();
				cyclic.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		}

		void doWork(){
			try {
				Thread.sleep(Math.abs(new Random().nextInt()%10000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(soldier+":任务完成");
		}
	}
	public static class BarrierRun implements Runnable{
		boolean flag;
		int N;

		public BarrierRun(boolean flag, int N) {
			this.flag = flag;
			this.N = N;
		}
		public void run() {
			if (flag) {
				System.out.println("司令:[士兵"+N+"个,任务完成!]");
			}else{
				System.out.println("司令:[士兵"+N+"个,集合完毕!]");
				flag = true;
			}
		}
	}

	public static void main(String[] args) {
		final int N = 10;
		Thread[] allSoldier = new Thread[N];
		boolean flag = false;
		CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
		System.out.println("集合队伍!");
		for (int i = 0; i <N ; i++) {
			System.out.println("士兵"+i+"报道!");
			allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
			allSoldier[i].start();
//			if (i == 5) {
//				allSoldier[0].interrupt();
//			}
		}
	}
}

在这里插入图片描述

//			if (i == 5) {
//				allSoldier[0].interrupt();
//			}

将这个注释打开的话
在这里插入图片描述
第零个终端之后,将会抛出InterruptedException异常,剩下的九个线程将会抛出BrokenBarrierException异常。

1.7. LockSupport

1.7.1. 概述

比较偏底层。
提供线程阻塞原语
挂起

1.7.2. 主要接口

LockSupport.park(); //停下来、挂起,将线程挂起,除非许可是可用的。
LockSupport.unpark(t1);//将t1继续执行,使得许可是可用的

1.7.3. 与suspend()比较

LockSupport不容易引起线程冻结,suspend将来可能会被废弃。
有点类似于信号量中的许可,如果unpark发生在park之前,park并不会将线程阻塞住。
如果rewiew发生在suspend之前,suspend就不能再继续执行了,永久挂起。

1.7.4. 中断响应

wait等是能够try catch 终端异常的,但是park是没有捕获这个异常的,所以:
能够响应中断,但不抛出异常。
中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志

例子

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
	public static Object u = new Object();
	static ChangeObjectThread t1 = new ChangeObjectThread("t1");
	static ChangeObjectThread t2 = new ChangeObjectThread("t2");

	public static class ChangeObjectThread extends Thread{
		public ChangeObjectThread(String name) {
			super.setName(name);
		}

		@Override
		public void run() {
			synchronized (u){
				System.out.println("in"+getName());
				LockSupport.park();
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {
		t1.start();
		Thread.sleep(100);
		t2.start();
		LockSupport.unpark(t1);
		LockSupport.unpark(t2);
		t1.join();
		t2.join();
	}
}

在这里插入图片描述
不论unpark在park的前面还是后面都不会阻塞。

1.8. ReentrantLock 的实现

1.8.1. CAS状态

锁到底有没有被人占用,通过是否到达期望值,通过值是否改变来判断是否应该拿到锁。

1.8.2. 等待队列

如果没有拿到锁,线程应该怎么办呢,应该进入等待的队列,如果有多个线程进来,多个线程在队列中进行等待。

1.8.3. park()

在队列中的线程都进行park操作。unlock的时候,从等待的队列中挑出一个出来进行unpark操作。

源码

 /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))//期望值是0,更新成1
            //如果成功了,便能拿到锁,就能继续往下走。
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //否则就尝试去做申请。
                acquire(1);
        }
        protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
     public final void acquire(int arg) {
     //在尝试一下,万一别人释放了呢?
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //如果尝试了还是不行就把自己放到等待队列中去
      private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //放到队列的尾巴上去。
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
 //返回的node然后再去尝试请求锁
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
               //如果确实是拿不到就会在这里挂起,下面会有详细
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    //通过park进行挂起
 private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }




//unlock操作
 public void unlock() {
        sync.release(1);
 }
  */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            //将队列头部的node进行unpark操作
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

2. 并发容器及典型源码分析

2.1. 集合包装

2.1.1. HashMap

HashMap不是一个线程安全的容器,可以通过如下的方法变成线程安全的,但是只适用于并发量比较小的情况。
Collections.synchronizedMap
在这里插入图片描述

源码

private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            if (m==null)
                throw new NullPointerException();
            this.m = m;
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet = null;
        private transient Set<Map.Entry<K,V>> entrySet = null;
        private transient Collection<V> values = null;

        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            }
        }

        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            }
        }

        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            }
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return m.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }
        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

通过观察分析,是将map封装到了synchronizeMap中,并且,将get put 等操作都放在了Synchronize代码块中,下面的List和Set同理,因为是放在了synchronize代码块中所以是串行的不是并行的,只能适用于并发量比较小的场景。

2.1.2. List

synchronizedList

2.1.3. Set

synchronizedSet

2.2. ConcurrentHashMap

高性能HashMap(解决synchronize只适用于并发量小的场景)

2.2.1HashMap源码分析

HashMap内部是一个数组
拿put方法来看

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    //进入inflateTable方法
  private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
    //看到是将疏浚存到了table中
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
    //每一个table中是entry表象
    static class Entry<K,V> implements Map.Entry<K,V> {
    //每个entry里面是key value next hash
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashCode() {
            return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
        }

        public final String toString() {
            return getKey() + "=" + getValue();
        }

        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void recordAccess(HashMap<K,V> m) {
        }

        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void recordRemoval(HashMap<K,V> m) {
        }
    }

在这里插入图片描述
get(key)是通过哈希算法来判断应该映射到哪个槽位的
两个不同的key比如,key1 key2可能被映射到同一个槽位中,这里叫做哈希冲突
一种解决方法是,既然你映射到了同一个槽位中,我就把你方法同一个槽位中,可是在一个entry的数组中如何放两个entry呢??通过entry中的next指向下一个entry

事实上HashMap的内部主要实现是数组,数组中放着entry,每个entry都是链表中的一环,链表的头部,当发生大量的hash冲突的时候蜕化成一个链表。

一般情况下HashMap不会放满,因为放满之后必然产生冲突,所以一般HashMap需要预留空间

2.2.2. ConcurrentHashMap源码分析

 public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }

Segment 段
如果大量线程进来,会一起竞争HashMap的赋值操作
把大的HashMap切割成若干个小的HashMap,每个线程进来的时候,先把当前的key映射到其中的一个小HashMap中去,在小HashMap中做一个普通HashMap应该做的事情,假如大的HashMap中有十六个小的HashMap,意味着大的HashMap可以同时接受十六个线程的赋值操作,相比于之前只有一个HasnMap,性能提高了十六倍。

这里的Segment就是上面说的小HashMap,通过移位操作拿到当前的偏移量如果不存在就创建一个:

 private Segment<K,V> ensureSegment(int k) {
        final Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        Segment<K,V> seg;
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            int cap = proto.table.length;
            float lf = proto.loadFactor;
            int threshold = (int)(cap * lf);
            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                == null) { // recheck
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                       == null) {
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

将第零个Segment作为原型将第k个Segment设置出来。

注意这里的put虽然是线程安全的但是并没有使用锁。

上面的s.put():


 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
 //这里的tryLock还是一个CAS操作tryLock不会等待,Lock才会等待
            HashEntry<K,V> node = tryLock() ? null :
            //如果trylock失败,代码在线面详细解释这个函数
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                槽位映射到index上,拿出index中的第一个
                HashEntry<K,V> first = entryAt(tab, index);
                //尝试将first插到entry中
                for (HashEntry<K,V> e = first;;) {
                //如果有hash冲突
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        //有Hash冲突就将value串起来
                        e = e.next;
                    }
                    //如果没有hash冲突
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                        //就将entry set到数组中去
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            int retries = -1; // negative while locating node
            //不断的trylock
            while (!tryLock()) {
                HashEntry<K,V> f; // to recheck first below
                if (retries < 0) {
                    if (e == null) {
                        if (node == null) // speculatively create node
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                //如果trylock超过了一定的次数,就会挂起
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                //这里可能是hash进行了扩容重hash等操作,将retrues复制为-1,
                //再不断的进行trylock
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }

这里的不断的trylock,如果到达一定的次数在挂起,是concurrentHashMap的核心优化
但是注意在concurrentHashMap中有一个size操作中的:

public int size() {
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; // true if size overflows 32 bits
        long sum;         // sum of modCounts
        long last = 0L;   // previous sum
        int retries = -1; // first iteration isn't retry
        try {
            for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

上面的代码中重复进行了lock和unlock,因为在想要得到map的size的时候是不能够再进行修改的,所以加上锁,这里可能会有性能问题,可是size操作用的并不是很频繁,所以可以忽略。

rehash操作:

//在put操作中
//如果大于了hash的阈值,就会进行rehash
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);

 /**
         * Doubles size of table and repacks entries, also adding the
         * given node to new table
         */
 private void rehash(HashEntry<K,V> node) {
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1;
            threshold = (int)(newCapacity * loadFactor);
            HashEntry<K,V>[] newTable =
                (HashEntry<K,V>[]) new HashEntry[newCapacity];
            int sizeMask = newCapacity - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                HashEntry<K,V> e = oldTable[i];
                if (e != null) {
                    HashEntry<K,V> next = e.next;
                    int idx = e.hash & sizeMask;
                    if (next == null)   //  Single node on list
                        newTable[idx] = e;
                    else { // Reuse consecutive sequence at same slot
                        HashEntry<K,V> lastRun = e;
                        int lastIdx = idx;
                        for (HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            int k = last.hash & sizeMask;
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        newTable[lastIdx] = lastRun;
                        // Clone remaining nodes
                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                            V v = p.value;
                            int h = p.hash;
                            int k = h & sizeMask;
                            HashEntry<K,V> n = newTable[k];
                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                        }
                    }
                }
            }
            int nodeIndex = node.hash & sizeMask; // add the new node
            node.setNext(newTable[nodeIndex]);
            newTable[nodeIndex] = node;
            table = newTable;
        }

rehash会将空间容量翻倍,将node放进去,是一个比较耗时的操作,做了一点油画,尽量重用现有的元素,不去新建元素,也就是说在翻倍前和翻倍后,同一个元素,很可能在用一个位置

2.3. BlockingQueue

阻塞队列

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
在这里插入图片描述
以上两段内容转自:http://ifeve.com/java-blocking-queue/
在这里插入图片描述
是一个接口,不是一个实际的类,是一个并发容器,但不是一个高性能的并发容器,和concurrentHashMap(高性能)不一样,但它本身的好处是在多个线程共享数据的容器

如上图,如果队列为空,还试图往队列里面读数据,读的线程就会等待,等待有其他的线程往里面写数据的时候,才会唤醒,并且去拿到数据。如果队列已经满了,还想往队列中存数据,写的线程就会等待,等有人将数据拿掉之后才会写进去。

所以会引起线程的阻塞。

ArrayBlockingQueue源码

/** Main lock guarding all access */
    final ReentrantLock lock;//保证线程安全
    /** Condition for waiting takes */
    private final Condition notEmpty;//提示读操作不为空
    /** Condition for waiting puts */
    private final Condition notFull;//提示写操作不为满

 public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        //put操作直接加锁,所以是比较耗性能的。
        lock.lockInterruptibly();
        try {
            while (count == items.length)
            //如果是满的会进行等待,直到下面的take操作
                notFull.await();
            insert(e);
        } finally {
            lock.unlock();
        }
    }
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
            //这里是为了读操作
                notEmpty.await();
                //去掉写操作的锁,具体代码往下看
            return extract();
        } finally {
            lock.unlock();
        }
    }

 private E extract() {
        final Object[] items = this.items;
        E x = this.<E>cast(items[takeIndex]);
        items[takeIndex] = null;
        takeIndex = inc(takeIndex);
        --count;
        //在这里讲锁打开
        notFull.signal();
        return x;
    }

2.4. ConcurrentLinkedQueue

高性能的链表队列,处理类似于concurrentHashMap,内部使用大量的无锁的算法。

转载于:https://www.cnblogs.com/LeesinDong/p/10835408.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值