JUC学习和笔记(万字文章)

  • Post author:
  • Post category:其他




JavaJUC学习篇


什么是JUC

java.util 工具包、包、分类


线程和进程

进程:一个程序,比如qq,酷我音乐啥的一个进程往往包含一个多个线程。

线程:java默认两个线程一个main,一个GC


java线程实现的方式:

继承Thread,实现Runnable接口,实现callable


并行和并发

并行:一个cpu处理多个任务

并发:是指多个处理器或者是多核的处理器同时处理多个不同的任务。


线程的状态:

创建 –》 就绪 –》 运行 –》阻塞 –>死亡

(我们通常分为五种,但是有些情况下分为7种,加的两种为等待和超时等待)


wait和sleep的区别

wait来自Object sleep来自Thread

其次:

wait会释放锁 sleep不会

使用范围不一样:

wait必须放到同步块中

sleep没有要求


Lock和Synchonized

传统的synchonized锁

synchonized(){

}

括号里边一般两种

1对象

2类名.class

还有 private synchonized void name(){}


Lock接口

ReentrantLock: 可重用锁

readLock: 读锁

writeLock:写锁


Synchronized 和 Lock 区别

1、Synchronized 内置的Java关键字, Lock 是一个Java类

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

3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁

4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下

去;

5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以

自己设置);

6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

生产者消费者问题

生产者和消费者问题Synchronized版

package com.xh.juc.day02;


/**
 * @Title
 * @Description
 * @author  罗小黑
 * 线程之间的通信问题,常用的是生产者和消费者问题。 等待唤醒,通知唤醒。
 * 线程交替执行 A B操作同一个变量 num = 0;
 * A num + 1
 * B num  - 1
 * @return
 * @date 2022/10/2 16:29

 */
public class A {
    public static void main(String[] args) {
        Date date = new Date();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"A").start();

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

// 生产者消费者: 判断等待 ,业务 , 通知
class Date{
    //数字资源锁
    private int number = 0;

    public synchronized  void increment() throws InterruptedException {
        if(number != 0){
            //等待
            this.wait();
        }

        //通知其他线程我加1完毕了
        number ++ ;
        System.out.println(Thread.currentThread().getName() + "=>" +number);
        this.notifyAll();
    }
    public synchronized  void  decrement() throws InterruptedException {
        if(number == 0){
            //等待
            this.wait();
        }
        number --;
        //通知其他线程我-1完毕了
        System.out.println(Thread.currentThread().getName() + "=>" +number);
        this.notifyAll();
    }
}

生产者消费者问题:两个还好,但是到了三个或者四个就会产生问题

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


问题存在:A,B,C,D四个线程的情况下出现问题

public class B {

    public static void main(String[] args) {
        Date date = new Date();

        //创建线程A进行加
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"D").start();
    }
}

A=>1

C=>0

B=>1

A=>2

B=>3

C=>2

C=>1

C=>0

B=>1

A=>2

B=>3

C=>2

B=>3

A=>4

D=>3

D=>2

D=>1

D=>0

A=>1

D=>0

以上原因是由于线程产生了虚假唤醒,导致出现问题。


解决办法,我们将if改成while判断

class Date1{
    //数字资源锁
    private int number = 0;

    public synchronized  void increment() throws InterruptedException {
        while(number != 0){//if改成while解决线程虚拟唤醒
            //等待
            this.wait();
        }
        //通知其他线程我加1完毕了
        number ++ ;
        System.out.println(Thread.currentThread().getName() + "=>" +number);
        this.notifyAll();
    }
    public synchronized  void  decrement() throws InterruptedException {
        while(number == 0){//改成while解决线程虚拟唤醒
            //等待
            this.wait();
        }
        number --;
        //通知其他线程我-1完毕了
        System.out.println(Thread.currentThread().getName() + "=>" +number);
        this.notifyAll();
    }
}


Lock解决生产者和消费者问题JUC版本的

基本实现

public class C {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"C").start();

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

    }
}

class  Data{
    private  int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //加一操作
    /*  condition.await();//等待相当于wait
        condition.signalAll();//唤醒全部*/
    public void increment () throws InterruptedException {

        try {
            lock.lock();
            while(number != 0){
                condition.await();
            }
            number ++;

            System.out.println(Thread.currentThread().getName()+"=>"+ number);
            condition.signalAll();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }


    }

    //减一操作
    public void decrement () throws InterruptedException {
        try {
            lock.lock();
            while (number == 0) {
                condition.await();
            }
            number--;

            System.out.println(Thread.currentThread().getName() +"=>"+ number);
            condition.signalAll();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


任何一个新的技术绝对不是仅仅覆盖了一起的技术,一定会在原来的技术上进行相应的拓展


Condtion实现精准唤醒

public class D {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data2.printA();
            }
        },"A").start();;
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data2.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data2.printC();
            }
        },"C").start();;
    }
}

