狂神的课程还是蛮好的B站视频 多线程的马士兵也很好,就是不开源,学费有点贵,公开课讲的也挺深入的。
1.什么是JUC
java.util中关于并发的工具包。线程的三种方式:继承Thread类、实现Runnable、实现Callable接口,Callable接口有返回值、效率高。
2.线程与进程
再次复习线程与进程之前的文章中也有介绍
进程是执行中的程序,是一个动态的概念,是cpu分配和管理资源的基本单位;线程是一个轻量级的进程,是CPU调度的基本单位,一个进程可能有多个线程,java中的线程是一个程序执行过程中的一个线程实体,JVM中允许一个应用并发执行多个线程。
线程的状态
public static enum State {
NEW,//新建状态
RUNNABLE,//运行状态
BLOCKED,//阻塞态
WAITING,//等待状态,死等
TIMED_WAITING,//超时等待
TERMINATED;//终止
private State() {
}
}
3.并发与并行
并发(Concurrency):多个事件在同一时间间隔 发生,并发是多个任务之间互相抢占资源
并行(Parallelism):多个是事件在同一时刻发生,不存在多个任务抢占资源
使用一个很典型的例子:
并发:两列队伍排队在同一个窗口买咖啡,窗口交替处理事件
并行:两列队伍在两个窗口买咖啡,两个窗口同时进行
4.wait与sleep
wait 是Object类中的方法 sleep是Thread类的方法
wait会释放锁,sleep拿着锁休眠
wait()必须在同步代码块中 sleep任意位置
wait需要其他线程将其唤醒,sleep固定的睡眠时间
wait使得线程进入WAITING状态;sleep使得线程进入TIMED-WAITING状态
5.Lock锁(重要)
Synchronized与Lock
1.Synchronized是内置的关键字,Lock是一个java类
2.Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.Synchronized会自动释放锁,lock必须手动释放
4.Synchronized线程1获取,线程2等待,lock锁不一定会一直等
5.Synchronized可重入锁,不可以中断,非公平;lock锁,可重入,可以判断锁,非公平
6.Synchronized适合锁少量的代码同步问题,lock适合锁大量的同步代码块
生产者消费者(使用juc包中的锁)
面试的四个问题:单例模式、排序算法、生产者消费者、死锁
JUC中的Lock与Condiction(该对象是一个条件对象,获取等待通知的组件)
condition.await(); 线程等待 和Object类的wait方法等效
condition.signal(); 线程唤醒 和Object类的notify方法等效
condition.signalAll(); 唤醒所有等待现场呢过 和Object的唤醒是随机的
JUC生产者消费者实例:
public class ProductorAndConsumer {
public static void main(String[] args) {
//共享资源
Data data = new Data();
//四个线程,两个生产者,两个消费者
new Thread(()->{ for (int i = 0; i <30 ; i++) data.increment(); },"P1").start();//生产者线程
new Thread(()->{ for (int i = 0; i <30 ; i++) data.increment(); },"P2").start();//生产者线程
new Thread(()->{ for (int i = 0; i <30 ; i++) data.decrement(); },"C1").start();//消费者线程
new Thread(()->{ for (int i = 0; i <30 ; i++) data.decrement(); },"C2").start();//消费者线程
}
}
class Data{
//共享资源
private int number = 0;
//使用JUC包中的锁
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//生产者生产 数字+1
public void increment(){
//等待、业务、通知
try{
lock.lock();
while(number!=0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName()+"生产"+number+"个产品");
condition.signalAll();
}finally {
lock.unlock();
}
}
//消费者消费 数字-1
public void decrement(){
//等待、业务、通知
try{
lock.lock();
while(number==0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"消费第"+number+"个产品");
number--;
condition.signalAll();
}finally {
lock.unlock();
}
}
}
Condition条件执行:
要求线程A先执行,执行完成后通知B,B执行完后通知C
public class ConditionDemo {
public static void main(String[] args) {
Data01 data = new Data01();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
data.printC();
}
},"C").start();
}
}
//资源类
class Data01{
Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try{
//等待
while (number !=1){
condition1.await();
}
//业务
System.out.println("我是"+Thread.currentThread().getName());
number++;
//通知
condition2.signal();
}catch(Exception ex){
ex.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
while(number!=2){
condition2.await();
}
System.out.println("我是"+Thread.currentThread().getName());
number++;
condition3.signal();
}catch(Exception ex){
ex.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
while(number!=3){
condition3.await();
}
System.out.println("我是"+Thread.currentThread().getName());
number=1;
condition1.signal();
}catch(Exception ex){
ex.printStackTrace();
}finally {
lock.unlock();
}
}
}
关于锁的8个问题(称之为8锁)
问题1、2
public class TestLock01 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
//问题1:两个同步方法操作同一资源,在开启中间进行延迟,先发短信再打电话
new Thread(phone::msgsend,"A").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone::call,"B").start();
//问题2:两个同步方法操作同一资源,发短信方法中有延迟,先发短信再打电话
new Thread(phone::msgsend,"A").start();
new Thread(phone::call,"B").start();
}
}
class Phone{
public synchronized void msgsend(){
//第二个问题才加入
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
}
问题3、4
ublic class TestLock01 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
//问题3:两个同步方法,两个对象,先打电话再发短信
new Thread(phone::msgsend,"A").start();
new Thread(phone1::call,"B").start();
//问题4:一个同步方法,一个非同步方法,同一个对象,先hello再发短信
new Thread(phone::msgsend,"A").start();
new Thread(phone::hello,"B").start();
}
}
class Phone{
public synchronized void msgsend(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
public void hello(){
System.out.println(Thread.currentThread().getName()+"say Hello");
}
}
问题5、6
public class TestLock01 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
//问题5:两个静态代码块,同一个对象,A线程的方法有延迟,先执行A线程,再执行B线程,锁的是class对象,是同一个模板
new Thread(()->{
phone.m1();
},"A").start();
new Thread(()->{
phone.m2();
},"B").start();
//问题6:两个静态代码块,两个对象调用,一个是模板一个是new出来的真实对象,锁住的是同一个对象class对象
new Thread(()->{
phone.m1();
},"A").start();
new Thread(()->{
phone1.m2();
},"B").start();
}
}
//资源类
class Phone{
public static synchronized void m1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("static1");
}
public static synchronized void m2(){
System.out.println("static2");
}
}
问题7、8
public class TestLock01 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
//问题7:一个静态同步方法一个非静态同步方法,同一个对象 先是静态然后是发短信
new Thread(()->{
phone.msgsend(); //非静态同步有延迟
},"A").start();
new Thread(()->{
phone.m2(); //静态同步没有延迟
},"B").start();
//一个静态同步方法一个非静态同步方法,同一个对象 先执行打电话,再执行静态
new Thread(()->{
phone.m1(); //静态同步有延迟
},"A").start();
new Thread(()->{
phone.call(); //非静态同步没有延迟
},"B").start();
//问题8:一个静态同步方法一个非静态同步方法,两个对象,静态同步方法有延迟非静态同步方法没有延迟 先执行打电话,再执行静态
new Thread(()->{
phone.m1(); //静态同步有延迟
},"A").start();
new Thread(()->{
phone1.call(); //非静态同步没有延迟
},"B").start();
//一个静态同步方法一个非静态同步方法,两个对象,非静态同步方法有延迟静态同步方法没有延迟 再执行静态,先执行发短信
new Thread(()->{
phone.msgsend(); //非静态同步有延迟
},"A").start();
new Thread(()->{
phone1.m2(); //静态同步没有延迟
},"B").start();
}
}
class Phone{
//非静态同步方法(有延迟)
public synchronized void msgsend(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
//非静态同步方法(没有延迟)
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
//静态同步方法(有延迟)
public static synchronized void m1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("static1");
}
//静态同步方法(没有延迟)
public static synchronized void m2(){
System.out.println("static2");
}
}
#### 6.集合类不安全
ArrayList线程不安全,解决方案如下
public static void main(String[] args) {
/*
* ArrayList()不安全的类解决方案有
* 1.使用Vector类
* 2.使用集合工具类Collections.sychronizedList(List)
* 3.使用JUC的包:new CopyOnWriteArrayList<>()
* */
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
Vector底层使用的Synchronized同步方法
CopyOnWriteArrayList底层使用的是ReentrantLock,使用的lock锁效率高
Set不安全
public static void main(String[] args) {
//方式一:使用工具类将其转换成安全的
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
//方式二:使用JUC中的类
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();
}
} public static void main(String[] args) {
//方式一:使用工具类将其转换成安全的
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
//方式二:使用JUC中的类
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();
}
}
HashSet底层:
//底层是一个hashMap
public HashSet() {
this.map = new HashMap();
}
//HashSet的add方法,是一个map添加值的方式
public boolean add(E var1) {
return this.map.put(var1, PRESENT) == null;
}
Map
HashMap:初始大小为1<<4,大小为16,加载因子0.75,线程不安全
public static void main(String[] args) {
//1.集合工具类
// Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
//2.使用JUC并发的Map类
Map<String,String> map = new ConcurrentHashMap();
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();
}
}
ConcurrentHashMap原理
存储数据的静态内部类Node以及子类的关系,本质相当于一个链表
最大容量1<<30 1073741824
private static final int MAXIMUM_CAPACITY = 1073741824
默认表的大小 16
private static final int DEFAULT_CAPACITY = 16
最大数组的大小 Integer.MAX_VALUE-8
static final int MAX_ARRAY_SIZE = 2147483639
默认的并发数: 16
private static final int DEFAULT_CONCURRENCY_LEVEL = 16
负载因子为 0.75F (float)
private static final float LOAD_FACTOR = 0.75F
转换成红黑树的阈值为 8
static final int TREEIFY_THRESHOLD = 8
红黑树转链表阈值为 6
static final int UNTREEIFY_THRESHOLD = 6
转换为红黑树表的最小容量为 64
static final int MIN_TREEIFY_CAPACITY = 64
每次转移的最小值为 16
private static final int MIN_TRANSFER_STRIDE = 16;
7.Callable
类似于Runnable,但其可以有返回值、可以抛出异常、方法不同重写call(),FutureTask是Runnable的一个实现类
public class CallAbleDemo {
public static void main(String[] args) {
MyCall myCall = new MyCall();
//使用Runnable接口的一个子类进行适配操作
FutureTask<String> futureTask = new FutureTask<String>(myCall);
try {
new Thread(futureTask).start();
//使用get方法可能会导致阻塞
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyCall implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(5*16*16+2*16+2);
return "522";
}
}
使用FutureTask结果会有缓存
get方法可能需要等到,会阻塞
8.常用辅助类(必须会)
CountDownLatch、CyclicBarrier、Semaphone
1.CountDownLatch
//门闩 CountDownLatch 类似于减法计数器
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go out");
//线程每次调用就会减1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//计数器为0时往下执行
countDownLatch.await();
System.out.println("clored");
}
2.CyclicBarrier
public static void main(String[] args) throws InterruptedException {
// 类似于加法计数器 CyclicBarrier
CyclicBarrier cyclic = new CyclicBarrier(7,()->{
System.out.println("已经到达7了,冲冲冲");
});
for (int i = 1; i <= 7; i++) {
//线程中无法获取i,定义临时变量
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"获取"+temp+"个宝物");
try {
cyclic.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
3.Semaphone 信号量
public static void main(String[] args) throws InterruptedException {
//信号量 Semaphone 使用停车位小例子:三个车位6辆车
//acquire()
//release()
Semaphore semap = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
//线程获取资源,获取后信号量-1
semap.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//线程释放资源,信号量+1
semap.release();
}
},String.valueOf(i)).start();
}
9.读写锁
ReadWriteLock 是一个读写锁的接口
public class ReadWriteDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写线程
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 MyCacheLock{
private volatile Map<String ,Object> map = new HashMap();
//读写锁,锁的粒度比较细
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存储数据
public void put(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入ok");
}catch (Exception ex){
ex.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
//获取数据
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"获取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"获取ok");
}catch(Exception ex){
ex.printStackTrace();
}finally {
readWriteLock.readLock().lock();
}
}
}
10.阻塞队列
队列先入先出的特性,产生阻塞的场景是:当队列存满时,队列存储数据时会阻塞;当队列为空时,拿取数据会阻塞
常用的阻塞队列
接口BlockingQueue 与List、Set同一级别,同样继承Collection接口,其实现类有:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue(同步队列)
阻塞队列的api
方式 | 抛出异常 | 不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add(元素) | offer(元素) | put(元素) | offer(元素,时间,时间单位) |
移除 | remove() | poll() | take() | poll(时间长度,时间单位) |
判断队首元素 | element() | peek() | — | —- |
四种情况的例子:
//1.存在异常情况
public static void test01(){
//创建大小为3的队列
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//添加的元素超出队列大小或抛出异常
// System.out.println(blockingQueue.add("d"));
System.out.println("=====================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// 从队列中拿取元素时,队列为空抛出异常
// System.out.println(blockingQueue.remove());
}
//2.没有异常情况
public static void test02(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//大于队列的元素添加返回值为false
System.out.println(arrayBlockingQueue.offer("d"));
System.out.println("=================");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//对于空数组返回值为null
System.out.println(arrayBlockingQueue.poll());
}
//3.阻塞等待
public static void test03(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
try {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//队列已经满了,当前元素插入时阻塞
// blockingQueue.put("d");
System.out.println("=========");
//获取时,没有元素时阻塞
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
//队列为空时,获取元素时阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//4.超时等待
public static void test04(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
try {
System.out.println(blockingQueue.offer("a", 2, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 2, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c", 2, TimeUnit.SECONDS));
//当前队列已经满了,等待两秒后插入,如果能够插入返回true否则丢弃
System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));
System.out.println("==============");
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
//当前队列为空,等待两秒后有元素返回该元素,没有时返回null
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
同步队列 SynchronousQueue队列
public static void main(String[] args) {
BlockingQueue synqueue = new SynchronousQueue();//同步队列
//开启线程1向同步队列中放置元素,放置后只有当另一个线程将元素拿走后才能继续放置元素
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1 ");
synqueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2 ");
synqueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3 ");
synqueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
//开启线程2向同步队列中拿取元素
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==>"+synqueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==>"+synqueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==>"+synqueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
11.线程池(重点)
池化技术:占用系统资源,优化资源的使用,池化技术,例如线程池、连接池、内存池、对象池。线程池的三大方法、七大参数、四种拒绝策略。
个人推荐美团的技术文档很完美:美团多线程
阿里开发手册中对线程池的规范
三大方法(不推荐使用该方式创建线程池)
//单个线程的线程池
ExecutorService threadPool =Executors.newSingleThreadExecutor();
//固定数量的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//可扩充的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
七大参数
1.核心线程池大小
2.最大核心线程池大小
3.超时没有调用释放
4.超时单位
5.阻塞队列
6.线程工厂:创建线程
7.拒绝策略
//7个参数int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8
//核心线程池大小、最大线程数、超时回收时间、时间单位、阻塞队列(类似于银行的等候区)、线程工厂、拒绝策略
ExecutorService threadPool = new ThreadPoolExecutor(3,5,
3, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()
);
四种拒绝策略
策略 | 描述 |
---|---|
AbortPolicy | 直接异常结束,组织系统正常运行 |
CallerRunsPolicy | 在调用者线程中,运行当前被丢弃的任务,并不是真的丢弃,但是,任务提交线程会降低性能 |
DiscardOldestPolicy | 丢弃最老的请求 |
DiscardPolicy | 该策略是默默丢弃无法处理的任务,不进行处理 |
最大线程数如何确定?(调优考虑)
cpu密集型和IO密集型
根据cpu核数:Runtime.getRuntime().availableProcessors();
IO密集型:指的是任务中有多个IO相关的大型任务,可以设置为2n测试。
12.函数式接口(重点)
目前的编程风格:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口是只有一个方法的接口,java.util.function包下有四大原生的函数式接口
Consumer接口 消费型接口 只输入不返回
public static void main(String[] args) { Consumer<String> son = (str)->{ System.out.println(str); }; son.accept("消费型接口"); }
Supplier接口 供给型接口 只返回没有参数输入
public static void main(String[] args) { Supplier<String> sup = ()->{return "1024程序员节日";}; System.out.println(sup.get()); }
Function接口 Function<T, R> T为传入的参数 R为返回的参数
public static void main(String[] args) { Function fun = (o)->{return ""+o;}; System.out.println(fun.apply("heihei")); }
Predicate接口 断定式接口,返回一个布尔类型的值
public static void main(String[] args) { Predicate<String> fun = (str)-> {return str.isEmpty();}; System.out.println(fun.test("")); }
函数式接口为了简化程序,可读性或许不是很好,但是以后的发展趋势,java的泛型、枚举、反射在很早之前就很流行应该掌握,流式编程、lambda表达式、链式编程、函数式编程最近的新特性也需要好好学习。
13.Stream流式计算
Stream流式计算,Stream中的方法大部分传入的参数是一个函数式的接口
例如:Stream
filter函数中传入一个断定式接口,返回一个布尔类型的值,用于判断
public static void main(String[] args) {
User u1 = new User(2,"a",25);
User u2 = new User(4,"b",22);
User u3 = new User(6,"c",23);
User u4 = new User(8,"d",24);
User u5 = new User(0,"e",21);
//集合用于存储数据
List<User> users = Arrays.asList(u1, u2, u3,u4,u5);
//需求,从集合中找出年龄大于23的学生、id为偶数、name反着排序并转换成大写,输出1个用户
users.stream()
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((o1,o2)->{return o2.compareTo(o1);})
.limit(1)
.forEach(System.out::println);
}
要想学好流式编程首先对函数式接口必须掌握。
14.ForkJoin
ForkJoin并行执行任务提高效率,使用在大数据量的情况,特点是工作窃取
工作窃取:对于分配的子任务,A线程执行到中间位置,B线程任务已经执行结束,B线程不会是一个等待或者阻塞的状态,会将A线程剩余的任务执行,称之为工作窃取。
使用ForkJoin,应该使用ForkJoinPool 和ForkJoinTask,其中ForkJoinTask中有递归事件和递归任务,递归事件没有返回值,递归任务有返回值。
对于一个超级大的数字求和,三种方式,由于计算机硬件的限制ForkJoin的结果不如传统的方式
ForkJoin
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp = 1000000L;
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 mid = (start+end)/2;//分解任务,将大任务分解为自定义的小任务
ForkJoinDemo task1 = new ForkJoinDemo(start,mid);
task1.fork();//拆分任务,将任务放入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(mid+1,end);
task2.fork();
return task1.join()+task2.join();
}
}
}
三种方式计算求和以及时间对比,流式编程需要加强。
//传统的方式
public static void test01(){
Long sum = 0L;
Long start = System.currentTimeMillis();
for (int i = 1; i <= 10_0000_0000L ; i++) {
sum+=i;
}
Long end = System.currentTimeMillis();
System.out.println("sum ="+sum+"时间:"+(end-start));
}
//forkjoin的方式
//传统的方式
public static void test02() throws ExecutionException, InterruptedException {
Long sum = 0L;
Long start = System.currentTimeMillis();
//首先依赖ForkJoinPool
ForkJoinPool forkJoin = new ForkJoinPool();
ForkJoinTask<Long> task= new ForkJoinDemo(0L,10_0000_0000L);
// forkJoin.execute(task);//执行任务 没有返回值
ForkJoinTask<Long> submit = forkJoin.submit(task);//提交任务
sum = submit.get();
Long end = System.currentTimeMillis();
System.out.println("sum ="+sum+"时间:"+(end-start));
}
//并行流的方式,一行代码完成十亿的相加
public static void test03(){
Long sum = 0L;
Long start = System.currentTimeMillis();
sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
Long end = System.currentTimeMillis();
System.out.println("sum ="+sum+"时间:"+(end-start));
}
15.异步回调
Future接口
没有返回值的CompletableFuture的使用,在其泛型中声明为Void
private static void noReturn() {
//发起一个请求
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync==>Void");
});
System.out.println("1111111111111");
try {
completableFuture.get();//获取阻塞执行结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
又返回值的CompletableFuture的使用,在其泛型中声明返回值类型
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync==>return");
// int i = 1/0;
return 1024;//当成功调用时,返回的正确信息
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>"+t);
System.out.println("u=>"+u);
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 233;//异步回调,当出现异常时,返回的错误值
}).get());
}
//消费型函数式接口的子类,可以传入两个参数没有返回值
//@FunctionalInterface
//public interface BiConsumer<T, U> {
// void accept(T var1, U var2);
//}
16.JMM
jmm是java的内存模型,是一个逻辑上的概念,不存在的,其中有EMSI缓存一致性性协议,将共享变量的值刷新回主内存中,cpu的缓存是一级、二级缓存存在于cpu的核中,三级缓存为共享的缓存,M(Modified)、E(exclusive)、S(shared)、I(invalid)
Modified:当前cpu缓存拥有最新数据,其他cpu拥有失效数据,以当前cpu数据为准
exclusive:只有当前cpu有数据,其他cpu没有修改数据,当前cpu数据和主存的数据是一致的
shared:当前cpu和其他cpu都有公共数据,并且和主存中的数据一致
invalid:当cpu数据失效,数据应该从主存中获取,其他cpu中可能有数据也可能无数据,当前cpu中的数据和主存是不一致的
Volatile关键字是java虚拟机提供轻量级的同步机制,保证可见性不保证原子性,禁止指令重排序。
内存交互的8个原子操作
lock (锁定):作用于主内存的变量,把一个变量表示为线程独占状态
unlock(解锁):作用于主内存的变量,将一个锁定的变量释放
read(读取):作用于主内存,将一个变量从主内存读取到工作内存中,提供给load
load(载入):作用于工作内存,将read操作的变量放入工作内存
use(使用):作用于工作内存的变量,将工作内存的变量传输给执行引擎
assign(赋值):作用于工作内存,将一个执行引擎中接受的值放入工作内存的变量副本中
store(存储):将工作内存中的值传送到主内存中,便于write使用
write(写入):作用于主内存中,将store操作的变量值放入主内存的变量中
规则
1.read和load、store和write操作不可单一出现,使用了read必须使用load,使用了store必须使用write
2.不允许现场呢过丢弃其最近的assign操作,即工作变量的数据改变之后,必须告知主内存
3.不允许一个线程将没有assign的数据从工作内存同步回主内存
4.一个新的变量必须在主内存产生,不允许一个工作内存直接使用一个未初始化的变量,一个变量在use和store之前必须经过assign和load
5.一个变量同一时间只有一个线程能对其进行lock,多次lock后必须执行相同次数的unlock
6.如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用该变量前,必须重新load或者assign操作初始化变量的值
7.如果一个变量没有被lock,就不能对其进行unlock操作,也不能unlock一个被其他线程锁住的变量
8.对一个变量进行unlock操作之前,必须把此变量同步到主内存
volatile可见性验证,对于一个线程使其对共享变量循环,另外一个线程改变共享变量让其结束循环
public class ViewSionDemo {
//没有volatile关键字,线程不会停止
private volatile static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while (num<1){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
Volatile不是原子操作的,只是保证可见性和指令重排序
public class VolatileDemo {
//volatile 保证可见性 禁止指令重排
private volatile static int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
},String.valueOf(i)).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
//synchronized能够保证原子性
private static void add() {
num++;
}
}
因此想要保证原子操作,可以使用前面学过的synchronized或者Lock锁解决,但是在JUC中提供了原子基本类,可以进行累加。
//将共享变量使用原子类进行操作
private static AtomicInteger num = new AtomicInteger(0);
num.getAndIncrement();
指令重排序:源代码—>编译器重排—->指令并行也可能重新排序——>内存系统重排——>执行 volatile指令重排序底层是内存屏障实现的,在关键字变量前后禁止指令交换顺序。
17.单例模式(会手写)
饿汉式
public class HungryDemo {
private final static HungryDemo HUNGRY = new HungryDemo();
private HungryDemo(){}
public static HungryDemo getInstance(){
return HUNGRY;
}
}
双检锁,懒汉式
public class LazyDemo {
private LazyDemo(){}
//volatile关键字必须要有,进制指令重排序
private volatile static LazyDemo lazy;
//并发情况下不安全
public static LazyDemo getInstance(){
if(lazy==null)
{
synchronized (LazyDemo.class){
//new对象时不时一个原子性的操作,对象的创建过程分为三步
//1.分配内存空间
//2.执行构造方法,初始化对象
//3.将对象指向内存空间
if(lazy==null)lazy = new LazyDemo();
}
}
return lazy;
}
}
懒汉式单例,静态内部类方式
public class HolderDemo {
//私有化构造器
private HolderDemo(){}
//调用该方法的时候才加载
public static HolderDemo getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final HolderDemo HOLDER = new HolderDemo();
}
}
当然所有的单例模式策略都不是绝对的安全,只是相对安全。反射破坏双检锁的单例
public static void main(String[] args) throws Exception {
LazyDemo instance1 = LazyDemo.getInstance();
//利用反射技术获取构造函数并修改权限,重新实例化对象
Class<LazyDemo> lazyDemoClass = LazyDemo.class;
Constructor<LazyDemo> declaredConstructor = lazyDemoClass.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyDemo instance2 = declaredConstructor.newInstance();
//创建出两个对象
System.out.println(instance1);
System.out.println(instance2);
}
构造器中再验证,防止反射破坏:
public class LazyDemo {
private static boolean flag = false;
private LazyDemo(){
if(!flag) flag = true;
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
//volatile关键字必须要有,进制指令重排序
private volatile static LazyDemo lazy;
//并发情况下不安全
public static LazyDemo getInstance(){
if(lazy==null)
{
synchronized (LazyDemo.class){
//new对象时不时一个原子性的操作,对象的创建过程分为三步
//1.分配内存空间
//2.执行构造方法,初始化对象
//3.将对象指向内存空间
if(lazy==null)lazy = new LazyDemo();
}
}
return lazy;
}
}
最好的单例模式使用枚举
public enum EnumSingle {
INSTANCE;
}
枚举实际底层也是一个类,也有自己的构造器,采用java的命令反编译文件可以看到,枚举类中是一个空参的构造器
javap -p EnumSingle.class
枚举是不能通过反射创建对象的,从Enum类中的newInstance方法可以看出,使用反射类型创建实例对象时,会产生异常提示
Cannot reflectively create enum objects
采用反射试图创建枚举类的实例对象
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//枚举类对象
EnumSingle instance = EnumSingle.INSTANCE;
//反射获取字节码对象,获取构造器、修改构造器权限,创建对象
Class<EnumSingle> enumSingleClass = EnumSingle.class;
Constructor<EnumSingle> constructor = enumSingleClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
EnumSingle instance2 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
使用反射获取枚举类型的结果是异常,但是异常信息与底层源码中的异常信息不相符合
反编译工具jad,下载地址jad下载 反编译后文件如下,枚举类继承了java中的Enum类,其中的构造函数不是一个默认的空参构造器,而是一个两个参数的构造器。
将反射测试总的构造器转换成有参数传递的构造器,再进行相关的测试,得到和源码中相同的异常提示。
Constructor
constructor = enumSingleClass.getDeclaredConstructor(String.class,int.class);
18.CAS
CAS(乐观锁的态度):compareAndSet,比较并交换,CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量(内存值),E表示预期值(旧值),N表示新值。当且仅当V值等于E值时,将V修改为N,V与E不同时,说明有线程进行了修改,什么都不做。
ABA问题:
CAS会产生ABA问题,当一个线程准备将A改成C,另一个线程将A改成B后再修改成A,当时对于第一个线程不知道中间的过程,因此会修改数据,一般来说是不允许的。通常ABA问题的解决是通过版本号或者时间戳来判断,每一次修改都会使得版本号+1
JUC中的原子引用,AtomicStampedReference类对ABA问题进行模拟与解决。
public static void main(String[] args) {
//创建带有时间戳的原子引用对象,初始值为2020,初始的版本号是1
//泛型是一个包装类,需要注意对象的引用问题
AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(13,1);
new Thread(()->{
//获取当前信息的版本号
int stamp = atomic.getStamp();
System.out.println("a1==>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomic.compareAndSet(13, 14, atomic.getStamp(), atomic.getStamp() + 1));
System.out.println("a2==>"+atomic.getStamp());
atomic.compareAndSet(14,13,atomic.getStamp(),atomic.getStamp()+1);
System.out.println("a3==>"+atomic.getStamp());
},"a").start();
new Thread(()->{
//获取当前信息的版本号
int stamp = atomic.getStamp();
System.out.println("b1==>"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//对于ABA问题,采用版本号或者时间戳进行控制,第一个线程将13改为14再改回13,第二个线程依旧能够发现并修改失败
System.out.println(atomic.compareAndSet(13, 17, stamp, stamp+ 1));
System.out.println("b2==>"+atomic.getStamp());
},"b").start();
}
注:Inter包装类使用了对象的缓存机制,默认范围为-128~127之间,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,new一定产生新的对象分配新的内存空间。
阿里官方文档第7页左右对Integer包装类进行说明,是一个大坑。
19.各种锁的理解
A.公平锁、非公平锁
公平锁:非常公平,不能够插队,先来后到
非公平锁:非常不公平,谁能抢到算谁的
//默认锁为非公平锁
public ReentrantLock() {
this.sync = new ReentrantLock.NonfairSync();
}
//自定义公平与不公平,true表示公平,false表示不公平
public ReentrantLock(boolean var1) {
this.sync = (ReentrantLock.Sync)(var1 ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
}
B.可重入锁
又称为递归锁,可以多次锁一个对象,进入多少次解锁也需要多少次,synchronized和ReentrantLock都为可重入锁,两者之间的异同点:
共同点:
都是用来协调对象对共享对象、变量的访问
都是可重入的锁,同一个线程可以多次获取同一个锁
都保证了可见性和互斥性
不同点:
1.ReentrantLock显示的获取和释放锁;synchronized隐式的获取与释放锁
2.ReentrantLock可中断、可轮回;synchronized不可响应中断
3.ReentrantLock是API级别的;synchronized是JVM级别的
4.ReentrantLock实现类公平锁、可以使用Condition类绑定条件
5.ReentrantLock的可重入实现是基于Volatile变量state和CAS来实现的;Synchronized可重入是依赖对象头部中markword中记录锁的持有者和计数器,每次重入计数器+1操作,退出时计数器-1
C.自旋锁
使用CAS自定一个锁
public class AutomicSpinlock {
//原子引用
AtomicReference<Thread> atomic = new AtomicReference<>();
//加锁
public void mylock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===>mylock");
//自旋锁
while(!atomic.compareAndSet(null,thread)){
}
}
//解锁
public void myunlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===>mylock");
//自旋锁
atomic.compareAndSet(thread,null);
}
}
测试锁
public static void main(String[] args) {
AutomicSpinlock lock = new AutomicSpinlock();
//自定义的锁,第一个线程获取到锁后第二个线程等待第一个线程释放锁后在能进行修改操作
new Thread(()->{
lock.mylock();
try{
TimeUnit.SECONDS.sleep(2);
}catch (Exception ex){
ex.printStackTrace();
}finally {
lock.myunlock();
}
},"线程1").start();
new Thread(()->{
lock.mylock();
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception ex){
ex.printStackTrace();
}finally {
lock.myunlock();
}
},"线程2").start();
}
D.死锁
再谈死锁,两个线程之间相互争夺资源,且相互不放弃。
死锁的四个必要条件
互斥条件:一个资源只能被一个进程使用
请求与保持:一个进程因为请求资源而阻塞时,对以获取的资源保持不放
不剥夺条件:进程已获取的资源,未使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成头尾相接的循环等待资源的关系
死锁的例子
public class DeathLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new Mythread(lockA,lockB)).start();
new Thread(new Mythread(lockB,lockA)).start();
}
}
class Mythread implements Runnable{
private String lockA;
private String lockB;
public Mythread(String lockA,String lockB){
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock=>"+lockA+"=>get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock=>"+lockB+"=>get"+lockA);
}
}
}
}
死锁的排查:
1.bin目录中有jps定位进程号
jps -l
查看进程号
找到对应的进程后查看堆栈信息
jstack 进程号
课程:秦老师的课程真的质量很高的,推荐学习java的学生学习狂神B站地址