Java进阶-JUC篇

高并发

准备工作:导入maven依赖包

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

第二步,将project里的modules的language level改为jdk8

第三步,将javaCompile改为jdk8

1.什么是JUC

java.util.concurrent 并发

java.util.concurrent.atomic 原子性

java.util.concurrent.locks

业务:普通的线程代码 Thread

Runnable:没有返回值,效率相比于Callable相对较低

2.线程和进程

一个进程可以有多个线程,且至少包含一个。

java默认线程:main ,gc线程,三种开启线程方式:Thread,Runnable,Callable

java是不能真正的开启多线程,源码中是调用了本地native方法,底层C++,java是作用在虚拟机上的,无法直接操作硬件

2.1 并发和并行

并发:多线程操作同一个资源,cpu单核模拟多个线程

并行:多个人一起行走,cpu多核,多个线程可以同时执行,线程池

//获取cpu的核数
//cpu密集型,IO密集型
Runtime.getRuntime().availableProcessors();

并发的本质:充分利用cpu的资源

线程状态:

public enum State {
   //线程新生
    NEW,
   //运行
    RUNNABLE,
   //阻塞
    BLOCKED,
   //等待,死等
    WAITING,
   //超时等待
    TIMED_WAITING,
    //终止
    TERMINATED;
}

wait/sleep区别:

1.来自不同的类,wait来自obj类,sleep来自Thread类

2.wait会释放锁,sleep不会释放锁

3.使用范围不同:wait必须在同步代码块中,sleep无限制

3.Lock锁

Synchronized同步锁,关键字,本质:排队,锁

并发:多线程操作同一个资源类,把资源类丢进线程里,Runnable函数式接口可以new被成为匿名内部类,但繁琐,所以用lambda表达式来代替()->{}

new Thread(()->{},"name").start;

Interface Lock:

实现类:ReentrantLock可重入锁,ReentrantReadWriteLock.ReadLock读锁,eentrantReadWriteLock.WriteLock写锁

在这里插入图片描述

公平锁:十分公平,先来后到

非公平锁:十分不公平,可以插队(默认)

Lock:手动加锁,手动释放锁

Synchronized和Lock锁的区别:

1.Synchronized是关键字,Lock是java类

2.Synchronized无法判断获取锁的状态,Lock可以判断是否获取锁

3.Synchronized会自动释放锁,Lock手动释放锁

4.Synchronized线程1获得锁,阻塞;线程2等待,Lock锁会trylock()抓取锁

5.Synchronized可重入锁,非公平锁,不可中断的,Lock锁可重入锁,可以中断,公平锁

6.Synchronized锁少量的代码块,Lock锁大量的代码块

生产者消费者问题

步骤:判断等待,业务,通知

面试:单例模式,排序算法,生产者消费者,死锁

线程也可以唤醒,而不会通知,中断或者超时即所谓的虚假唤醒,等待应该总是出现在while循环中

JUC版的生产者和消费者问题

在这里插入图片描述

Lock替代synchronized方法和语句的使用,Condition取代了对象

Condition方法:

condition.await();
condition.signalAll();

package com.liu.demo1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//生产者消费者问题
public class test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }

}
//等待--业务--通知
class Data{

    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
        /*
        condition.await();
        condition.signalAll();
         */

    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number!=0){
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程+1完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程-1完毕
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Condition精准通知和唤醒线程

package com.liu.demo1;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//生产者消费者问题
public class test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.printA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.printB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.printC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.printD();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }

}
//等待--业务--通知
class Data{

    private int number = 1;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    Condition condition4 = lock.newCondition();
        /*
        condition.await();
        condition.signalAll();
         */