class Data2{
    private int number = 1; //1A 2B 3C
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    public void printA(){
        lock.lock();
        try{
            //业务,判断 ->执行 ->通知
            while(number != 1){
                //等待
                condition.await();
            }
            System.out.println(Thread.currentThread().getName()+ "==>AA" );
            number = 2;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try{
            //业务,判断 ->执行 ->通知
            while ( number != 2){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+ "==>BB");
            number = 3;
            condition2.signal();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try{
            //业务,判断 ->执行 ->通知
            while (number != 3){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "==>CC");
            number =1;
            condition.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}


什么是锁,如何判断锁对象:

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone =new Phone();
        //并不是谁先谁后,而是由于锁的存在,谁先拿到谁执行
        new Thread(()->{
            try {
                phone.sendSms();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快

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

class Phone{

    public synchronized  void sendSms() throws InterruptedException {

        TimeUnit.SECONDS.sleep(2);
        System.out.println("发短信");
    }
    public synchronized  void sendTel(){
        System.out.println("打电话");
    }
}

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        //创建两个不同的对象,每个对象都 持有一把锁,故而有两把锁
        Phone2 phone2 =new Phone2();
        Phone phone = new Phone();
        //并不是谁先谁后,而是由于锁的存在,谁先拿到谁执行
        new Thread(()->{
            try {
                //存在锁 phone2.sendSms();
                phone.sendSms();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快

        new Thread(()->{
           // phone2.hello();
            phone2.sendTel();
        },"B").start();
    }
}

class Phone2{
        //synchronized锁的对象时方法的调用者
    public synchronized  void sendSms() throws InterruptedException {

        TimeUnit.SECONDS.sleep(2);
        System.out.println("发短信");
    }
    public synchronized  void sendTel(){
        System.out.println("打电话");
    }

    //没有加锁,普通方法,故而没有收到锁的限制
    public void hello(){
        System.out.println("说hello");
    }
}

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        //加了static保证锁住类,而不是之前的对象了
        Phone3 phone3 =new Phone3();
        Phone phone = new Phone();
        new Thread(()->{
            try {
                //存在锁 phone2.sendSms();
                phone.sendSms();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快

        new Thread(()->{
            // phone2.hello();
            phone3.sendTel();
        },"B").start();
    }
}

class Phone3{
    //synchronized锁的对象时方法的调用者
    //static类一加载就有了 此时锁的是类而不是一开始的对象了锁的是Phone3.class
    public static synchronized  void sendSms() throws InterruptedException {

        TimeUnit.SECONDS.sleep(2);
        System.out.println("发短信");
    }
    public static synchronized  void sendTel(){
        System.out.println("打电话");
    }

    //没有加锁,普通方法,故而没有收到锁的限制
    public void hello(){
        System.out.println("说hello");
    }
}



import java.util.concurrent.TimeUnit;

public class Test4 {
    public static void main(String[] args) {
        Phone4 phone4 = new Phone4();
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            try {
                phone4.sendSms();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();

        new Thread(()->{
            phone2.sendTel();
        }).start();

    }
}

class Phone4{
    public static  synchronized  void sendSms() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("发短信");
    }

    public synchronized  void call(){
        System.out.println("打电话");
    }
}

new this 具体的一个手机

static Class 唯一的一个模板


集合类是不安全的

List

//java.util.ConcurrentModificationException  并发修改异常
public class ListTest {
    public static void main(String[] args) {
        //并发下ArrayList是不安全的。   java.util.ConcurrentModificationException
        /*
         解决办法,使用Vector可以保证不报错
           List<String> list =  new Vector<>();

         解决方法二,使用Collection将ArrayList转变成安全的,使用锁机制
         List<String> list = Collections.synchronizedList(new ArrayList<>());

        解决办法三:使用JUC下的CopyOnWriteArrayList
        List<String> list = new CopyOnWriteArrayList<>(); //JUC里边自带的解决办法
         */
        //copyOnWrite写入时复制  COW计算程序领域的一种优化策略
        //多个线程调用的时候,list,读取的时候固定的,写入(覆盖)
        //写入时候避免覆盖,造成数据问题
        // copyOnWrite 比vector nb在哪?
       // List<String> list = new ArrayList<>();
        List<String> list = new CopyOnWriteArrayList<>(); //JUC里边自带的解决办法
        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();
        }
    }
}


Set的不安全

/**
 * @Title java.util.ConcurrentModificationException
 * @Description 同理可得还是出现并发修改异常
 * @author  罗小黑
 * @date 2022/10/5 14:22

 */
public class SetTest {
    public static void main(String[] args) {
        //解决办法,还是如以前一样
        // Set<String> set = new HashSet<>();
        //方法一使用Collection.synchronizedSet方法解决
       // Set<String> set = Collections.synchronizedSet(new HashSet<>());
        //方法二使用JUC下的Copy...
        //Set<String > set = new CopyOnWriteArraySet<>();
        Set<String > set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}


HashSet的底层就是HashMap

public HashSet() {
        map = new HashMap<>();
    }

//add  set 本质上就是map key 是无法重复的
public boolean add(E e){
    return map.put(e,PRESENT) == null;
}
private static final object PRESENT = new Object();  //不可变的值



HashMap


回顾hashMap的 简单用法加载因子为0.75

//ConcurrentModificationException存在并发修改异常
public class HashMapTest {
    public static void main(String[] args) {
        //map 是这样子用的吗?不是,工作中不用HashMap(16,0.75)
        //默认等价干什么?

        ConcurrentModificationException存在并发修改异常
        //Map<String ,Object> map = new HashMap<>();
       //解决办法一,还是使用        Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());
        //Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());

        //解决办法二
        Map<String,String> map = new ConcurrentHashMap<>();

        for (int i = 0; i < 10; 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. 方法不同,run()/call()

代码测试

public class CallabelTest {
    //两者对比,看到这里实现的Runnable接口没有返回值
    //new Thread (new Runnable()).start();
    //new Thread (new FutureTask<V>()).start();
    //new Thread (new FutureTask<V>(Callable)).start();
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new Thread().start(); //怎么启动Callable
        MyThread1 thread = new MyThread1();
        FutureTask futureTask =  new FutureTask(thread);
        // 适配类
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start(); //结果会被缓存,效率会高
        Integer o = (Integer)futureTask.get();//获取callbale的返回结果
        System.out.println(o);
    }
}

//实现Runnable接口
class MyThread implements  Runnable{
    @Override
    public void run() {
    }
}

//实现Callable接口
class MyThread1 implements Callable<Integer>{

    //对比发现,这里实现的Callable接口是存在返回值的
    @Override
    public Integer call() throws Exception {
        System.out.println("Call()");
        return 123;
    }
}

细节:

  1. 存在缓存
  2. 结果可能需要等待,会阻塞



常用的辅助类(必须掌握)


CountDownLatch

//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要执行任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread( ()->{
                System.out.println(Thread.currentThread().getName() + " \t GO OUT");
                countDownLatch.countDown(); //数量减1
            },String.valueOf(i)).start();
        }
        countDownLatch.await(); //等待计数器归零,让向下执行
        //保证数据全部执行之后才将程序结束
        //countDownLatch.countDown(); // -1
        System.out.println("关门");
    }
}

减法计数器:

原理是数量减一:

 countDownLatch.countDown(); // -1

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


每次有线程调用countDown()数量1,假设计数器变为0,countDownLatch.await就会被唤醒,继续执行!


CycliBarrier: 加法计数器

   public class CycliBarrierDemo {
        public static void main(String[] args) {
            //如果这里的数值没有在for循环中体现的话,就不能结束事件,也就会一直运行,占用程序运行
            CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
                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 + "个龙珠"); //我们发现是操作不到i的
                    try {
                        cyclicBarrier.await();  //等待
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (BrokenBarrierException e) {
                        throw new RuntimeException(e);
                    }
                }).start();
            }
        }
    }



Semaphore :  信号量

    public class SemaphoreDemo {
        public static void main(String[] args) {
            //线程数量;停车位,限流
            Semaphore semaphore = new Semaphore(6);
    
            for (int i = 0; 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) {
                        throw new RuntimeException(e);
                    } finally {
                        semaphore.release();
                    }
                    //release()释放
                },String.valueOf(i)).start();
            }
        }
    }

