JAVA并发之多线程基础(5)

上面介绍了并发编程中的栅栏等JAVA并发之多线程基础(4) 。通过唯一的一个终点线来帮助确定线程是多晚开始执行下一次操作。

LockSupport

提供了一个比较底层的线程挂起操作。有点类似于suspend()方法,但是这个方法不建议使用。

  1. park()使得当前线程挂起。里面也是调用了Unsafe类进行底层操作。
public static void park() {
        UNSAFE.park(false, 0L);
    }
复制代码

2.unpark(Thread thread)是将当前线程继续往下执行。

public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
复制代码

LockSupport上面的两个方法不像suspend()resume()方法。在这两个方法中resume()必须在suspend()之前执行。否则线程就会被永远的挂起,造成死锁。而LockSupport中的unpark可以在park之前。

package com.montos.lock;
import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
	public static Object obj = 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(name);
		}
		@Override
		public void run() {
			synchronized (obj) {
				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();
	}
}
复制代码

通过上面的一个小的Demo可以看出LockSupport的使用方法,在使用方法中也是很简单的。以上篇幅就对JDK内的并发类进行了讲解。大多数用到了CAS无锁操作。避免线程阻塞的情况发生。

并发集合

在日常的业务处理中,集合类是我们使用最多的一个操作。对应的也就是三大类:MapSet以及最后的List。在日常并发量小的情况下,也许我们会这么使用对应的集合操作:

//Map集合
private static final Map<String,String> map = Collections.synchronizedMap(new HashMap<String,String>());
//List集合	
private static final List<String> list = Collections.synchronizedList(new ArrayList<String>());
//Set集合
private static final Set<String> set = Collections.synchronizedSet(new HashSet<>());
复制代码

上面的使用了Collections中的同步方法进行包装。深入了解里面的调用过程,也就是各个方法上面加上了synchronized进行限制。从而达到最后线程安全的目的。如果并发量很大的话,是很会影响性能的,毕竟它使得对应的读和写操作都变成了串行。于是有了下面的几种线程安全类:

  • ConcurrentHashMap高性能并发的Map。里面的操作是跟HashMap是一样的。但是里面多了一种结构:Segment(段)。
static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }
复制代码

可以看出他是继承了重入锁进行实现的。为什么会有段的概念在这里面呢?为了更好的支持高并发而设计的一个概念。每个元素在特定的段中,将整个集合分成许多段进行管理,而高并发的时候,只要对每个段进行控制就能起到一个同步的作用。

再统计数量大小的时候,ConcurrentHashMap中利用了CounterCell结构体进行统计。每一个段中存储了该段的大小,然后再循环求和出当前的数组大小情况。

final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
复制代码
  • BlockingQueue是一个非常好的多线程共享数据的容器。当队列为空的时候,读线程就会进行等待,相反,如果当前队列已满,那么写线程就会等待。看到对应的实现类中都会有下面三个成员变量:
/** Main lock guarding all access */
final ReentrantLock lock; //加锁进行控制访问

/** Condition for waiting takes */
private final Condition notEmpty;//元素获取的时候,进行判断是否为空进行阻塞

/** Condition for waiting puts */
private final Condition notFull;//元素加入的时候,判断队列是否已满
复制代码

这上面的三个变量就控制整个阻塞队列的访问以及元素的增加。从根本上来说他并不像上面介绍的ConcurrentHashMap访问性能高,但是我们需要的是他的多线程访问共有数据的能力。

阻塞队列的元素获取方法

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;
    }
复制代码

阻塞队列的元素增加方法

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();//通知读线程进行操作 
    }
复制代码
  • ConcurrentLinkedQueue上面谈及到的是多线程共享数据的容器,而这个是高并发情况下使用的。里面大量的使用了无锁的操作以及锁自旋,可以很好的保证性能问题,有兴趣的小伙伴可以去了解下。

以上谈及到的就是JDK中关于并发的一些简单介绍。在我介绍完成之后,我会对底层的源码进一步讲解,有兴趣的小伙伴可以关注我~

转载于:https://juejin.im/post/5cf08d3c518825789e031472

猜你喜欢

转载自blog.csdn.net/weixin_34191845/article/details/91425038