JUC学习笔记
Posted Shinka_YXS
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC学习笔记相关的知识,希望对你有一定的参考价值。
尚硅谷JUC源码讲授实战教程完整版(java juc线程精讲)
1.volatile 关键字与内存可见性
当程序运行,JVM会为每一个执行任务的线程分配独立的缓存用于提高效率。
内存可见性问题是,当多个线程操作共享数据时,彼此不可见。
volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。相较于synchronized是一种较为轻量级的同步策略。
volatile 不具备"互斥性",也不能保证变量的"原子性"
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo).start();
while (true) {
// synchronized (threadDemo) {
if (threadDemo.isFlag()) {
System.out.println("---------");
break;
}
// }
}
}
}
class ThreadDemo implements Runnable {
// private boolean flag = false;
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (Exception e) {
}
flag = true;
System.out.println("flag=" + isFlag());
}
public boolean isFlag() {
return flag;
}
}
2.原子变量与CAS算法
原子变量:jdk1.5后java.util.concurrent.atomic包下提供了常用的原子变量(其中有AtomicInteger)
1、volatile保证内存可见性
2、CAS (Compare-And-Swap)算法保证数据的原子性CAS算法是硬件对于并发操作共享数据的支持
CAS包含了三个操作数:内存值V、预估值A、更新值B。当且仅当V == A 时,V = B(将B赋值给A)。否则,将不做任何操作
public class AtomicTest {
public static void main(String[] args) {
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable {
// private int serialNum = 0;
private AtomicInteger serialNum = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNum());
}
public int getSerialNum() {
// return serialNum++;
return serialNum.getAndIncrement();
}
}
3.模拟CAS算法
public class CompareAndSwapTest {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean flag = cas.compareAndSet(expectedValue, (int)(Math.random() * 100));
System.out.println(flag);
}
}).start();
}
}
}
class CompareAndSwap {
private int value;
// 获取内存值
public synchronized int get() {
return value;
}
// 比较
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
this.value = newValue;
}
return oldValue;
}
// 设置
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
4.同步容器类ConcurrentHashMap
- Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
- ConcurrentHashMap同步容器类是Java 5增加的一个线程安全的哈希表。
对于多线程的操作,介于HashMap 与 Hashtable之间。
java1.8之前内部采用“锁分段”机制替代 Hashtable的独占锁。进而提高性能,1.8之后是CAS - 此包还提供了设计用于多线程上下文中的collection实现:
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。
当期望许多线程访问一个给定collection时,ConcurrentHashMap通常优于同步的HashMap,
ConcurrentSkipListMap通常优于同步的TreeMap。
当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。
/**
* CopyOnWriteArrayList/CopyOnWriteArraySet : 写入并复制
* 注意:不适合添加操作多的场景,因为每次添加都会进行复制,开销大,效率低
* 适合并发迭代操作多的场景
*/
public class CopyOnWriteArrayListTest {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 10; i++) {
new Thread(ht).start();
}
}
}
class HelloThread implements Runnable {
// private static List<String> list = Collections.synchronizedList(new ArrayList<>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
// 使用的是List<String>时 此处会抛出java.util.ConcurrentModificationException异常
list.add("AA");
}
}
}
5.CountDownLatch闭锁
-
Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
-
CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
-
闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
确保某个计算 在其需要的所有资源都被初始化之后 才继续执行;
确保某个服务 在其依赖的所有其他服务都已经启动之后 才启动;
等待直到某个操作所有参与者都准备就绪再继续执行。
/**
* CountDownLatch闭锁:在完成某些运算时,只有其他线程的运算全部完成后,当前运算才继续执行
*/
public class CountDownLatchTest {
public static void main(String[] args) {
// 后面开了5个线程,此处就传入计数5
CountDownLatch countDownLatch = new CountDownLatch(5);
LatchDemo latchDemo = new LatchDemo(countDownLatch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(latchDemo).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 当计数减到0时才执行
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
public CountDownLatch countDownLatch;
public LatchDemo(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
synchronized (this) {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}finally {
countDownLatch.countDown();
}
}
}
}
6.实现Callable接口
/**
* 创建线程方式三:实现Callable接口
* 相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
*/
public class CallableTest {
public static void main(String[] args) {
CallableImpl callableImpl = new CallableImpl();
// 执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果。FutureTask是Future接口的实现类
// 还有另外一个构造方法是FutureTask(Runnable runnable, V result)
FutureTask<Integer> result = new FutureTask<>(callableImpl);
new Thread(result).start();
// 接收线程运算后的结果
try {
Integer sum = result.get(); // futureTask也可用于闭锁
System.out.println(sum);
System.out.println("---------当分线程执行完 get()拿到结果了才执行此处---------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class CallableImpl implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
7.Lock同步锁
—、用于解决多线程安全问题的方式:
1.同步代码块(synchronized隐式锁)
2.同步方法(synchronized隐式锁)
3.同步锁Lock(jdk 1.5后出现,是个显式锁,需要通过lock()方法上锁,必须通过unlock()方法进行释放锁,所以有一定的风险,比如锁未成功释放)
public class LockTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
}
}
class Ticket implements Runnable {
private int ticketNum = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock(); // 上锁
try {
if (ticketNum > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + --ticketNum);
}
} finally {
lock.unlock(); // 释放锁
}
}
}
}
8.生产者消费者案例-虚假唤醒
如何用Lock同步锁实现等待唤醒机制,也就是像synchronized关键字的wait()和notify()的等待唤醒机制
8.1未使用等待唤醒机制出现的问题
public class ProductorAndConsumerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A").start();
new Thread(consumer, "消费者B").start();
}
}
// 店员
class Clerk {
private int productNum = 0;
// 进货
public synchronized void get() {
if (productNum >= 5) {
System.out.println("商品仓位已满!");
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
} else {
System.out.println(Thread.currentThread().getName() + "进货:" + ++productNum);
// this.notifyAll();
}
}
// 卖货
public synchronized void sale() {
if (productNum <= 0) {
System.out.println("商品已售罄!");
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
} else {
System.out.println(Thread.currentThread().getName() + "售出:" + --productNum);
// this.notifyAll();
}
}
}
// 生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
clerk.get();
}
}
}
// 消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
clerk.sale();
}
}
}
即使仓位满了,生产者还是不停地进货;即使商品售罄了,消费者还是不停地想要去消费、
8.2使用等待唤醒机制
将8.1中注释的代码放开、此时运行结果都是有效的数据、
8.3问题修正1
8.2的等待唤醒机制有些问题、
当把仓位改为1,并给生产者加0.2秒的延迟、
// 进货
public synchronized void get() {
if (productNum >= 1) { // 把仓位改为1
System.out.println("商品仓位已满!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(以上是关于JUC学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
JUC并发编程 共享模式之工具 JUC CountdownLatch(倒计时锁) -- CountdownLatch应用(等待多个线程准备完毕(
可以覆盖上次的打印内)等待多个远程调用结束)(代码片段