原理:

semaphore.acquire();获得,假如已经满了,等待,等待被释放

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

读写锁

ReadWriteLock:读可以被多个线程读,但写的时候只能是一个线程写

  public class ReadWriteLockDemo {
    
        public static void main(String[] args) {
    
            MyCacheLock myCache = new MyCacheLock();
            for (int i = 0; i < 5; i++) {
                final  int temp = i; //lamada表达式中不能直接读取i,只能通过这样的转换
                //只做写入的操作
                new Thread(()->{
                    myCache.put(temp + "",temp +"");
                },String.valueOf(i)).start();
            }
    
            //只做读取的操作
            for (int i = 0; i < 5; i++) {
                final  int temp = i; //lamada表达式中不能直接读取i,只能通过这样的转换
                //只做写入的操作
                new Thread(()->{
                    myCache.get(temp + "");
                },String.valueOf(i)).start();
            }
    
        }
    }
    
    
    //未加锁导致,我在写入1的时候由于线程影响可能会产生1没写入完成,
    // 二也进行写入了
    class MyCache{
        private  volatile Map<String,Object> map = new HashMap<>();
    
        //这个是一个存的过程
        public void put(String key , Object obj){
            System.out.println(Thread.currentThread().getName()+ "写入"+ key);
            map.put(key,obj);
            System.out.println(Thread.currentThread().getName()+"写入成功");
        }
    
        //这个是一个取的过程
        public void get(String key){
            System.out.println(Thread.currentThread().getName()+ "读取"+ key);
            Object obj =  map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取成功");
        }
    }
    
    class MyCacheLock{
        private  volatile Map<String,Object> map = new HashMap<>();
    
        //private Lock lock = new ReentrantLock(); //普通锁
        //读写锁,存写入的时候,只希望同时只有一个线程写
        private ReadWriteLock lock =  new ReentrantReadWriteLock();
        //这个是一个存的过程
        public void put(String key , Object obj){
    
            //这个时候就是进行枷锁了
            lock.writeLock().lock();
            try{
                System.out.println(Thread.currentThread().getName()+ "写入"+ key);
                map.put(key,obj);
                System.out.println(Thread.currentThread().getName()+"写入成功");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.writeLock().unlock();
            }
        }
    
        //这个是一个取的过程
        public void get(String key){
    
            //加上读锁
            lock.readLock().lock();
    
            try{
                System.out.println(Thread.currentThread().getName()+ "读取"+ key);
                Object obj =  map.get(key);
                System.out.println(Thread.currentThread().getName() + "读取成功");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.readLock().unlock();
            }
        }
    
    }

阻塞队列


队列(FIFO)

写入的时候:如果队列是满的,那么久必须阻塞等待。

取出来的时候:如果队列是空的,那么必须阻塞等待生产。


BlockingQueue

在线程下,我们使用的时候需要注意阻塞队列是多线程下并发处理,线程池处理。

学会使用队列

添加,移除

四组API(熟练掌握并使用 )

方式 抛出异常 不会抛出异常 阻塞等待 超时等待
添加 add offer put offer(,)
移除 remove poll take poll(,)
判断队列首位 element peek
  1. 抛出异常

