JUC笔记1

JUC学习小结

并发容器

BlockingQueue

阻塞队列接口,先进先出(FIFO)

主要的实现类有ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue、SynchronousQueue

1.ArrayBlockingQueue

基于数组实现,内部维护了一个定长数组items,用于缓存队列中的数据对象,内部还有两个整型变量putIndex和takeIndex,用来标识队列头部和尾部在数组中的位置共用一个锁资源ReentrantLock lock,但其读写和获取足够轻巧,故没必要再引入单独的锁机制

生产者消费者

以下以生产者消费者为例

public class ArrayBlockingQueueDemo {
	// 队列的最大长度
	private final int MAX_LEN = 10;
	// 实现多线程可选择继承Thread,实现Runnable接口和实现Callable接口
	class Provider extends Thread {

		ArrayBlockingQueue<Integer> queue;

		public Provider (ArrayBlockingQueue<Integer> queue) {
			this.queue = queue;
		}

		@Override
		public void run() {
			provider();
		}
		private synchronized void provider() {
			while (true) {
				while (queue.size() == MAX_LEN) {
					System.out.println("队列资源已满");
					// flag = false;
				}

				try {
					queue.put(1);
					System.out.println("生产-当前队列资源数:"+queue.size());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	class Consumer extends Thread {

		ArrayBlockingQueue<Integer> queue;

		public Consumer (ArrayBlockingQueue<Integer> queue) {
			this.queue = queue;
		}

		@Override
		public void run() {
			consumer();
		}

		private void consumer() {
			while (true) {
				while (queue.size() == 0) {
					System.out.println("队列内无资源");
				}
				queue.poll();
				System.out.println("消费-当前队列资源数:"+queue.size());
				// 生产者等待消费者释放资源
				try {
					Thread.sleep(51);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		ArrayBlockingQueueDemo pac = new ArrayBlockingQueueDemo();
		ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(pac.MAX_LEN);
		Consumer consumer1 = pac.new Consumer(queue);
		Provider provider1 = pac.new Provider(queue);
		provider1.start();
		consumer1.start();
	}
}
2.LinkedBlockingQueue

基于链表实现,内部维护了一个数据缓冲队列(由一个链表构成)

生产者消费者分别采用独立的锁ReentrantLock putLock和ReentrantLock takeLock,高并发情况下可以并行操作数据。

3.DelayQueue

其中的元素只有给定时间到了,才能从队列中获取数据

该队列没有大小限制,故生产者可以一直放资源。

4.PriorityBlockingQueue

基于优先级的阻塞队列,优先级由传入的Compare对象决定

不会阻塞生产者,但在无可用数据时才会阻塞消费者,因此生产速度必须大于消费速度,避免安全隐患。

5.SynchronousQueue

无缓冲区的等待队列,生产者直接将数据交给消费者,消费者直接找生产者要数据,若一方没找到则等待。

BlockingDeque

双端阻塞队列,相较于BlockingQueue头部进尾部出,该队列头部尾部都可进出,可以自由选择

实现类以LinkedBlockingDeque为例(基于链表的双端阻塞队列)

public class LinkedBlockingDequeDemo {
	private static int MAX_NUM = 10000;
	private static LinkedBlockingDeque<Integer> deque = new LinkedBlockingDeque<>();

	static class Privider implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < MAX_NUM; i++) {
				deque.addFirst(i);
				System.out.println("添加" + i);
				try {
					Thread.sleep(40);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	static class Consumer implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < MAX_NUM; i++) {
				deque.getLast();
				try {
					System.out.println("获取" + deque.takeLast());
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		Privider privider = new Privider();
		Consumer consumer = new Consumer();
		Thread prividerThread = new Thread(privider);
		Thread consumerThread = new Thread(consumer);
		prividerThread.start();
		consumerThread.start();
	}

}
ConcurrentHashMap

与HashMap类似,但线程安全,1.7采用分段锁机制,用Segment数组存数据,对每个Segment数组加锁,1.8采用CAS

public class ConcurentHashMaoDemo {
	private static int MAX_NUM = 1000;
	private static ConcurrentHashMap<Integer, String>
			map = new ConcurrentHashMap<>();

	static class Provider implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < MAX_NUM; i++) {
				map.put(i, "value" + i);
				System.out.println("添加了:value" + i);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	static class Consumer implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < MAX_NUM; i++) {
				System.out.println("获取了:" + map.get(i));
				try {
					Thread.sleep(150);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		Provider provider = new Provider();
		Consumer consumer = new Consumer();
		Thread providerThread = new Thread(provider);
		Thread consumerThread = new Thread(consumer);
		providerThread.start();
		consumerThread.start();
	}

}

Executor框架

把整个线程分为任务、任务的执行、对执行结果的操作

任务:包括被执行的任务需要实现的接口:Runnable或Callable接口

任务的执行:考扩任务执行的核心接口Executor,继承自Executor的ExecutorService接口。(Executor中有两个ExecutorService的实现类ThreadPoolExecutor和ScheduledThreadPoolExecutor)

对执行结果的操作:包括Future接口和实现它的FutureTask

Executor

Executor框架的基础接口,规定了execute()方法

ExecutorService

继承自Executor的接口

ThreadPoolExecutor

线程池的核心实现类,用来执行被提交的任务。参数:

int corePoolSize:核心线程池的大小

int maximumPoolSize:最大线程池的大小

long keepAliveTime:多余的空闲线程等待新任务的最长时间

TimeUnit unit:上面keepAliveTime时间的单位(毫秒/秒/分/时)

BlockingQueue workQueue:工作队列,存放任务

ScheduledThreadPoolExecutor

可以在给定的延迟后执行任务,或者定期执行任务

Future接口和实现它的FutureTask

代表任务执行的结果,可以用来查询、取消执行任务的结果

Runnable或Callable接口的实现类

执行的任务,都可以被ThreadPoolExecutor和ScheduledThreadPoolExecutor执行,可以交给ExecutorService.submit()执行,Runnable可以直接交给ExecutorService.execute()执行

Executors

提供线程池相关操作,提供一系列工厂方法创建线程池,返回的线程池都实现了ExecutorService接口

线程池

Executors提供了创建四种线程池的工厂方法

FixedThreadPool

固定线程数的线程池,使用LinkedBlockingQueue作为工作队列,以下为用Executors调用工厂方法

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
ExecutorService executorService = Executors.newFixedThreadPool(5);
CachedThreadPool

可缓存的线程池,使用SynchronousQueue作为工作队列,采用直接提交的提交策略。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
ExecutorService executorService = Executors.newCachedThreadPool();
SingleThreadPool

只有一个核心线程的线程池,串行执行任务

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
ExecutorService executorService = Executors.newSingleThreadExecutor();
ScheduledThreadPool

支持定时及周期性执行任务的线程池

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
ExecutorService executorService = Executors.newScheduledThreadPool(5);

Atomic原子类

以i++为例,它实际上分为"读-改-写"三个步骤,保证原子性就是保证这三个步骤不可分割,atomic中实现的基础是CAS(Compare-And-Swap),使用一个Unsafe对象调用底层的native函数实现,。
CAS
核心在于用期望值与实际值比较,如果相同则执行操作提交,否则不做修改。例如一整型变量i=10,线程A想把i的值+1,则此时期望值=10,更新后的新值为11;但此时线程B已经把i的值修改成了11,则此时i的实际值为11,与线程A中的期望值比较不一样,则不做修改。
原子更新基本类型
包括AtomicInteger,AtomicLong,AtomicBoolean,对应的值value用volatile修饰,保证内存可见;用CAS算法保证数据的原子性。以下以AtomicInteger为例,常用方法包括:
int get():获取当前值

void set(int newValue):设置为给定值

void lazySet(int newValue):最终设置为给定值

int getAndSet(int newValue):获取当前值,再设置为给定值

boolean compareAndSet(int expect, int update):内存值与预期值(expect)相同,则设置为新值(update)

int getAndIncrement():先获取当前值,再以原子方式将当前值+1
public class AtomicIntegerDemo {
	public static AtomicInteger atomicFlag = new AtomicInteger();
	public static int basicFlag = 0;
	public static int MAX_NUM = 35000;

	public static class MyAtomicAddRunnable implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < MAX_NUM; i++) {
				// AtomicInteger的自增方法
				atomicFlag.incrementAndGet();
				// System.out.println(atomicFlag.get());
			}
			System.out.println("flag = "+atomicFlag.get());
		}
	}

	public static class MyBasicAddRunnable implements Runnable {

		@Override
		public void run() {
			for (int i = 0; i < MAX_NUM; i++) {
				basicFlag++;
				// System.out.println(basicFlag);
			}
			System.out.println("flag = "+basicFlag);
		}
	}

	public static void main(String[] args) {
		MyAtomicAddRunnable r1 = new MyAtomicAddRunnable();
		MyAtomicAddRunnable r2 = new MyAtomicAddRunnable();
		MyBasicAddRunnable r3 = new MyBasicAddRunnable();
		MyBasicAddRunnable r4 = new MyBasicAddRunnable();
		Thread t1 = new Thread(r1);
		Thread t2 = new Thread(r2);
		t1.start();
		t2.start();
	}
}
没使用原子类型时输出的值为:flag = 69999

使用了原子类型时输出值为:flag = 70000
原子更新引用类型
包括AtomicReference,AtomicStampedReference,AtomicMarkableReference,前面原子基本类型每次只能更新一个变量,当存在多个变量,如一个对象的多个属性时,就需要引用类型

public class AtomicReferenceDemo {
	private static AtomicReference<Person> reference = new AtomicReference();

	public static void main(String[] args) {
		Person person1 = new Person("zs", 20);
		Person person2 = reference.getAndSet(person1);
		System.out.println("reference: "+reference.get());
		System.out.println("person2: "+person2);

		System.out.println("=====================");

		// 先将旧值赋给person3,自己的值再set为传入的Person
		Person person3 = reference.getAndSet(new Person("ls", 22));
		person2 = reference.get();
		System.out.println("reference: "+reference.get());
		System.out.println("person1: "+person1);
		System.out.println("person2: "+person2);
		System.out.println("person3: "+person3);
		// 同样采用CAS,赋的是内存地址,equals结果为true
		System.out.println("person1.equals(person3) = " + person1.equals(person3));

		System.out.println("=====================");

		// 如果reference的值为person2的话,就将其set为person1
		System.out.println(reference.compareAndSet(person2, person1));
		System.out.println("reference: "+reference.get());
		System.out.println("person1: "+person1);
		System.out.println("person2: "+person2);
		System.out.println("person1.equals(person2) = " + person1.equals(person2));
		System.out.println("person1.equals(reference.get()) = " + person1.equals(reference.get()));
	}
}
但根据CAS的特点,如果我们在线程1用AtomicReference对某一对象进行操作,提交之前有一个线程2先修改了这一对象的值后又修改了回来,导致线程1在获取对象内存值(valueOffset)时,以为该对象没有被修改过,可能会出一些安全隐患,所以引入AtomicStampedReference,AtomicMarkableReference加入版本号和修改标记表示对象是否被修改过。

public class AtomicStampedReferenceDemo {

	private static final Integer DEFAULT_NUM = 50;
	private static final Integer UPDATE_NUM = 55;
	private static final Integer TEM_NUM = 54;

	private static AtomicStampedReference<Integer> reference = new AtomicStampedReference(DEFAULT_NUM, 1);
	
    /**
    * 线程A:将reference内的值由DEFAULT_NUM修改为UPDATE_NUM
    * 线程B:将reference内的值由DEFAULT_NUM修改为TEM_NUM,再由TEM_NUM修改回DEFAULT_NUM
    */
	public static void main(String[] args) {
		new Thread(() -> {
			// 当前值
			Integer value = reference.getReference();
			//当前版本号
			int stamp = reference.getStamp();
			System.out.println(Thread.currentThread().getName() + "===当前值为:" + value +
					",当前版本号为:" + stamp);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (reference.compareAndSet(value, UPDATE_NUM, stamp, stamp + 1)) {
				System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
						",当前版本号为:" + reference.getStamp());
			} else {
				System.out.println("版本号不同,更新失败");
			}

		},"线程A").start();

		new Thread(() -> {
			// 确保线程A先执行
			Thread.yield();
			// 当前值
			Integer value = reference.getReference();
			//当前版本号
			int stamp = reference.getStamp();
			System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
					",当前版本号为:" + reference.getStamp());
			reference.compareAndSet(reference.getReference(), TEM_NUM, stamp, stamp + 1);
			System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
					",当前版本号为:" + reference.getStamp());
			reference.compareAndSet(reference.getReference(), DEFAULT_NUM, stamp, stamp + 1);
			System.out.println(Thread.currentThread().getName() + "===当前值为:" + reference.getReference() +
					",当前版本号为:" + reference.getStamp());
		}, "线程B").start();
	}
}
输出结果:

线程A===当前值为:50,当前版本号为:1
线程B===当前值为:50,当前版本号为:1
线程B===当前值为:54,当前版本号为:2
线程B===当前值为:50,当前版本号为:3
版本号不同,更新失败
原子更新数组类型
包括AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,是存储前面基本类型变量的数组,以下以AtomicIntegerArray为例

常用方法:

int length():得到当前数组长度

int get(int i):得到下标为i处的元素

void set(int i, int newValue):将下标i处的元素设置为给定值

void lazySet(int i, int newValue):将下标i处的元素最终设置为给定值

boolean compareAndSet(int i, int expect, int update):比较i处元素是否为期待值,true则设置为给定值

int getAndIncrement(int i):i处元素值+1
public class AtomicIntegerArrayDemo {
	private static int[] intArray = new int[]{115, 26, 34, 49, 58};
	private static AtomicIntegerArray array = new AtomicIntegerArray(intArray);