    public void printA() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number=2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB() throws InterruptedException {
        lock.lock();
        try {
            while (number!=2){
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number=3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() throws InterruptedException {
        lock.lock();
        try {
            while (number!=3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number=4;
            condition4.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printD() throws InterruptedException {
        lock.lock();
        try {
            while (number!=4){
                //等待
                condition4.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

4.八锁现象

1.执行问题

package com.liu.lock8;

import java.util.concurrent.TimeUnit;
/*
8锁就是8个问题
1.两个线程,总是A先执行,再B执行,是由于锁的原因,synchronized方法锁的对象是方法的调用者,谁先拿到锁谁执行!两个方法用的同一把锁!
2.两个线程,不管方法1睡眠多久,都是方法一先执行,还是方法一先拿到锁!
 */
public class test {
    public static void main(String[] args) throws InterruptedException {
        phone phone = new phone();
        new Thread(()->{
            phone.sendMessage();
        },"A").start();

        //线程睡眠用JUC的TimeUnit枚举类
        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class phone{

    public synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
package com.liu.lock8;

import java.util.concurrent.TimeUnit;

/*
3.增加一个普通方法后,如果加了锁的方法和普通方法睡眠时间相同,都是锁的方法先执行,如果锁的方法睡眠时间长,就是普通方法先执行!
4.有两个对象,都执行同步方法,睡眠时间短的就先执行!
 */
public class test2 {
    public static void main(String[] args) throws InterruptedException {
        phone phone1 = new phone();
        phone2 phone2 = new phone2();
        new Thread(()->{
            phone1.sendMessage();
        },"A").start();

        //线程睡眠用JUC的TimeUnit枚举类
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}
class phone2{

    public synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    //不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

package com.liu.lock8;

import java.util.concurrent.TimeUnit;

/*
5.静态同步方法,只有一个对象,方法一先执行,phone3.class全局唯一对象,static在类加载的时候就执行了,Class模板,锁的是class
6.两个对象,还是静态模块的先后顺序执行,锁的是class!
 */
public class test3 {
    public static void main(String[] args) throws InterruptedException {
        phone3 phone2 = new phone3();
        phone3 phone3 = new phone3();
        new Thread(()->{
            phone2.sendMessage();
        },"A").start();

        //线程睡眠用JUC的TimeUnit枚举类
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            phone3.call();
        },"B").start();

    }
}
class phone3{

    public static synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}

package com.liu.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 7.一个静态同步方法,一个普通同步方法,两者锁的对象不同,一个锁的是class模板,一个锁的是方法调用者,谁睡眠时间短谁先执行!
 * 8.两个对象,两个同步方法,还是方法一先执行,锁的对象不同,睡眠时间短的先执行!
 */
public class test4 {
    public static void main(String[] args) throws InterruptedException {
        phone4 phone4 = new phone4();
        phone4 phone5 = new phone4();
        new Thread(()->{
            phone4.sendMessage();
        },"A").start();

        //线程睡眠用JUC的TimeUnit枚举类
        TimeUnit.SECONDS.sleep(3);

        new Thread(()->{
            phone5.call();
        },"B").start();

    }
}
class phone4{

    //锁的class模板
    public static synchronized void sendMessage(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发信息");
    }
    //锁的是调用者
    public synchronized void call(){
        System.out.println("打电话");
    }
}

5.集合类不安全

并发下的ArrayList不安全,会报错:java.util.ConcurrentModificationException,解决方法:

  • 1.Vector objects = new Vector<>();
  • 2.Collections.synchronizedList(new ArrayList<>());工具类变成安全的
  • 3.JUC下的解决方案: List list1 = new CopyOnWriteArrayList<>();
  • 写入时复制,计算机程序设计的一种优化策略,在写入的时候避免覆盖,造成数据问题,读写分离
  • 底层就是新复制了一个数组再插入进去,效率高,因为没有用synchronized,用的lock
package com.liu.unsafe;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;

public class test {

    public static void main(String[] args) {

        /**并发下的ArrayList不安全,会报错:java.util.ConcurrentModificationException,解决方法:
         * 1.Vector<Object> objects = new Vector<>();
         * 2.Collections.synchronizedList(new ArrayList<>());工具类变成安全的
         * 3.JUC下的解决方案: List<String> list1 = new CopyOnWriteArrayList<>();
         * 写入时复制,计算机程序设计的一种优化策略,在写入的时候避免覆盖,造成数据问题,读写分离
         * 底层就是新复制了一个数组再插入进去,效率高,因为没有用synchronized,用的lock
         */

        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();
        }
    }
}

package com.liu.unsafe;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

public class SetTest {
    //报错:ConcurrentModificationException
    /*解决方案:
    1.将线程变成安全的,用Collections工具类
    2.JUC方法:Set<String> set = new CopyOnWriteArraySet<>();
     */
    public static void main(String[] args) {
//        Set<String> set = Collections.synchronizedSet(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();

        }
    }
}

Set的底层源码:就是hashmap

public HashSet() {
        map = new HashMap<>();
    }
//add set 本质是map key是无法重复的
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

HashMap:

package com.liu.unsafe;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest {
    public static void main(String[] args) {
//        Map<String,String> map =  new HashMap<>();
       /*
       工作中不用HashMap
       默认等价于 new HashMap<>(16,0.75);
       加载因子 初始化容量 16*0.75=12,超过12就扩容
        */
        /**解决线程不安全的方案
         *Map<String,String> map = new ConcurrentHashMap<>();
         */
        /*
        hashmap和ConcurrentHashMap的区别:
        1.HashMap是线程不安全的,ConcurrentHashMap是线程安全的
        2.ConcurrentHashMap将整个hash桶进行了分段segment,每个分段上都有锁存在,当一个线程访问其中一个数据片段时,其他的数据也能够被其他线程访问。
        3.ConcurrentHashMap让锁的力度更精细,并发性能更好
        4.ConcurrentHashMap不能存储null键值对,HashMap可以存储null键值对
         */
        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();
        }
    }
}

HashMap与JUC里的ConcurrentHashMap区别:

hashmap和ConcurrentHashMap的区别:
1.HashMap是线程不安全的,ConcurrentHashMap是线程安全的
2.ConcurrentHashMap将整个hash桶进行了分段segment,每个分段上都有锁存在,当一个线程
访问其中一个数据片段时,其他的数据也能够被其他线程访问。
3.ConcurrentHashMap让锁的力度更精细,并发性能更好
4.ConcurrentHashMap不能存储null键值对,HashMap可以存储null键值对

6.Callable

函数式接口,类似于Runnable,Runnable不返回结果,也不能抛出被检查的异常,而Callable需要返回结果,也要抛出异常

package com.liu.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();和上面一个一样,只是一个适配类
        //new Thread(new FutureTask<V>(Callable)).start(); 启动callable

        MyThread thread= new MyThread();
        FutureTask futureTask = new FutureTask(thread);//适配类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//结果会被缓存,效率高

        Integer o = (Integer) futureTask.get();//获取callable的返回结果,需要等待,可能会产生阻塞
        System.out.println(o);
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

7.常用辅助类

7.1 CountDownLatch

减法计数器

countDownLatch.countDown();//数量-1

countDownLatch.await();//等到计数器归零,再向下执行

package com.liu.add;

import java.util.concurrent.CountDownLatch;

//计数器
public class CountDownLatchTest {
    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()+" go out");
                countDownLatch.countDown();//数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//等到计数器归零,再向下执行

        System.out.println("close!");
    }
}

7.2 CyclicBarrier

cyclicBarrier.await();//等待计数器变成7

加法计数器

package com.liu.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("成功");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            //lambda表达式拿不到i,所以定义一个中间变量
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 收集 "+ temp +" 个");
                try {
                    cyclicBarrier.await();//等待计数器变成7
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

7.3 Semaphore

信号量

semaphore.acquire();获得,如果已经满了,等待,等待被释放完为止

semaphore.release();//释放,会将当前的信号量释放+1,然后唤醒等待的线程

作用:多个共享资源互斥的使用,并发限流,控制最大的线程数。

package com.liu.add;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreTest {

    public static void main(String[] args) {
        //限流
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                //acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+" 抢到");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+" 离开");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }

    }


}

8.读写锁ReadWriteLock

  • 独占锁(写锁):一次只能被一个线程占用
  • 共享锁(读锁):多个线程可以同时占有
  • 读写锁:ReadWriteLock = new ReentrantReadWriteLock();
package com.liu.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**独占锁(写锁):一次只能被一个线程占用
 * 共享锁(读锁):多个线程可以同时占有
 * 读写锁
 * ReadWriteLock
 * 读-读 可以共存
 * 读-写 不能共存
 * 写-写 不能共存
 */
public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();

            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,Object 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 o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取成功");

    }
}

//加锁
class MyCache2{

    private volatile Map<String,Object> map = new HashMap<>();
    //读写锁,更加细腻度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    //存,写入的时候,只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();//写锁加锁,与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();//解锁
        }


    }
    //取,读有多个线程读
    public void get(String key){
        readWriteLock.readLock().lock();//读锁
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取成功"+o);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
}

9.阻塞队列

FIFO:

  • 阻塞,写入时如果队列满了,就必须阻塞等待
  • 队列,取出时如果队列是空的,必须阻塞等待生产

阻塞队列:在多线程并发处理,线程池使用

在这里插入图片描述

BlockQueue:

在这里插入图片描述

在这里插入图片描述

学会使用队列:添加,移除

9.1 四组API:

方式抛出异常有返回值,无异常阻塞 等待超时等待
添加addoffer()put()offer()重载
移除removepoll()take()poll()重载
判断队列首elementpeek()
public static void test1(){
        //不是泛型,是队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

//        //IllegalStateException 队列已满,抛异常
//        System.out.println(blockingQueue.add("d"));

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

        //取完后也会报错,无元素的异常
    }
//不抛出异常,队列满了再添加只会输出false,用.offer()方法
//取值也不会报异常,无值就会输出null值
    public static void test2(){
        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"));

    System.out.println(blockingQueue.element());//查看队首值
    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());
    }
 //等待阻塞(一直阻塞)
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(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()); //没有此元素,会一直阻塞
        
    }
//等待阻塞(等待超时)
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        //存
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        blockingQueue.offer("d", 2,TimeUnit.SECONDS);//重载方法,超时等待
        
        //取
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll(2,TimeUnit.SECONDS);//重载方法,等不到取值就退出
    }

9.2 同步队列

SynchronousQueue,没有容量,进去一个元素必须等待取出来之后才能再往里面放一个元素,SynchronousQueue不存储元素,put一个值就会取出一个值,才可以再put一个值

package com.liu.bq;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        /*
        SynchronousQueue不存储元素,put一个值就会取出一个值,才可以再put一个值
         */
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();//同步队列

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+" put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+" put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+" put 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();


        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+">"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+">"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+">"+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

10、线程池(重点)

  • 池化技术

程序的运行,本质:占用系统的资源,优化资源的使用

线程池,连接池,内存池,对象池

池化技术:避免浪费资源,事先准备好资源,最大的并发数

线程池的好处:降低资源消耗,提高效率,方便管理,线程复用,可以控制最大并发数,管理线程

10.1 线程池三大方法:

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,更加明确线程池的运行规则,避免资源耗尽的风险

FixedThreadPool 和 SingleThreadPool:允许的请求队列太长,可能会堆积大量的请求,会导致OOM

CachedThreadPool会造成太多线程,导致OOM

package com.liu.pool;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo01 {

    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单一线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定线程池的大小,最多有5个线程并发
        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的

        try {
            for (int i = 1; i <= 10; i++) {
                //使用线程池后,用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }


    }
}

10.2 7大参数:

int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小

long keepAliveTime,//超时后不操作就释放
TimeUnit unit,//超时单位
BlockingQueue workQueue,//阻塞队列
ThreadFactory threadFactory,//编程工厂,创建线程的
RejectedExecutionHandler handler) //拒绝策略

最大承装:队列加上最大线程池,超出就会触发拒绝策略

源码分析:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//21亿,造成OOM溢出
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
//本质:开启线程就是ThreadPoolExector()
    
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
                          int maximumPoolSize,//最大核心线程池大小
                          long keepAliveTime,//超时后不操作就释放
                          TimeUnit unit,//超时单位
                          BlockingQueue<Runnable> 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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

10.3 4种拒绝策略:

在这里插入图片描述

AbortPolicy:超出最大承载数就会抛出异常,拒绝策略

CallerRunsPolicy:会让main线程执行

DiscardPolicy():队列满了,丢弃剩余未执行的任务,不会抛出异常

DiscardOldestPolicy:队列满了,尝试会和最早的竞争,不抛出异常

package com.liu.pool;

import java.util.concurrent.*;

public class Demo01 {

    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单一线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定线程池的大小,最多有5个线程并发
//        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() // 人满了,拒绝接待,并抛出异常
        );

        try {
            //最大承装:队列加上最大线程池,超出就会触发拒绝策略
            for (int i = 1; i <= 10; i++) {
                //使用线程池后,用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }


    }
}

10.4 面试题:

自定义线程池最大线程到底该如何定义?

调优:

分情况,1.CPU密集型:有多少核,就有多少条线程去执行,通过代码获取cpu核数去定义最大线程池数,

System.out.println(Runtime.getRuntime().availableProcessors());

2.IO密集型:判断程序中十分耗IO的线程,io十分占用资源,至少留15个线程处理

11、四大函数式接口(必要)

程序员必备知识:lambda表达式,链式编程,函数式接口,Stream流式计算

函数式接口:只有一个方法的接口,简化编程模型

foreach方法的参数也是函数式接口

四大原生的函数式接口:

Consumer:消费型接口

在这里插入图片描述

//消费型接口
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };

        Consumer<String> consumer1 = (str) ->{ System.out.println(str);};

BiConsumer:有两个参数,无返回值

Function:函数式接口:有一个输入参数,有一个输出,new一个函数式接口就是一个匿名内部类

在这里插入图片描述

public static void main(String[] args) {

//        Function function = new Function<String,String>() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };

        Function<String,String> function = (str)->{return str;};
    }

Predicate:断定型接口:有一个输入参数,返回值是布尔值

在这里插入图片描述

//断定型接口:有一个输入参数,返回值是布尔值
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };
        Predicate<String> predicate1 = (str) ->{return str.isEmpty();};

    }