```java
  public class BlockingQueueDemo {
       /* Collections
        List
        Set
        */
        public static void main(String[] args) {
            test1();
        }
        /*
        抛出异常
         */
        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"));
            /*由于我定义的是3个值,故而如果添加四个值的话导致保持
           队列满了,这就说明和我们创建一个数组一样,定于一个大小,超出这个大小就好报错
             抛出异常 Queue full*/
           // System.out.println(blockingQueue.add("d"));
            //讲队列的所有添加值全部移除,使得队列为空
           // 一次只能移除一个
            System.out.println(blockingQueue.remove());
            System.out.println(blockingQueue.remove());
            System.out.println(blockingQueue.remove());
            /*
            次数队列为空,我们可以发现将会产生问题
            java.util.NoSuchElementException
            */
            System.out.println(blockingQueue.remove());
        }
    }
  1. 不会抛出异常
  /*
            不抛出异常
         */
        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"));//false
            System.out.println(blockingQueue.peek()); //判断队首元素
            System.out.println("===========================");
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());
            System.out.println(blockingQueue.poll());  //null
        }
  1. 阻塞 , 等待
 /*
            等待,阻塞(一直阻塞)
         */
        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());
        }
    
  1. 超时等待
 /*
        超时等待
     */
    public  static  void test4() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        //超时等待啊,超过两秒钟就退出
        blockingQueue.offer("d",2, TimeUnit.SECONDS);

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        blockingQueue.poll(2,TimeUnit.SECONDS);//超过2秒钟就退出
    }

SynchronousQueue

 /*
        同步队列
        和其他的是不一样的,SynchronousQueue不存储元素
        put了一个元素,必须从里边先take出来,否则不能在put进去值
     */
    public class SynchronousQueueDemo {
        public static void main(String[] args) {
            //同步队列
         SynchronousQueue 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) {
                 throw new RuntimeException(e);
             }
         },"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) {
                 throw new RuntimeException(e);
             }
         },"T2").start();
        }
    }
    


线程池(重点)


池化技术

程序的运行,本质:占用系统的资源,优化资源的使用!=>池化技术

线程池,连接池,内存池,对象池。。。 创建,销毁,十分浪费资源

池化技术,事先准备好一些资源,有人要用,就可以来我这里拿,用完之后还给我


线程池的好处

  1. 降低资源的消耗
  2. 提高响应的速度
  3. 方便管理

线程复用,可以控制最大的并发数,管理线程

/*
    Executor 工具类,里边有三大方法
 */
public class Demo1 {
    public static void main(String[] args) {
     // ExecutorService threadPool =  Executors.newSingleThreadExecutor();//创建一个单例的线程池
       ExecutorService threadPool =  Executors.newCachedThreadPool();//创建一个缓存的线程池,遇强则强,遇弱则弱
       //ExecutorService threadPool =  Executors.newFixedThreadPool(5);//创建一个固定的线程大小

        //TODO 线程使用完毕是要关闭的                                           
        try {
            for (int i = 0; i < 10; i++) {
                final int temp = i;
                //new Thread(()->{}).start();//常规的创建线程的方式
                //现在线程池的创建方式 threadPool.execute(()->{});
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+ "=>" + "OK");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            threadPool.shutdown();
        }
    }
}


7大参数

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亿
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
	public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
//我们发现都是调用 ThreadPoolExecutor
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;
    }


【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:


1)FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。


2)CachedThreadPool 和 ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

  /*
        Executor 工具类,里边有三大方法
    
        七大参数
        四种拒绝策略
        new ThreadPoolExecutor.AbortPolicy() //银行满了,但还有人进去
        new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里
        new ThreadPoolExecutor.DiscardPolicy()  //队列满了不会抛出异常,会丢到任务
        new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的去竞争
     */
    public class Demo1 {
        public static void main(String[] args) {
         // ExecutorService threadPool =  Executors.newSingleThreadExecutor();//创建一个单例的线程池
          // ExecutorService threadPool =  Executors.newCachedThreadPool();//创建一个缓存的线程池,遇强则强,遇弱则弱
           //ExecutorService threadPool =  Executors.newFixedThreadPool(5);//创建一个固定的线程大小
           /*
                七大参数 ,自定义线程池,一般是工作中使用,因为使用工具了类会出现问题
            */
            ExecutorService threadPool = new ThreadPoolExecutor(
                    2,
                    5,
                    3,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(3),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的去竞争
                    );
            //TODO 线程使用完毕是要关闭的
            try {
                for (int i = 0; i < 10; i++) {
                    final int temp = i;
                    //new Thread(()->{}).start();//常规的创建线程的方式
                    //现在线程池的创建方式 threadPool.execute(()->{});
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName()+ "=>" + "OK");
                    });
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                threadPool.shutdown();
            }
        }
    }

线程池大小如何去设置

CPU密集型和IO密集型(调优)

/*
        最大线程数应该如何定义?
        IO密集型 十分占用资源 程序 , 15个类型任务  判断你的程序中判断程序中占用IO的线程
        CPU密集型 ,几核就是几,可以保持CPU的效率最高
         */
        //输出CPU的核心数
        System.out.println(Runtime.getRuntime().availableProcessors());


四大函数接口(必需掌握)

新时代的程序员需要掌握的lambda表达式,链式编程,函数式接口,Stream流式计算


函数式接口:只有一个方法的接口

比如:Runnable

简化开发模型,在新版本中大量使用

比如foreach

   /*
     * @Title 函数式接口
     * @Description  Function
     * 有一个输入参数,有一个输出
     * 只要是 函数式接口,那么我们都可以用lambda 表达式简化
     *
     * @author  罗小黑
     * @param null
     * @return
     * @date 2022/10/7 18:38
 
     */
    public class FunctionDemo {
        public static void main(String[] args) {
           Function function = new Function<String,String>(){
    
               //工具类,输出输入的值
               @Override
                public String apply(String o) {
                    return o;
                }
            };
           Function function1 = (str)->{ return  str;};
            System.out.println(function.apply("asd"));
            System.out.println(function1.apply("add"));
        }
    }

    //断定性接口;有一个输入参数,返回值只能是布尔值
    public class PredicateDemo {
        public static void main(String[] args) {
           Predicate predicate = new Predicate<String >(){
    
               //判断字符串是否为空
               @Override
               public boolean test(String o) {
    
                   return o.isEmpty();
               }
           };
    
           //注意一下,这里泛型需要定义一下
           Predicate<String> predicate1 = (str)->{return str.isEmpty();};
            System.out.println(predicate.test("aad"));
            System.out.println(predicate1.test(""));
        }
    }

    /*
        消费型接口
        只有输入,没有返回值
     */
    public class ConsumerDemo {
        public static void main(String[] args) {
            Consumer consumer = new Consumer<String>() {
                @Override
                public void accept(String o) {
                    System.out.println(o);
                }
            };
            Consumer<String> consumer1 = (str)->{
                System.out.println(str);
            };
            consumer.accept("你好");
            consumer1.accept("我喜欢你");
        }
    }

    //供给型函数接口,没有参数,只有返回值
    public class SupplierDemo {
        public static void main(String[] args) {
            Supplier supplier = new Supplier<String>() {
                @Override
                public String get() {
                    return "你好";
                }
            };
    
            Supplier supplier1 = ()->{return "我还是喜欢你";};
            System.out.println(supplier.get());
            System.out.println(supplier1.get());
        }
    }


老程序员,泛型,反射,枚举都需要会


新程序员,lambda表达式,函数式接口,Stream流式计算


什么是流式计算

大数据:存储+计算

集合,MySql本质上就是存储东西

计算都应该交给流来处理

 /*
    流式计算
    现有一个题目:要求一分钟内完成此题,只能用一行代码实现
        现有五个用户,
           1.ID必须为偶数
           2.年龄必须要大于23岁
           3.用户名必须转换为大写
           4.用户名字倒叙排序
           5.只输出一个用户
     */
    public class Test {
        public static void main(String[] args) {
            User u1 = new User(1,12,"a");
            User u2 = new User(2,25,"b");
            User u3 = new User(3,23,"c");
            User u4 = new User(4,15,"d");
            User u5 = new User(5,16,"e");
    
            //使用List集合存储,将集合类对象转换
            List<User> list =  Arrays.asList(u1,u2,u3,u4,u5);
            System.out.println(list);
            //计算交给Steam流
            list.stream()
                    .filter(u->{return  u.getId()%2 ==0;})//过滤id为偶数的
                    .filter(u->{return  u.getAge() > 16;}) //过滤年龄小于16的
                    .map(u->{return u.getName().toUpperCase();}) //通过map函数式接口将名字转换为大写
                    .sorted((uu1,uu2) -> {return  uu1.compareTo(uu2);})//排序并比较
                    .limit(1)//只显示一条数据
                    .forEach(System.out::println);//将他遍历出来
        }
    }


ForkJoin

ForkJoin在JDK1.7中,并行执行任务,提交效率,一般通用于大数据量!

大数据:Map Reduce(把大任务拆分为小任务)


ForkJoin特点

测试

 public class Test {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
          // test1();
            //test2();
            test3(); //贼强,速度很快一般大型的时候使用
        }
    
        //普通程序员
        // Sum=时间4099
        public  static  void test1(){
            Long start = System.currentTimeMillis();
    
            Long sum = 0L;
            for (long i = 1L; i <= 10_0000_0000; i++) {
                sum +=  i;
            }
            long end = System.currentTimeMillis();
    
            System.out.println("Sum="+sum+"时间\t" + (end -start));
        }
    
    
        //中级程序员
        //Sum=时间11302
        public  static  void test2() throws ExecutionException, InterruptedException {
            Long start = System.currentTimeMillis();
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            ForkJoinTask<Long> task = new ForkJoinDemo(1L,10_0000_0000L);
            forkJoinPool.execute(task);
           ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
            Long sum = submit.get();
            long end = System.currentTimeMillis();
            System.out.println("Sum="+"时间" + (end -start));
    
        }
       
        //并行流  时间241
        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 + "\n时间" + (end -start));
    
        }
    }

异步回调

Future设计初衷,对将来某个事件结果进行建模

  /*
    异步调用ajax技术,一般上ajax使用原理就异步调用
    异步执行:
    成功回调
    失败回调
    
     */
    public class FutureDemo {
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //发送一个请求  ,没有返回值的异步回调
            CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "RunAsny=>Void");
            });
            System.out.println("111");
            completableFuture.get();//获取执行结果
    
            System.out.println("=================================");
            //有返回值的异步回调
            //有返回值的是supplyAsync异步回调
            //ajax,成功和失败的回调
            //返回的是错误的信息。
          CompletableFuture<Integer> completableFuture1 =   CompletableFuture.supplyAsync(()->{
              System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
               int i =  4/0;
               return 1024 ;
    
            });
          completableFuture1.whenComplete((t,u)->{
              System.out.println( " --> " +t); //正确的返回结果
              System.out.println( " -- >" + u);  //错误信息
    
          }).exceptionally((e)->{
              System.out.println(e.getMessage());
              return 444; //可以通过这个标识码获取错误的返回结果
          }).get();
    
        }
    }
    



JVM初步理解


谈谈你对Volatile的理解


volatile是javaJVM提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 静止指令重排


JMM:java内存模型,不存在的东西,就是一个概念!约定

关于JMM的一些同步的约定

  1. 线程解锁前,必须把共享变量立刻 刷回主存。
  2. 线程加锁前,必须读取主存中的最新值到工作内存中。
  3. 加锁和解锁是同一把锁。

线程工作内存,主内存

jvm的8种操作:


> 1. read - > load
> 2. user -> assign
> 3. write ->store
> 4. lock -> unlock


内存交互操作有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

  /*
    在这个例子中我们发现,当执行线程的时候,num =0是一开始的时候,之后我们将
    改成num = 1,但是程序输出1却没有终止
    这里原因是,我们更改了num的值,但是线程B知道而线程A却不知道
    也就是main线程知道,但是Thread线程却是不知道
     */
    public class VolatileDemo {
        private  static  int num = 0;
    
        public static void main(String[] args) throws InterruptedException {
    
            new Thread( ()->{
                while (num == 0){
                    
                }
            }).start();
    
            TimeUnit.SECONDS.sleep(1);
            num =1;
            System.out.println(num);
        }
    }

验证特性

  • 保证可见性
/*
在这个例子中我们发现,当执行线程的时候,num =0是一开始的时候,之后我们将
改成num = 1,但是程序输出1却没有终止
这里原因是,我们更改了num的值,但是线程B知道而线程A却不知道
也就是main线程知道,但是Thread线程却是不知道
 */
public class VolatileDemo {
    //不加volatile就会陷入无限循环
    private volatile   static  int num = 0;

    public static void main(String[] args) throws InterruptedException {

        new Thread( ()->{
            while (num == 0){
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);
        num =1;
        System.out.println(num);
    }
}
  • 不保证原子性:不可分割

原子性: 线程A在执行任务的时候,不能被打扰到,也不能被分割,要么同时成功要么同时失败。

//不保证原子性
public class VolatileDemo2 {
    //这个时候我们可以发现,volatile无法保证原子性
    private volatile   static  int num = 0;

    //synchronized可保证为结果为20000
    public  static void  add(){
        num ++; // 不是原子性
    }

    public static void main(String[] args) {
       //理论上num 为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        /*
        java中默认两个线程,第一个main,第二个GC线程
         */
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "  " + num);
    }
}

//不保证原子性
public class VolatileDemo2 {
    //这个时候我们可以发现,volatile无法保证原子性

    private volatile   static  int num = 0;
    //synchronized可保证为结果为20000

  //我们此时发现,lock也可以保证结果为20000
    private static Lock lock = new ReentrantLock();

    public  static void  add(){
        lock.lock();
        try{
            num ++;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
       //理论上num 为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        /*
        java中默认两个线程,第一个main,第二个GC线程
         */
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "  " + num);
    }
}

如果不加Lock和synochronized如何保证原子性?


使用原子类解决原子性

//不保证原子性
public class VolatileDemo2 {
    //这个时候我们可以发现,volatile无法保证原子性

//    private volatile   static  int num = 0;
  private volatile  static AtomicInteger num = new AtomicInteger();

    //synchronized可保证为结果为20000

  //我们此时发现,lock也可以保证结果为20000
    private static Lock lock = new ReentrantLock();

    public  static void  add(){
    /*    lock.lock();
        try{
            num ++;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }*/
        num.getAndIncrement(); //AtomicInteger +1方法


    }

    public static void main(String[] args) {
       //理论上num 为20000
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        /*
        java中默认两个线程,第一个main,第二个GC线程
         */
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "  " + num);
    }


这些类的底层都是和操作系统挂钩,在内存中修改中,Unsafe是一个特殊的存在。

指令重排


什么是指令重排:你写的程序,计算机并不是按照你写的程序那样去执行的。

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

int x = 1; //1
int y = 2;//2
x = x+ 5;// 3
y = x +x;  // 4
//我们希望的顺序是 1234   可能是 1234  2134 1324 但不可能是 4123

可能造成影响的结果: a b x y 这四个值 默认都是 0

线程A 线程B
x =a y =b
b =1 a = 2

正常的结果 x = 0; y = 0;


volatile可以避免指令重排

内存屏障,CPU指令,作用:

  1. 保证特定的操作执行顺序
  2. 保证某些变量的内存可见性(利用这些特性Volatile实现可见性)


volatile可保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排的现象产生。


内存屏障使用最多的场景是单例模式

单例模式

//饿汉式单例模式
public class SingleDemo {
    //饿汉式单例这些数据都没有使用,但是被创建的时候还是占用很多内存
    private  byte[] d1 =  new byte[1024* 1024];
    private  byte[] d2 =  new byte[1024* 1024];
    private  byte[] d3 =  new byte[1024* 1024];
    private  byte[] d4 =  new byte[1024* 1024];
    //无参构造 只要是单例,我们都要创建一个私有化构造
    private  SingleDemo(){
    }

    //单例模式的调用方式
    private  static  SingleDemo singleDemo = new SingleDemo();

    public static  SingleDemo getInstance(){
        return  singleDemo;
    }

}
/*
 * @Title 懒汉式单例模式
 * @Description 
 * @author  罗小黑
 * @param null
 * @return 
 * @date 2022/10/9 0:04

 */
public class LazyDemo {
    //不说二话直接整上构造器,只要是单例模式,构造器私有
    private  LazyDemo(){
        System.out.println(Thread.currentThread().getName() + "\t OK");
    }
   //先创建一个lazy对象,为保证不会产生指令重排,我们需要在这加入volatile
    private  volatile static  LazyDemo lazyDemo;

/*
 * @Title 双重检测模式下的懒汉式单例,DCL懒汉式
 * @Description
 * @author  罗小黑
 * @return com.xh.juc.day06.Single.LazyDemo
 * @date 2022/10/9 0:15

 */
    //重点是getInstance在单例模式中我们直接使用的是getInstance()
    public  static  LazyDemo getInstance(){
       //解决办法之一最常用的加锁。

        if(lazyDemo == null){
            synchronized (LazyDemo.class){
                lazyDemo = new LazyDemo(); //不是一个原子性操作
    /*
    有三步:
           1. 分配内存空间
           2. 执行构造方法,初始化对象
           3.把这个对象指向这个空间
           这个时候可能会产生指令重排,会出现故障
     */

            }
            //如果传如的对象为空,那么我们就需要将对爱心给他赋予
            //   lazyDemo = new LazyDemo();

        }
        return lazyDemo;
    }
    //问题,单线程下没问题,但是多线性下出现问题.
    // 我们发现在多线程下会产生不一样的结果,而且也不会创建10个线程
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyDemo.getInstance();
            },String.valueOf(i)).start();
        }
    }
}

// 静态内部类 
public class Holder { 
    private Holder(){
	}
    public static Holder getInstace(){ 
        return InnerClass.HOLDER;
    }
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

//单例不安全,反射
//枚举
 // enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE; 
    } 
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 
        EnumSingle instance1 = EnumSingle.INSTANCE; 
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); 
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        // NoSuchMethodException: com.kuang.single.EnumSingle.<init>() 
        System.out.println(instance1);
        System.out.println(instance2);
    } 
}


CAS

public class CASDemo {
    //CAS compareAndSet : 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022);

        //期望,更新
       // public final boolean compareAndSet(int expect,int update);
        //如果我期望的值达到了,那么就更新,否则不更新 CAS是CPU的调度
        //java 无法操作内存,java可以调用C++ native  C++可以操作内存,java的后门,可以操作这个类(Unsafe)调度
        System.out.println(atomicInteger.compareAndSet(2022, 2123));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}


CAS:比较当前工作内存中的值和主内存的值,如果这个值是期望的,那么执行操作,如果不是就一直循环

缺点:

  1. 循环会耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

CAS:ABA问题(狸猫换太子)

public class CASDemo {
    //CAS compareAndSet : 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022);

        //期望,更新
       // public final boolean compareAndSet(int expect,int update);
        //如果我期望的值达到了,那么就更新,否则不更新 CAS是CPU的调度
        //java 无法操作内存,java可以调用C++ native  C++可以操作内存,java的后门,可以操作这个类(Unsafe)调度
        //(A)
        System.out.println(atomicInteger.compareAndSet(2022, 2123));
        System.out.println(atomicInteger.get());
// B
        System.out.println(atomicInteger.compareAndSet(2123, 2022));
        System.out.println(atomicInteger.get());
    //A    
        System.out.println(atomicInteger.compareAndSet(2022, 2123));
        System.out.println(atomicInteger.get());

    }
}


原子引用(ABA问题)

//乐观锁和悲观锁
public class CASDemo2 {
    public static void main(String[] args) {
        //注意如果泛型是包装类,需要注意引用类型和范围Integer的范围是-128 到 127
        //正常的应用的时候,我们使用的都是对象,而不是直接使用Integer的等,
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获取版本号
            System.out.println( Thread.currentThread().getName()+ " " + atomicStampedReference.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(
                    1, 5,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println( Thread.currentThread().getName()+ " -A2- " + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(
                    5, 6,
                    atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println( Thread.currentThread().getName()+ " -A3- " + atomicStampedReference.getStamp());

        },"A").start();


        //比较AB两个线程的版本号
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获取版本号
            System.out.println( Thread.currentThread().getName()+ " " + atomicStampedReference.getStamp());

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(2020, 6666,
                    stamp, stamp + 1));
            System.out.println( Thread.currentThread().getName()+ " -B2- " + atomicStampedReference.getStamp());


        },"B").start();
    }
}


锁机制的了解


公平锁

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

非公平锁:java自带默认非公平锁,可以插队

//java源码
   public ReentrantLock() {
       //我们发现,这里默认就是不公平锁
        sync = new NonfairSync();
    }

```java
  public class 公平锁 {
        //创建一个公平锁
        private Lock lock = new ReentrantLock();
    
        //synchronized
    
        public static void main(String[] args) {
            Phone phone = new Phone();
                new Thread(() -> {
                    phone.sms();
                },"A").start();
                new  Thread(()->{
                    phone.sms();
                },"B").start();
        }
    }
    
    class  Phone{
        public  synchronized  void sms(){
            System.out.println(Thread.currentThread().getName()+"发短信");
            call();
        }
        public  synchronized  void  call(){
            System.out.println(Thread.currentThread().getName()+"打电话");
        }
    }