	public static void main(String[] args) {
		System.out.println(array.length());
		System.out.println("下标为2的值为:" + array.get(2));

		array.set(2, 33);
		System.out.println("set后下标为2的值为:" + array.get(2));

		array.lazySet(2, 36);
		System.out.println("lazySet后下标为2的值为:" + array.get(2));

		System.out.println(array.compareAndSet(2, 38, 39));

		System.out.println("自增前值为:" + array.getAndIncrement(0));
		System.out.println("自增后为:" + array.get(0));
	}
}

输出结果为:
数组长度为:5
下标为2的值为:34
set后下标为2的值为:33
lazySet后下标为2的值为:36
是否设置为新值:false
自增前值为:115
自增后为:116
原子更新字段
包括AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater,是基于反射的原子更新字段的值,相较于原子基本类型有一些限制:

1.字段必须是volatile修饰的,保证线程间共享变量时立即可见

2.字段的可访问描述符相同(private/public/protected)

3.只能是实例对象(不能加static)

4.只能是可修改变量(不能加final)

5.对于AtomicIntegerFieldUpdater/AtomicLongFieldUpdater,只能修改基本类型int/long,对于其包装类Integer/Long不能修改,只能由AtomicReferenceFieldUpdater修改

AtomicIntegerFieldUpdater例子:
public class AtomicIntgerFieldUpdaterDemo {