Supplier:供给型接口

在这里插入图片描述

//供给型接口:无参数,有返回值
        Supplier<String> supplier = new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("get()");
                return null;
            }
        };

        Supplier<Integer> supplier1 = () ->{return 1024;};

12、Stream流式计算

package com.liu.stream;

import java.util.Arrays;
import java.util.List;

/**
 * 题目要求:一分钟完成此题,只用一行代码
 * 5个用户,筛选:
 * 1.id必须偶数
 * 2.年龄必须大于23岁
 * 3.用户名转为大写字母
 * 4.用户名字倒着排序
 * 5.只输出一个用户
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 21);
        User u2 = new User(2, "b", 22);
        User u3 = new User(3, "c", 23);
        User u4 = new User(4, "d", 24);
        User u5 = new User(6, "e", 25);
        //存储给list
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        //计算给stream流,链式编程
        list.stream()
                .filter(user -> {return user.getId()%2==0;})
                .filter(user -> {return user.getAge()>23;})
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((uu1,uu2) ->{return uu1.compareTo(uu2);})
                .limit(1)
                .forEach(System.out::println);

    }
}

13、ForkJoin

什么是forkJoin:并行执行任务,提高效率,处理大数据量。
在这里插入图片描述

forkjoin特点:工作窃取,线程可以抢占其他线程未进行的事务,双端队列

继承RecursiveTask:迭代任务,有返回值

继承RecursiveAction:迭代事件,无返回值

package com.liu.forkjoin;

import java.util.concurrent.RecursiveTask;

/*
    大数据计算,用forkJoin,或者用stream并行流
    如何使用forkJoin
    1.new forkJoinPool 通过它执行
    2.计算任务,forkjoinPool.submit(ForkJoinTask task)
    3.计算类要继承 ForkJoin Task,RecursiveTask迭代任务
     */