public class LockDemo {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone2.sms();
        },"A").start();

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

        System.out.println("=====================");
        Test te = new Test();
        new Thread(()->{
            te.sms();
        },"C").start();

        new Thread(()->{
            te.sms();
        },"D").start();

    }


}

class Phone2{
    private Lock lock = new ReentrantLock();

    public  void  sms(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"发消息");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        call();
    }

    public  void call(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+ "打电话");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

class Test{
    public  void  sms(){
        System.out.println(Thread.currentThread().getName()+"发消息");
        call();
    }
    public  void  call(){
        System.out.println(Thread.currentThread().getName()+ "打电话");
    }
}


可重入锁

可重入锁是

某个线程已经获得某个锁,可以再次获取锁而不会出现死锁



再次获取锁的时候会判断当前线程是否是已经加锁的线程,如果是对锁的次数+1,释放锁的时候加了几次锁,就需要释放几次锁。


代码中的锁的递归只是锁的一种表现及证明形式,除了这种形式外,还有另一种表现形式。同一个线程在没有释放锁的情况下多次调用一个加锁方法,如果成功,则也说明是可重入锁。


自旋锁

  /*
    自旋锁:
    我们自己定义一个自旋锁
     */
    public class 自旋锁 {
        AtomicReference<Thread> atomicStampedReference = new AtomicReference<>();
        /*
        //加锁
        所谓自旋锁
         */
        public  void  myLock(){
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "=> MY Lock");
            //自旋开始
            while (!atomicStampedReference.compareAndSet(null,thread)){
            }
        }
        /*
         解锁
         */
        public void myUnLock(){
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "=> MY UnLock");
            atomicStampedReference.compareAndSet(thread,null);
        }
    
    }
  public class Test_Self {
        public static void main(String[] args) {
    
            /*
            一般情况下使用的方式
    
            ReentrantLock reentrantLock = new ReentrantLock();
            reentrantLock.lock();
            reentrantLock.unlock();
      */
            /*
            使用我们自定义下的方式
            我们使用的是CAS比较的也就是compareAndSet
             */
            自旋锁  t = new 自旋锁();
    
            new Thread(()->{
                t.myLock();
    
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    t.myUnLock();
                }
    
            },"A").start();
    
            new Thread(()->{
                t.myLock();
    
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    t.myUnLock();
                }
            },"B").start();
    
        }
    }