	private static AtomicIntegerFieldUpdater<DataDemo> updater =
			AtomicIntegerFieldUpdater.newUpdater(DataDemo.class, "count");

	static class DataDemo {
		public volatile int count = 100;
	}

	public static void main(String[] args) {
		DataDemo dataDemo = new DataDemo();
		// 把count的值由100更新为120
		Thread t1 = new Thread(() -> {
			if (updater.compareAndSet(dataDemo, 100, 120)) {
				System.out.println("update success,count = " + dataDemo.count);
			} else {
				System.out.println("update false,count = " + dataDemo.count);
			}
		});
		Thread t2 = new Thread(() -> {
			if (updater.compareAndSet(dataDemo, 100, 120)) {
				System.out.println("update success,count = " + dataDemo.count);
			} else {
				System.out.println("update false,count = " + dataDemo.count);
			}
		});

		t1.start();
		t2.start();
	}
}
输出结果:
update success,count = 120
update false,count = 120

AtomicReferenceFieldUpdater例子:

public class AtomicReferenceFieldUpdaterDemo {

	public static void main(String[] args) {
        // 把name由catA换成catB
		AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater
				.newUpdater(Cat.class, String.class, "name");
		Cat cat = new Cat();
		System.out.println(cat.name);
		updater.compareAndSet(cat, "catA", "carB");
		System.out.println(cat.name);
	}

}

class Cat {
	public volatile String name = "catA";
}
输出结果
catA
carB
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值