public class ForkJoinTest extends RecursiveTask<Long> {
    private Long start;
    private Long end;

    private Long temp = 10_0000L;
    public ForkJoinTest(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 {//forkjoin方式
            long middle = (start+end) / 2;
            ForkJoinTest task1 = new ForkJoinTest(start, middle);
            task1.fork();//拆分任务,把任务压入线程队列
            ForkJoinTest task2 = new ForkJoinTest(middle + 1, end);
            task2.fork();//拆分任务,把任务压入线程队列

            return task1.join() + task2.join();//结果相加
        }
    }

}

package com.liu.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        test1();//耗时:7460
//        test2(); //时间7398
        test3(); //时间210
    }

    public static void test1(){
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (Long i = 1L; i<=10_0000_0000 ; i++){
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间"+(end - start));
    }

    //forkjoin方式
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinTest(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);//有返回结果
        Long sum = submit.get();//在此会阻塞等待

        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间"+(end-start));
    }

    //stream并行流(最好的方式)
    public static void test3(){
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间"+(end-start));
    }
}

14、异步回调

Future接口设计的初衷:对将来某个事件的结果进行建模

用异步回调用CompletableFuture类实现了Future接口,有两个方法,无返回值的runAsync(Runnable接口),有返回值的supplyAsync(),可以获得同步结果