悲观锁

总是以悲观的角度来看问题,当用户拿到锁之后,认为用户会修改


乐观锁

总是以乐观的方式看待问题,当用户每次拿到锁后,都不会对数据进行修改


死锁

两个线程强制一个资源造成的两个线程处于等待状态,在不经过人的干预的情况之下一直会保持这种状态,我们称之为死锁。

如何去避免死锁(避免死锁产生的条件)

如何去判断线程产生死锁问题

  public class 死锁{
        public static void main(String[] args) {
            String lockA = "lockA";
            String lockB = "lockB";
    
            new Thread(new MyTest(lockA,lockB),"A").start();
    
            new Thread(new MyTest(lockB,lockA),"B").start();
        }
    }
    
    class  MyTest implements  Runnable{
    
        private  String lockA;
        private  String lockB;
    
    
        public MyTest(String lockA, String lockB) {
            this.lockA = lockA;
            this.lockB = lockB;
        }
    
        @Override
        public void run() {
            synchronized (lockA){
                System.out.println(Thread.currentThread().getName() + "Lock A "  + "=> get " + lockB);
    
                //我们让线程A休眠2秒
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
    
                synchronized (lockB){
                    System.out.println(Thread.currentThread().getName() + "Lock B " + lockB + "=> get " + lockA);
                    //我们让线程A休眠2秒
                }
            }
        }
    }
    


