JUC笔记
Posted scanner小霸王
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC笔记相关的知识,希望对你有一定的参考价值。
synchronized的8种现象
new 锁的是 一个对象
static 锁的是 Class
1)锁的调用者是同一个对象,按照顺序执行
先打印邮件1,再发短信2
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/**
* **1、标准访问,请问先打印邮件1还是短信2?**
*
*
*/
public class Test1
public static void main(String[] args)
Phone phone = new Phone();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() ->
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
phone.sendMS();
, "B").start();
// 手机,发短信,发邮件
class Phone
// 被 synchronized 修饰的方法、锁的对象是方法的调用者、
public synchronized void sendEmail()
System.out.println("sendEmail");
public synchronized void sendMS()
System.out.println("sendMS");
2)邮件方法暂停4秒钟,还是先打印邮件
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/**
* **2、邮件方法暂停4秒钟,请问先打印邮件还是短信?**
*/
public class Test2
public static void main(String[] args)
Phone2 phone = new Phone2();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() ->
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() ->
phone.sendMS();
, "B").start();
// 手机,发短信,发邮件
class Phone2
// 被 synchronized 修饰的方法、锁的对象是方法的调用者、
public synchronized void sendEmail()
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
public synchronized void sendMS()
System.out.println("sendMS");
3)新增的方法没有被 synchronized 修饰,不是同步方法,所以不需要等待,其他线程用了一个把锁
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/**
* 3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?
*/
public class Test3
// 回家 卧室(锁) 厕所
public static void main(String[] args)
Phone3 phone = new Phone3();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() -> // 一开始就执行了
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() -> // 一秒后执行
phone.hello();
, "B").start();
// 锁:竞争机制
// 手机,发短信,发邮件
class Phone3
// 被 synchronized 修饰的方法、锁的对象是方法的调用者、
public synchronized void sendEmail()
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
public synchronized void sendMS()
System.out.println("sendMS");
// 新增的方法没有被 synchronized 修饰,不是同步方法,所以不需要等待,其他线程用了一个把锁
public void hello()
System.out.println("hello");
4)我们这里两个线程不是同个对象。
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/**
* **4、两部手机、请问先打印邮件还是短信?**
*/
public class Test4
// 回家 卧室(锁) 厕所
public static void main(String[] args)
// 两个对象,互不干预
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() -> // 一开始就执行了
phone1.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() -> // 一秒后执行
phone2.sendMS();
, "B").start();
// 手机,发短信,发邮件
class Phone4
// 被 synchronized 修饰的方法、锁的对象是方法的调用者、调用者不同,没有关系,量个方法用得不是同一个锁!
public synchronized void sendEmail()
// 善意的延迟
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
public synchronized void sendMS()
System.out.println("sendMS");
5)/被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
是 同一把锁
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/*
**5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?**
*/
public class Test5
// 回家 卧室(锁) 厕所
public static void main(String[] args)
// 两个对象,互不干预
Phone5 phone = new Phone5();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() -> // 一开始就执行了
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() -> // 一秒后执行
phone.sendMS();
, "B").start();
// 手机,发短信,发邮件
class Phone5
// 对象 类模板可以new 多个对象!
// Class 类模版,只有一个
// 被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
// 同一把锁
public static synchronized void sendEmail()
// 善意的延迟
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
public static synchronized void sendMS()
System.out.println("sendMS");
6)虽然是phone1和phone2不同对象调用,但是静态Static修饰之后,是个Class对象,锁的是同个对象,所以还是打电话优先
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/** 第一次听可能不会,第二次也可能不会,但是不要放弃,你可以!
**6、两个静态同步方法,2部手机,请问先打印邮件还是短信?**
*/
public class Test6
// 回家 卧室(锁) 厕所
public static void main(String[] args)
// 两个对象,互不干预
Phone6 phone = new Phone6();
Phone6 phone2 = new Phone6();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() -> // 一开始就执行了
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() -> // 一秒后执行
phone2.sendMS();
, "B").start();
// 手机,发短信,发邮件
class Phone6
// 对象 类模板可以new 多个对象!
// Class 类模版,只有一个
// 被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
// 同一把锁
public static synchronized void sendEmail()
// 善意的延迟
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
public static synchronized void sendMS()
System.out.println("sendMS");
7)一个普通同步方法,一个静态同步方法不同锁,先sendMS
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/**
* 7、一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?**
*/
public class Test7
// 回家 卧室(锁) 厕所
public static void main(String[] args)
// 两个对象,互不干预
Phone7 phone = new Phone7();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() -> // 一开始就执行了
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() -> // 一秒后执行
phone.sendMS();
, "B").start();
// 解析:连个方法的锁不同,所以不阻塞
class Phone7
// CLASS
public static synchronized void sendEmail()
// 善意的延迟
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
// 对象
// 普通同步方法
public synchronized void sendMS()
System.out.println("sendMS");
8)两个对象分别调用静态方法和非静态方法,sendMS优先
package com.coding.lock8;
import java.util.concurrent.TimeUnit;
/**
* **8、一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?**
*/
public class Test8
public static void main(String[] args)
// 两个对象,互不干预
Phone8 phone = new Phone8();
Phone8 phone2 = new Phone8();
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
new Thread(() -> // 一开始就执行了
phone.sendEmail();
, "A").start();
// 干扰
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
new Thread(() -> // 一秒后执行
, "B").start();
class Phone8
// CLASS
public static synchronized void sendEmail()
// 善意的延迟
try
TimeUnit.SECONDS.sleep(4);
catch (InterruptedException e)
e.printStackTrace();
System.out.println("sendEmail");
// 对象
// 普通同步方法
public synchronized void sendMS()
System.out.println("sendMS");
集合类不安全
1)ArrayList不支持并发,效率高,可以使用CopyOnWriteArrayList,
CopyOnWriteArrayList比vector效率高,vector底层用了synchnized,CopyOnWriteArrayList用了locked锁
synchronized与Lock的区别
a) synchronized 是Java内置的关键字,使用后会自动释放锁
Lock是java.util.concurrent.Locks 包下的一个接口,必须要手动释放。特别是在发生异常时,需要在 finally 块中进行手动释放,否则会发生死锁行为
b)synchronized 是非公平锁,即不能保证等待锁线程的顺序;Lock的实现 ReentrantLock 可通过实例化true or false 的构造参数实现公平锁和非公平锁,默认为非公平锁
Lock lock =new ReentrantLock (true);公平锁
package com.coding.collunsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 善于总结:
* 1、 故障现象: ConcurrentModificationException
* 2、 导致原因: 多线程操作集合类不安全
* 3、 解决方案:
* List<String> list = new Vector<>(); // Vector 是一个线程安全的类,效率低下 50
* List<String> list = Collections.synchronizedList(new ArrayList<>()); // 60
* List<String> list = new CopyOnWriteArrayList<>(); // JUC 100 推荐使用
*/
public class UnsafeList2
public static void main(String[] args)
// 代码实现
// ArrayList<Object> list = new ArrayList<>(); // 效率高,不支持并发!
// List<String> list = new Vector<>(); // Vector 是一个线程安全的类,效率低下 50
// List<String> list = Collections.synchronizedList(new ArrayList<>()); // 60
// 多线程高并发程序中,一致性最为重要
// 写入时复制; COW 思想,计算机设计领域。优化策略
// 思想: 多个调用者,想调用相同的资源; 指针
// 只是去读,就不会产生锁!
// 假如你是去写,就需要拷贝一份都自己哪里,修改完毕后,在替换指针!
List<String> list = new CopyOnWriteArrayList<>(); // JUC 100
// 测试多线程下是否安全List,3条线程都不安全了
// 多线程下记住一个异常,并发修改异常 java.util.ConcurrentModificationException
// Exception ConcurrentModificationException
for (int i = 1; i <= 30; i++)
new Thread(()->
// 3个结果
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
,String.valueOf(i)).start();
2)Set不安全
使用:CopyOnWriteArraySet
hashSet底层就是hashMap
package com.coding.collunsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 善于总结:
* 1、 故障现象: ConcurrentModificationException 并发修改异常!
* 2、 导致原因: 并发下 HashSet 存在安全的问题
* 3、 解决方案:
* Set<String> set = Collections.synchronizedSet(new HashSet<>()); 60
* Set<String> set =new CopyOnWriteArraySet<>(); // 100
*
*/
public class UnsafeSet1
public static void main(String[] args)
// Set<String> set = new HashSet<>(); // 底层是什么
Set<String> set =new CopyOnWriteArraySet<>();
for (int i = 1; i <=30 ; i++)
new Thread(()->
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
,String.valueOf(i)).start();
3)hashMap不安全,使用ConcurrentHashMap
package com.coding.collunsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
// 任何存在想修改JDK源码都是不可取的
// ConcurrentModificationException
// 并发下 HashMap 不安全
// 解决方案:Map<String, String> map = new ConcurrentHashMap<>();
public class UnSafeMap
public static void main(String[] args)
// 在开发中会这样使用 HashMap 吗? 不会 一开始就知道 100大小的容量
// 根据实际的业务设置初始值
// 人生如程序,不是选择,就是循环,学习和总结十分重要!
//Map<String, String> map = new HashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
// 加载因子,初始值
// Map<String, String> map = new HashMap<>(100,0.75);
for (int i = 1; i <= 30; i++)
new Thread(()->
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
,String.valueOf(i)).start();
Callable
1)可以有返回值
2)可以抛出异常
3)方法不同,call()
new Thread(task,“A”).start(); 的构造器中,传的是Runnable的接口,但是Callable和Runnable没有关系,需要借助FutureTask
FutureTask task = new FutureTask(myThread); // 适配类
// 会打印几次 end
new Thread(task,“A”).start(); // 执行线程
//返回值在FutureTask 拿到
System.out.println(task.get());// 获取返回值, get()
注意:
a)有缓存,所以System.out.println(task.get());只输出一次
b)结果可能需要等待,会阻塞
package com.coding.callabledemo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
// 练武不练功,到老一场空
// API 工程师,只会用,不会分析~
public class Test1
public static void main(String[] args) throws ExecutionException, InterruptedException
// Thread(Runnable)
// Thread(RunnableFuture)
// Thread(FutureTask)
MyThread myThread = new MyThread();
FutureTask task = new FutureTask(myThread); // 适配类
// 会打印几次 end
new Thread(task,"A").start(); // 执行线程
new Thread(task,"B").start(); // 执行线程。细节1:结果缓存!效率提高N倍
System.out.println(task.get());// 获取返回值, get()
// 细节2:task.get() 获取值的方法一般放到最后,保证程序平稳运行的效率,因为他会阻塞等待结果产生!
// 线程是一个耗时的线程,不重要!
class MyThread implements Callable<Integer>
@Override
public Integer call() throws Exception
System.out.println("end");
TimeUnit.SECONDS.sleep(3);
return 1024;
JUC常用的辅助类
1)CountDownLatch:减法计数器
countDownLatch.countDown();減一
countDownLatch.await(); // 阻塞等待计数器归零,才繼續執行
package com.coding.demo03;
import java.util.concurrent.CountDownLatch;
// 程序如果不加以生活的理解再加上代码的测试,你就算不会
public class CountDownLatchDemo
// 有些任务是不得不阻塞的 减法计数器
public static void main(String[] args) throws InterruptedException
CountDownLatch countDownLatch = new CountDownLatch(6); // 初始值
for (int i = 1; i <=6 ; i++)
new Thread(()->
System.out.println(Thread.currentThread().getName()+"Start");
// 出去一个人计数器就 -1
countDownLatch.countDown();
,String.valueOf(i)).start();
countDownLatch.await(); // 阻塞等待计数器归零
// 阻塞的操作 : 计数器 num++
System.out.println(Thread.currentThread().getName()+"===END");
// 结果诡异的吗,达不到预期的 Main end 在最后一个
public static void test1()
for (int i = 1; i <=6 ; i++)
new Thread(()->
System.out.println(Thread.currentThread().getName()+"Start");
,String.valueOf(i)).start();
System.out.println(Thread.currentThread().getName()+"End");
2)CyclicBarrier:加法计算器
CyclicBarrier cyclicBarrier = new CyclicBarrier(8, new Runnable()
@Override
public void run()
System.out.println(“神龙召唤成功!”);
);
cyclicBarrier.await(); // 等待 阻塞,只有得到8的时候才会继续执行
package com.coding.demo03;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
// CyclicBarrier 栅栏 加法计数器
public class CyclicBarrierDemo
public static void main(String[] args)
// 集齐7个龙珠召唤神龙 ++ 1
// public CyclicBarrier(int parties, Runnable barrierAction)
// 等待cyclicBarrier计数器满,就执行后面的Runnable,不满就阻塞
CyclicBarrier cyclicBarrier = new CyclicBarrier(8, new Runnable()
@Override
public void run()
System.out.println("神龙召唤成功!");
);
for (int i = 1; i <= 7; i++)
final int temp = i;
new Thread(()->
System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠");
try
cyclicBarrier.await(); // 等待 阻塞
catch (InterruptedException e)
e.printStackTrace();
catch (BrokenBarrierException e)
e.printStackTrace();
, String.valueOf(i)).start();
3)Semaphore:信号量
// 模拟6个车,只有3个车位
Semaphore semaphore = new Semaphore(3); // 3个位置
semaphore.acquire(); // 得到,如果已经满了,就等待,直到有位置为止
semaphore.release(); // 释放位置,会将信号量释放+1,然后唤醒等待的线程
作用:多个共享资源共享资源互斥的使用!并发限流,控制最大的线程数
package com.coding.demo03;
import sun.misc.Unsafe;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// 抢车位
public class SemaphoreDemo
public static void main(String[] args)
// 模拟6个车,只有3个车位
Semaphore semaphore = new Semaphore(3); // 3个位置
for (int i = 1; i <= 6; i++)
new Thread(()->
// 得到车位
try
semaphore.acquire(); // 得到
System.out.println(Thread.currentThread().getName()+"抢到了车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"离开了车位");
catch (InterruptedException e)
e.printStackTrace();
finally
semaphore.release(); // 释放位置
,String.valueOf(i)).start();
读写锁
读的时候可以多个线程同时读取,写的时候只能有一个线程写
// 读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock(); // lck.unlock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();
package com.coding.rwdemo;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteDemo
public static void main(String[] args)
MyCache2 myCache = new MyCache2();
// 多个线程同时进行读写
// 五个线程在写 线程是CPU调度的
for (int i = 1; i < 5; i++)
final int temp = i;
new Thread(()->
myCache.put(temp+"",temp+"");
,String.valueOf(i)).start();
// 五个线程在读
for (int i = 1; i < 5; i++)
final int temp = i;
new Thread(()->
myCache.get(temp+"");
,String.valueOf(i)).start();
// 线程操作资源类,存在问题的
class MyCache
private volatile Map<String,Object> map = new HashMap<>();
// 没有加读写锁的时候,第一个线程还没有写入完成,可能会存在其他写入~
// 写。独占
public void put(String key,String value)
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
// 存在别的线程插队
System.out.println(Thread.currentThread().getName()+"写入完成");
// 读
public void get(String key)
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取结果:"+result);
// 线程操作资源类,存在问题的
class MyCache2
private volatile Map<String,Object> map = new HashMap<>();
// ReadWriteLock --> ReentrantReadWriteLock lock不能区分读和写
// ReentrantReadWriteLock 可以区分读和写,实现更加精确的控制
// 读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写。独占
public void put(String key,String value)
// lock.lock 加锁
readWriteLock.writeLock().lock();
try
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
// 存在别的线程插队
System.out.println(Thread.currentThread().getName()+"写入完成");
catch (Exception e)
e.printStackTrace();
finally
readWriteLock.writeLock().unlock(); // lck.unlock();
// 多线程下尽量加锁!
// 读
public void get(String key)
readWriteLock.readLock().lock();
try
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取结果:"+result);
catch (Exception e)
e.printStackTrace();
finally
readWriteLock.readLock().unlock();
阻塞队列
写入:如果队列满了,就必须阻塞等待
取:如果队列是空的,必须等待生产
四组API
1)抛出异常
add()
remove();
element():查看队首的元素
package com.coding.blocking;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
public class Test1
public static void main(String[] args)
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列
// add返回布尔值
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.add("d")); // java.lang.IllegalStateException: Queue full
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove()); // a
System.out.println(blockingQueue.element());
blockingQueue.remove();
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove()); // b
System.out.println(blockingQueue.remove()); // c
System.out.println(blockingQueue.remove()); // java.util.NoSuchElementException
2)不会抛出异常
offer():添加—》返回boolean值
poll():弹出----》返回null
peek():查看队首的元素
package com.coding.blocking;
import java.util.concurrent.ArrayBlockingQueue;
// 通常!
public class Test2
public static void main(String[] args)
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// 等待(一直等待,超时就不等你)
// System.out.println(blockingQueue.offer("d")); // false 我们通常不希望代码报错!这时候就使用offer
System.out.println(blockingQueue.peek()); // 查看队首
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); // null
// System.out.println(blockingQueue.peek()); // 查看队首 null
3)阻塞等待
put(); —>当队列的长度不够时候,将会陷入阻塞
take();当没有元素时候,将陷入阻塞
package com.coding.blocking;
import java.util.concurrent.ArrayBlockingQueue;
public class Test3
public static void main(String[] args) throws InterruptedException
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列
// 一直阻塞。超过3秒我就不等了, 业务必须要做!
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take()); // 阻塞等待拿出元素
4)超时等待
blockingQueue.offer(“d”,3L,TimeUnit.SECONDS);
等3秒,没有位置就退出
blockingQueue.poll(3L,TimeUnit.SECONDS)
package com.coding.blocking;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test4
public static void main(String[] args) throws InterruptedException
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列
// 设置超时的时间
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// 超过3秒就不等待了
blockingQueue.offer("d",3L,TimeUnit.SECONDS); // 不让他一直等待,然后也不想返回false,设置等待时间
System.out.println(blockingQueue.poll()); // a
System.out.println(blockingQueue.poll()); // b
System.out.println(blockingQueue.poll()); // c
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS)); // 阻塞
同步队列 SynchronousQueue
每一个 put 操作。必须等待一个take。否则无法继续添加元素!
相当于长度只有一个
package com.coding.blocking;
import java.util.concurrent.Executor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
// 同步队列
// 每一个 put 操作。必须等待一个take。否则无法继续添加元素!
public class Test5
public static void main(String[] args)
// 不用写参数!
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(()->
try
System.out.println(Thread.currentThread().getName()+"put 1");
queue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
queue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
queue.put("3");
catch (InterruptedException e)
e.printStackTrace();
,"A").start();
new Thread(()->
try
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+queue.take());
catch (InterruptedException e)
e.printStackTrace();
,"B").start();
线程池
1)池化技术:
池化技术简单点来说,就是提前保存大量的资源,以备不时之需。在机器资源有限的情况下,使用池化
技术可以大大的提高资源的利用率,提升性能等。
2)线程池好处:
a)降低资源的消耗。
b)提高响应的速度。
c)方便管理。
它的主要特点为:线程复用,控制最大并发数,管理线程。
3)Executors工具的三大方法:
本质调用的是:ThreadPoolExecutor方法
ExecutorService threadpool1 = Executors.newFixedThreadPool(5); // 固定大小
ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以弹性伸缩的线程池,遇强则强
ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一个
a)newSingleThreadExecutor
b)newFixedThreadPool
c)newCachedThreadPool
package com.coding.pool;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Executors.
* ExecutorService.execute
*/
public class Test1
public static void main(String[] args)
// 平时我们创建一些类使用工具类操作 s
// 总数可以管理
// 线程池 Executors原生三大方法
ExecutorService threadpool1 = Executors.newFixedThreadPool(5); // 固定大小
ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以弹性伸缩的线程池,遇强则强
ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一个
try
// 10个线程,会显示几个线程~
for (int i = 1; i <= 100; i++)
// 线程池,执行线程
threadpool3.execute(()->
System.out.println(Thread.currentThread().getName()+" running...");
);
catch (Exception e)
e.printStackTrace();
finally
// 线程池关闭
threadpool3.shutdown();
4)七大参数ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时了没人调用就会释放
TimeUnit unit,//超时的单位
BlockingQueue workQueue,阻塞队列
ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
RejectedExecutionHandler handler
//拒绝策略
)
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
5)四种拒绝策略
超过最大承载:maximumPoolSize+workQueue.size()将会使用拒绝策略
// 拒绝策略说明:
// 1. AbortPolicy (默认的:队列满了,就丢弃任务抛出异常!)
// 2. CallerRunsPolicy(由调用线程(提交任务的线程)处理该任务,哪来的回哪去? 谁叫你来的,你就去哪里处理)
// 3. DiscardOldestPolicy (丢弃队列最前面的任务,然后重新提交被拒绝的任务 )
// 4. DiscardPolicy (队列满了任务也会丢弃,不抛出异常)
5)最大线程怎么设置:
a)CPU密集型 几核,就定义为几,保持CPU使用率最高
System.out.println(Runtime.getRuntime().availableProcessors());
b)IO密集型(判断你程序中十分耗IO的线程)>15
例如:程序有15个大型任务,io十分占用资源;
四大函数式接口
函数式接口:只有一个方法的接口;
只要有函数式接口,就会有lambda表达式的简化
1)函数型接口:
package com.coding.function4;
import java.util.function.Function;
public class Demo01
public static void main(String[] args)
// new Runnable(); ()->
//
// Function<String,Integer> function = new Function<String,Integer>()
@Override // 传入一个参数,返回一个结果
public Integer apply(String o)
System.out.println("into");
return 1024;
// ;
// 链式编程、流式计算、lambda表达式
Function<String,Integer> function = s->return s.length();;
System.out.println(function.apply("abc"));
2)断定型接口:有一个输入参数,返回值只能是布尔值
package com.coding.function4;
import java.util.function.Predicate;
public class Demo02
public static void main(String[] args)
// Predicate<String> predicate = new Predicate<String>()
// @Override
// public boolean test(String o)
// if (o.equals("abc"))
// return true;
//
// return false;
//
//
// ;
Predicate<String> predicate = s->return s.isEmpty();;
System.out.println(predicate.test("abced"));
3)消费型接口:只有输入,没有返回值
package com.coding.function4;
import java.util.function.Consumer;
public class Demo03
public static void main(String[] args)
// 没有返回值,只能传递参数 消费者
// Consumer<String> consumer = new Consumer<String> ()
// @Override
// public void accept(String o)
// System.out.println(o);
//
// ;
Consumer<String> consumer =s->System.out.println(s);;
consumer.accept("123");
// 供给型接口 只有返回值,没有参数 生产者
4)供给型接口:没有参数,只有返回值
package com.coding.function4;
import java.util.function.Supplier;
public class Demo04
public static void main(String[] args)
// Supplier<String> supplier = new Supplier<String>()
// @Override
// public String get()
// return "aaa";
//
// ;
Supplier<String> supplier = ()->return "aaa";;
System.out.println(supplier.get());
Stream流计算
“集合讲的是数据,流讲的是计算!”
package com.coding.stream;
import com.sun.corba.se.spi.orbutil.threadpool.WorkQueue;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
/**
* 一下数据,进行操作筛选用户:要求:一行代码做出此题,时长1分钟!
* 1、全部满足偶数ID
* 2、年龄都大于24
* 3、用户名转为大写
* 4、用户名字母倒排序
* 5、只能输出一个名字
*/
public class StreamDemo
public static void main(String[] args)
User u1 = new User(11, "a", 23);
User u2 = new User(12, "b", 24);
User u3 = new User(13, "c", 22);
User u4 = new User(14, "d", 28);
User u5 = new User(16, "e", 26);
// 集合管理数据
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// 计算交给Stream
// 过滤 filter
// 映射 map
// 排序, sort
// 分页 limit
list.stream()
.filter(u->return u.getId()%2==0;)
.filter(u->return u.getAge()>24;)
.map(u->return u.getUsername().toUpperCase();)
.sorted((o1,o2)->return o2.compareTo(o1);)
.limit(1)
.forEach(System.out::println);
// 泛型、注解、反射
// 链式编程 + 流式计算 + lambda表达式
// ForkJoinPool 执行
// ForkJoinTask
// recursive
// WorkQueue
分支合并
并行执行任务的,提高效率,大数据量;
这个里面维护的是双端队列
1)ForkJoin
a)介绍
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任
务,最终汇总每个小任务的结果得到这个大任务的结果
b)工作窃取
package com.coding.stream;
import java.util.concurrent.RecursiveTask;
// 吃糖,集中注意力, 计算的返回值类型
// 每一次痛苦都可以成长!
public class ForkJoinDemo extends RecursiveTask<Long>
private Long start;
private Long end;
private static final Long temp = 10000L; // 临界值
public ForkJoinDemo(Long start, Long end)
this.start = start;
this.end = end;
// 计算
@Override
protected Long compute()
// 如果这个数 超过中间值,就分任务计算!
if (end-start<=temp) // 正常计算
Long sum = 0L;
for (Long i = start; i <= end; i++)
sum += i;
return sum;
else
// 获取中间值
long middle = (end + start) / 2;
ForkJoinDemo right = new ForkJoinDemo(start, middle);// 第一个任务
right.fork();
ForkJoinDemo left = new ForkJoinDemo(middle+1, end);// 第一个任务
left.fork();
// 合并结果
return right.join() + left.join();
异步回调
package com.coding.stream;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
// CompletableFuture 异步回调, 对将来的结果进行结果,ajax就是一种异步回调!
public class CompletableFutureDemo
public static void main(String[] args) throws Exception
// 多线程也可以异步回调
//
// // 没有返回结果,任务执行完了就完毕了! 新增~
// CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() ->
// // 插入数据,修改数据
// System.out.println(Thread.currentThread().getName() + " 没有返回值!");
// );
//
// System.out.println(voidCompletableFuture.get());
// 有返回结果 ajax。 成功或者失败!
CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() ->
System.out.println(Thread.currentThread().getName() + " 有返回值!");
// int i = 10/0;
return 1024;
);
// 有一些任务不紧急,但是可以给时间做!占用主线程!假设这个任务需要返回结果!
System.out.println(uCompletableFuture.whenComplete((t, u) -> // 正常编译完成!
System.out.println("=t==" + t); // 正常结果
System.out.println("=u==" + u); // 信息错误!
).exceptionally(e -> // 异常!
System.out.println("getMessage=>" + e.getMessage());
return 555; // 异常返回结果
).get());
JMM:java内存模型,不存在的东西,约定
1)volitile 是 Java 虚拟机提供的轻量级的同步机制,三大特性:
a、保证可见性
b、不保证原子性
c、禁止指令重排
2)什么是JMM
JMM 本身是一种抽象的概念,并不真实存在,它描述的是一组规则或者规范~
3)JMM 关于同步的规定:
a、线程解锁前,必须把共享变量的值刷新回主内存
(线程操作变量的时候,其实是会拷贝主线程的内存到子线程工作内存中去,所以直接修改的是子线程的内存)
b、线程加锁前,必须读取主内存的最新值到自己的工作内存
c、加锁解锁是同一把锁
4)内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类
型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量
才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便
随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机
遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的
变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存
中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内
存的变量中
JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须
write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量
实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解
锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,
必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
Volatile
1)、保证可见性
volatile 读取的时候去主内存中读取在最新值!
package com.coding.jmm;
import java.util.concurrent.TimeUnit;
public class Test1
// volatile 读取的时候去主内存中读取在最新值!
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException // Main线程
new Thread(()-> // 线程A 一秒后会停止! 0
while (num==0)
).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
2)、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败。
num++
a)获得这个值
b)+1
c)写回这个值
使用原子类:(原子类底层是CAS)
a)AtomicInteger num = new AtomicInteger();
num.getAndIncrement();//+1
package com.coding.jmm;
import java.util.concurrent.atomic.AtomicInteger;
// 遇到问题不要着急,要思考如何去做!
public class Test2
private volatile static AtomicInteger num = new AtomicInteger();
public static void add()
num.getAndIncrement(); // 等价于 num++
public static void main(String[] args)
for (int i = 1; i <= 20; i++)
new Thread(()->
for (int j = 1; j <= 1000; j++)
add(); // 20 * 1000 = 20000
,String.valueOf(i)).start();
// main线程等待上面执行完成,判断线程存活数 2
while (Thread.activeCount()>2) // main gc
Thread.yield();
System.out.println(Thread.currentThread().getName()+" "+num);
3)、禁止指令重排
处理器在进行指令重排的时候,考虑:数据之间的依赖性
volatile可以避免指令重排:
内存屏障,CPU指令,作用:
a)保证特定的操作的执行顺序
b)可以保证某些变量的内存可见性
深入理解CAS
1)CAS : 比较并交换
java无法操作内存
java可以操作C++
C++可以操作内存
CAS:由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功
能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU
的原子指令,不会造成所谓的数据不一致问题。
CAS(CompareAndSwap)
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作
内存中的值一致为止。
2)CAS 应用
CAS 有3个操作数,内存值V,旧的预期值A,要修改的更新值B。且仅当预期值A 和 内存值 V 相同时,
将内存值 V 修改为B,否则什么都不做。
3)CAS 的缺点
a、循环时间长开销很大。
可以看到源码中存在 一个 do…while 操作,如果CAS失败就会一直进行尝试。
b、只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是:
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候就可以用锁来保证原子性。
Unsafe 类:java后门,可以通过这个类操作内存
package com.coding.cas;
import com.coding.demo02.A;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo
public static void main(String[] args)
AtomicInteger atomicInteger = new AtomicInteger(5);
// compareAndSet 简称 CAS 比较并交换!
// compareAndSet(int expect, int update) 我期望原来的值是什么,如果是,就更新
// a
System.out.println(atomicInteger.compareAndSet(5, 2020)+"=>"+atomicInteger.get());
// c 偷偷的改动
System.out.println(atomicInteger.compareAndSet(2020, 2021)+"=>"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 5)+"=>"+atomicInteger.get());
// b
System.out.println(atomicInteger.compareAndSet(5, 1024)+"=>"+atomicInteger.get());
4)ABA问题
右边线程对变量A的操作:经过cas(1,3)改为3,在经过cas(3,1)改为1,但是左边的线程并不知道该变量被改变了
原子引用
带版本号的原子操作
AtomicStampedReference
package com.coding.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* AtomicReference 原子引用
* AtomicStampedReference 加了时间戳 类似于乐观锁! 通过版本号
*/
public class CASDemo2
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args)
new Thread(()->
//1 、 获得版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("T1 stamp 01=>"+stamp);
try
TimeUnit.SECONDS.sleep(2);
catch (InterruptedException e)
e.printStackTrace();
atomicStampedReference.compareAndSet(100,101,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("T1 stamp 02=>"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("T1 stamp 03=>"+atomicStampedReference.getStamp());
,"T1").start();
new Thread(()->
// GIT 看到数据被动过了!
//1 、 获得版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("T1 stamp 01=>"+stamp);
// 保证上面的线程先执行完毕!
try
TimeUnit.SECONDS.sleep(3);
catch (InterruptedException e)
e.printStackTrace();
boolean b = atomicStampedReference.compareAndSet(100, 2019,
stamp, stamp + 1);
System.out.println("T2 是否修改成功:"+b);
System.out.println("T2 最新的stamp:"+stamp);
System.out.println("T2 当前的最新值:"+atomicStampedReference.getReference());
,"T2").start();
各种锁的理解
1)公平锁和非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比现申请的线程
优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
//ReentrantLock默认非公平锁
// 无参 public ReentrantLock() sync = new NonfairSync(); //
有参 public ReentrantLock(boolean fair) sync = fair ? new FairSync() : new NonfairSync();
2)可重入锁
可重入锁(也叫递归锁) 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码 * 在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口