package com.liu.future;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/*
之前异步调用:ajax:不是编程语言,组合了浏览器内建的XMLHttpRequest对象和JavaScript和HTML DOM(显示或使用数据)
ajax应用程序可以将数据通过纯文本或者json文本传输,json是JavaScript对象标记语法,xml是可标记扩展语言,传输数据,存储数据

JUC下异步回调:CompletableFuture
异步执行
成功回调
失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //无返回值的异步回调
//        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("111111111111");
//        completableFuture.get();//获取阻塞执行结果

        //有返回值的异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t); // 正常的返回结果
            System.out.println("u=>" + u); // 无错误就是null,有错误就是打印错误的信息
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 520; //错误的返回结果
        }).get());
    }
}

15、JVM

Volatile是java虚拟机提供轻量级的同步机制

1.保证可见性

2.不保证原子性

3.禁止指令重排

JMM:Java内存模型,不存在的东西,概念,约定

JMM的一些同步约定:

1.线程解锁前,必须把共享变量立刻刷回主存

2.线程加锁前,必须读取主存中的最新值到工作内存中

3.加锁和解锁是同一把锁

线程,工作内存,主内存

8种操作:

lock:锁定作用于主存的变量,把一个变量标识未线程独占状态

unlock,read,load,use:把工作内存的变量传给执行引擎,assign:赋值,write,store

在这里插入图片描述

存在基本问题:线程B修改了主存的值,线程A不能及时可见!

在这里插入图片描述

16、Volatile

保证可见性:

解决上述的基本问题,能够让线程A实时看到主存发生的变化

不保证原子性:

原子性:不可分割,线程A在执行任务的时候不能被打扰,也不能被分割,要么同时成功,要么同时失败,也就是不保证同时成功或失败

不用lock和synchronized(这个可以保证原子性),如何保证原子性?

使用JUC下的原子类AtomicBoolean,AtomicInteger,AtomicLong,这些类的底层都直接和操作系统挂钩,在内存种修改值,Unsafe类是一个特殊的类

禁止指令重排:

源代码–>编译器优化的重排–>指令并行也可能重排–>内存系统重排–>执行

处理器在进行指令重排的时候考虑数据之间的依赖性

内存屏障,CPU指令,保证内存可见性(避免指令重排)

在这里插入图片描述

17、单例模式

构造器私有!!!,单例模式不安全

在单例模式中,使用volatile最多!

饿汉式单例模式:一开始就将对象加载了,会浪费内存

package com.liu.single;

//饿汉式单例模式:一开始就将对象加载了,会浪费内存
public class HungrySingleTest {

    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];


    private HungrySingleTest(){

    }

    private final static HungrySingleTest HUNGRY_SINGLE_TEST = new HungrySingleTest();

    public static HungrySingleTest getInstance(){//实例化
        return HUNGRY_SINGLE_TEST;
    }
}

双重检查锁加volatile原子性操作懒汉式单例,DCL模式

加volatile原因是因为new实例化时不是原子性的操作

package com.liu.single;

//懒汉式模式
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazyMan lazyMan;

    //双重检查锁模式懒汉式单例 DCL懒汉式 加上volatile原子性操作
    public static LazyMan getInstance(){//实例
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//不是一个原子性的操作
                    /*
                    1.分配内存空间
                    2.执行构造方法,初始化对象
                    3.把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }

}

重点:利用反射破坏异常

单例模式都不安全

1.通过私有构造器可以获得实例对象,通过反射也可以获得实例对象,不安全,解决方法:通过在构造器加锁可以解决

2.可以通过两个实例对象都是反射获取,依然可以破坏异常,解决方案:设计一个标识位关键字解决

3.依然可以破坏,通过反射的获取字段属性,并设为可获取的和重新设置标识位,又可以通过反射来破坏异常,解决:通过枚举类来解决

package com.liu.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

//懒汉式模式
public class LazyMan {

    private static boolean liuxiang = false;
    private LazyMan(){
        synchronized (LazyMan.class){
            if (liuxiang == false){
                liuxiang = true;
            }else {
                throw new RuntimeException("不要用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazyMan lazyMan; //加volatile原因是因为new实例化时不是原子性的操作

    //双重检查锁模式懒汉式单例 DCL懒汉式 加上volatile原子性操作
    public static LazyMan getInstance(){//实例
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//不是一个原子性的操作
                    /*
                    1.分配内存空间
                    2.执行构造方法,初始化对象
                    3.把这个对象指向这个空间
                     */
                }
            }
        }
        return lazyMan;
    }

    //反射可以破坏单例
    public static void main(String[] args) throws Exception {
       // LazyMan instance = LazyMan.getInstance(); 通过构造器获得实例对象

        Field liuxiang = LazyMan.class.getDeclaredField("liuxiang");
        liuxiang.setAccessible(true);//破坏字段
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);//无视私有的构造器,破坏构造器
        LazyMan instance = declaredConstructor.newInstance();

        liuxiang.set(instance,false);//将第一个字改为false,依然可以破坏异常
        /*假如两种实例方式都是通过反射的方式获得,依然可以破坏异常,解决方法:通过设置一个标识位,不通过反编译是获取不了这个标识位的关键字的
          依然可以通过反射来破坏异常,通过类.class.getDeclaredField()方法,再利用setAccessible(true)方法破坏,
          再重新设置为false就可以破坏异常
          解决:通过枚举类来解决通过反射破坏异常
         */
        LazyMan instance2 = declaredConstructor.newInstance();//用构造器new实例化,可以通过在构造器里加锁来阻止通过反射来破坏异常

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }


}