1. 使用 jps -l 定位进程号



2. 使用 jstack 进程号 查看进程的信息 ,找到死锁问题

   **PS E:\IDEAProgram\juc-learning> jstack 2400**
   2022-10-09 21:13:37
   Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.261-b12 mixed mode):
   
   "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001f454e10800 nid=0x31ec wait
   ing on condition [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "B" #13 prio=5 os_prio=0 tid=0x000001f470d10800 nid=0x3690 waiting for moni
   tor entry [0x000000870e7ff000]
      java.lang.Thread.State: BLOCKED (on object monitor)
           at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
           - waiting to lock <0x0000000775b9f8a0> (a java.lang.String)        
           - locked <0x0000000775b9f8d8> (a java.lang.String)
           at java.lang.Thread.run(Thread.java:748)
   
   "A" #12 prio=5 os_prio=0 tid=0x000001f470d03000 nid=0x39e0 waiting for moni
   tor entry [0x000000870e6ff000]
      java.lang.Thread.State: BLOCKED (on object monitor)
           at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
           - waiting to lock <0x0000000775b9f8d8> (a java.lang.String)        
           - locked <0x0000000775b9f8a0> (a java.lang.String)
           at java.lang.Thread.run(Thread.java:748)
   
   "Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000001f470ba4000 nid=0x3
   ea0 runnable [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000001f470af0000 nid
   =0x3f94 waiting on condition [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000001f470aef000 nid=
   0x3168 waiting on condition [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000001f470ade000 nid=
   0xd1c waiting on condition [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000001f470add000 nid=
   0x1d30 waiting on condition [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000001f470adb000 nid=
   0x268c runnable [0x000000870dffe000]
      java.lang.Thread.State: RUNNABLE
           at java.net.SocketInputStream.socketRead0(Native Method)
           at java.net.SocketInputStream.socketRead(SocketInputStream.java:116
   )
           at java.net.SocketInputStream.read(SocketInputStream.java:171)     
           at java.net.SocketInputStream.read(SocketInputStream.java:141)     
           at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)      
           at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)       
           at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
           - locked <0x0000000775c8d1c0> (a java.io.InputStreamReader)        
           at java.io.InputStreamReader.read(InputStreamReader.java:184)      
           at java.io.BufferedReader.fill(BufferedReader.java:161)
           at java.io.BufferedReader.readLine(BufferedReader.java:324)        
           - locked <0x0000000775c8d1c0> (a java.io.InputStreamReader)        
           at java.io.BufferedReader.readLine(BufferedReader.java:389)        
           at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.
   java:49)
   
   "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001f46edb9800 nid=0xd
   64 waiting on condition [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001f46ee1e000 nid=0
   x10e8 runnable [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
   
   "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001f454eb7000 nid=0x2ee0 in
    Object.wait() [0x000000870dcfe000]
      java.lang.Thread.State: WAITING (on object monitor)
           at java.lang.Object.wait(Native Method)
           - waiting on <0x0000000775a08ee0> (a java.lang.ref.ReferenceQueue$L
   ock)
           at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)    
           - locked <0x0000000775a08ee0> (a java.lang.ref.ReferenceQueue$Lock)
           at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)    
           at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216) 
   
   "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001f454eb0000 nid=
   0x35e0 in Object.wait() [0x000000870dbff000]
      java.lang.Thread.State: WAITING (on object monitor)
           at java.lang.Object.wait(Native Method)
           - waiting on <0x0000000775a06c00> (a java.lang.ref.Reference$Lock) 
           at java.lang.Object.wait(Object.java:502)
           at java.lang.ref.Reference.tryHandlePending(Reference.java:191)    
           - locked <0x0000000775a06c00> (a java.lang.ref.Reference$Lock)     
           at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
   
   "VM Thread" os_prio=2 tid=0x000001f454ead800 nid=0x1f50 runnable
   
   "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000001f454e26000 nid=0x3f00
    runnable
   
   "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000001f454e28800 nid=0x3eb0
    runnable
   
   "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000001f454e29800 nid=0x3e84
    runnable
   
   "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000001f454e2d000 nid=0x3fb8
    runnable
   
   "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000001f454e32000 nid=0x3ca0
    runnable
   
   "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000001f454e32800 nid=0x3df8
    runnable
   
   "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000001f454e35800 nid=0x3630
    runnable
   
   "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000001f454e36800 nid=0x8ec 
   runnable
   
   "VM Periodic Task Thread" os_prio=2 tid=0x000001f470be6000 nid=0x21f4 waiti
   ng on condition
   
   JNI global references: 12
   
   
   Found one Java-level deadlock:
   =============================
   "B":
     waiting to lock monitor 0x000001f454eb3c68 (object 0x0000000775b9f8a0, a 
   java.lang.String),
     which is held by "A"
   "A":
     waiting to lock monitor 0x000001f454eb65a8 (object 0x0000000775b9f8d8, a 
   java.lang.String),
     which is held by "B"
   
   Java stack information for the threads listed above:
   ===================================================
   "B":
           at com.xh.juc.day07.???.MyTest.run(死锁.java:40)
           - waiting to lock <0x0000000775b9f8a0> (a java.lang.String)        
           - locked <0x0000000775b9f8d8> (a java.lang.String)
           at java.lang.Thread.run(Thread.java:748)
   "A":
           at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
           - waiting to lock <0x0000000775b9f8d8> (a java.lang.String)        
           - locked <0x0000000775b9f8a0> (a java.lang.String)
           at java.lang.Thread.run(Thread.java:748)
   
   Found 1 deadlock.

如何检测死锁:


1. 查看日志

2. 堆栈信息


轻量锁

① 当一个线程要进入同步块时,首先会创建一个Lock Record(锁记录)对象,该对象包含Displaced Mark Word和Owner。

② 锁记录对象会拷贝对象头中的Mark Word,即Displaced Mark Word。

③ 拷贝成功后,锁记录将owner指向对象头,然后尝试通过cas将对象头的Mark Word更改为指向Lock

Record的指针,并且将对象Mark Word的锁标志位设置为“00”。

④ 如果更新成功,则表示该对象处于轻量级锁定状态。如果失败,那么首先检测是否是可重入,如果可重入则进入执行。

⑤ 如果不可重入,则膨胀为重量锁。(有的文章说会进行自旋,有的说不进行自旋只要存在竞争就膨胀为重量锁,美团说当只有一个等待线程时,该线程自旋。当超过一定次数或者超过一个自旋线程时直接膨胀为重量锁)。

⑥ 释放锁时,使用cas对象头的Mark Word恢复,如果cas成功,则解锁成功。如果cas失败,则进入重量锁解锁流程。


重量锁

JDK1.6之前,锁没被优化,synchronized使用的是重量锁。重量锁需要操作系统帮忙,所以需要进行用户态到内核态的切换,同时还会带来线程上下文切换。

原理

Monitor对象分为3部分组成:Owner、EntryList和WaitSet

① JVM会创建一个Monitor对象,然后将锁对象的对象头中MarkWord改变成指向Monitor对象的指针。

② 当其中一个线程抢占到锁后,Monitor的Owner会置为该线程id,只能有一个。

③ 其余没抢到的,会被放置到EntryList中阻塞。

④ 进入WAITING状态的线程,会被放在WaitSet中。


排他锁

排他锁(EXclusive Lock),又称X锁、独占锁、写锁。针对行锁。

当有事务对数据加写锁后,其他事务不能再对锁定的数据加任何锁,又因为InnoDB对select语句默认不加锁,所以其他事务除了不能写操作外,照样是允许读的(尽管不允许加读锁)。

📢主要为了在事务进行写操作时,不允许其他事务修改。


偏向锁

轻量锁每次重入和退出时还是会执行cas操作,并且要创建锁记录对象。如果在大多数情况下,锁总是由一个线程多次获得,不存在多线程竞争,就可以使用偏向锁优化。

① 第一次使用CAS将线程ID设置到对象的Mark Word。

② 以后该线程进入同步块时,不需要CAS进行加锁,只会往当前线程的栈中添加一条Displaced Mark Word为空的Lock

Record中,用来统计重入的次数。



版权声明:本文为qq_45922256原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。