枚举:有两个参数!!

package com.liu.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//enum枚举类,是一个class类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;

        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        //NoSuchMethodException异常无参的时候,IllegalArgumentException枚举确实不能破坏单例模式,有参的时候
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

18、理解CAS

什么是CAS?

cas:compareAndSet:比较并交换,比较工作内存中的值和主内存中的值,如果这个值是期望的,那么就执行操作,如果不是就一直循环,自旋锁

Java无法操作内存,但java可以通过native方法掉头c++,c++可以操作内存,Java的后门。通过Unsafe类操作内存

package com.liu.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo01 {
    //cas:compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022); //底层用的cas
        //public final boolean compareAndSet(int expect, int update)
        //如果期望的值达到了就更新,否则不更新,CAS是CPU的并发原语!
        
        System.out.println(atomicInteger.compareAndSet(2022, 2023));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2022, 2023));
        System.out.println(atomicInteger.get());

    }

}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

缺点:

1.循环会耗时

2.一次只能保证一个共享变量的原子性

ABA问题

ABA问题

狸猫换太子来解释

package com.liu.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo01 {
    //cas:compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022); //底层用的cas
        //public final boolean compareAndSet(int expect, int update)
        //如果期望的值达到了就更新,否则不更新,CAS是CPU的并发原语!

        //对于写的sql:利用乐观锁解决问题:要知道谁动了线程!
        //=======捣乱的线程=========
        System.out.println(atomicInteger.compareAndSet(2022, 2023));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2023, 2022));
        System.out.println(atomicInteger.get());

        //=======捣乱的线程=========
        System.out.println(atomicInteger.compareAndSet(2022, 6666));
        System.out.println(atomicInteger.get());
    }

}

乐观锁:

是一种乐观思想,认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作

当线程拿到资源时,上乐观锁,在提交前,其他的锁也可以操作这个资源,当冲突的时候,并发机制会保留前一个提交,打回后一个提交,让后一个线程重新获取资源后,再操作,再提交。

拿版本号去对比

lock是乐观锁

悲观锁:

认为读少写多,遇到并发写的可能性高,每次拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,别人想拿到这个数据就会block,直到拿到锁。

当线程拿到资源时,就对资源上锁,并在提交后,才释放资源,其他线程才能使用资源

synchronized是悲观锁

在并发量低的时候性能差不多,在并发量高的时候,乐观锁的性能远远优于悲观锁。

19、原子引用

利用带版本号的原子引用解决ABA问题:知道谁动了线程,修改了数据,AtomicStampedReference() 带时间戳的,对应的思想是乐观锁

避坑:

Integer使用了对象缓存机制,默认范围是-128-127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间,超过这个区间的所有数据都会在堆上产生,不会复用对象

package com.liu.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo02 {

    public static void main(String[] args) {
        //注意:如果泛型是包装类,注意对象的引用问题,因为是对象缓存机制,超过一定访问再new的话会新分配空间地址
        //正常在业务操作,这里引用的都是一个对象
        AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);

        new Thread(()->{
            int stamp = atomicInteger.getStamp();//获得版本号
            System.out.println("a1=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //下面这一段代码跟乐观锁里面的version+1版本号操作一样
            System.out.println(atomicInteger.compareAndSet(1, 2,
                    atomicInteger.getStamp(), atomicInteger.getStamp() + 1));

            System.out.println("a2=>"+atomicInteger.getStamp());

            System.out.println(atomicInteger.compareAndSet(2, 1,
                    atomicInteger.getStamp(), atomicInteger.getStamp() + 1));

            System.out.println("a3=>"+atomicInteger.getStamp());
        },"a").start();

        //与乐观锁的原理相同,由于中间修改过了线程,导致正常的线程不能修改,返回false
        new Thread(()->{
            int stamp = atomicInteger.getStamp();//版本号获取
            System.out.println("b1=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicInteger.compareAndSet(1, 6, stamp, stamp + 1));

            System.out.println("b2=>"+atomicInteger.getStamp());
        },"b").start();
    }
}

20、各种锁的理解

1、公平锁、非公平锁

公平锁:非常公平,不能插队,必须先来后到

非公平锁:可以插队,非常不公平(默认都是非公平锁)

Lock lock = new ReentrantLock(); //非公平锁
Lock lock = new ReentrantLock(true); //公平锁

2.可重入锁

递归锁:拿到外面的锁也就拿到里面的锁了

Lock锁必须配对,加了几把锁就要解几把锁!!!

3.自选锁

spinlock

自定义锁测试:

package com.liu.lock;

import java.util.concurrent.atomic.AtomicReference;

//自旋锁
public class SpinLockDemo01 {

    //Thread null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+ "==> mylock");
        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){//所期望的是空,更新为当前线程

        }
    }

    //解锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+ "==> myUnlock");
        atomicReference.compareAndSet(thread,null);//如果是期望的线程,就置为空
    }

}

package com.liu.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/*
测试结果:
t1线程先拿到锁,t2再拿到锁,自旋,等待t1解锁,t2才会解锁
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        //底层使用cas实现的自旋锁
        SpinLockDemo01 lock = new SpinLockDemo01();

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }

        },"t1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"t2").start();

    }

}

4、死锁

线程之间想获取其他线程的锁!

package com.liu.lock;

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        MyThread myThread = new MyThread(lockA,lockB);
        MyThread myThread1 = new MyThread(lockB,lockA);
        new Thread(myThread,"t1").start();
        new Thread(myThread1,"t2").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+"==>"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"==>"+lockA);
            }
        }

    }
}

解决死锁问题:

1.使用jps -l定位进程号,在终端里输入命令查看当前进程号

在这里插入图片描述

2.使用jstack 144444命令查看进程问题

在这里插入图片描述

5.读写锁

  • 独占锁(写锁):一次只能被一个线程占用
  • 共享锁(读锁):多个线程可以同时占有

kDemo01();

    new Thread(()->{
        lock.myLock();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.myUnLock();
        }

    },"t1").start();

    TimeUnit.SECONDS.sleep(1);

    new Thread(()->{
        lock.myLock();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.myUnLock();
        }
    },"t2").start();

}

}


## 4、死锁

**线程之间想获取其他线程的锁!**

```java
package com.liu.lock;

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        MyThread myThread = new MyThread(lockA,lockB);
        MyThread myThread1 = new MyThread(lockB,lockA);
        new Thread(myThread,"t1").start();
        new Thread(myThread1,"t2").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+"==>"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"==>"+lockA);
            }
        }

    }
}

解决死锁问题:

1.使用jps -l定位进程号,在终端里输入命令查看当前进程号

在这里插入图片描述

2.使用jstack 144444命令查看进程问题
在这里插入图片描述

5.读写锁

  • 独占锁(写锁):一次只能被一个线程占用
  • 共享锁(读锁):多个线程可以同时占有
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值