JUC学习笔记

JUC并发编程

  1. 什么是JUC

    image-20220126211447430

    就是指java中的这三个包的简称

    小知识回顾

    一个进程可以拥有多个线程,最少一个

    java默认有两个线程

    1. main线程
    2. GC线程

    java自身不能开启线程,是调用了本地的native方法

    线程的6个状态

    public enum State {
           
        	//运行
            NEW,
    
           
        	//运行
            RUNNABLE,
    
        	//阻塞
            BLOCKED,
    
          
        	//等待
            WAITING,
    
         
        	//超时等待
            TIMED_WAITING,
    
    
             
        	//终止
            TERMINATED;
        }
    
    
  2. 查看本机cpu数量(线程数)

    package com.zyc.test;
    
    public class Test {
        public static void main(String[] args) {
      //runtime查询当前可以使用的线程也就是cpu核数量
        System.out.println(Runtime.getRuntime().availableProcessors());
    	}
    }
    
  3. wait和sleep的区别

    1. 来自不同的类

    wait => Object

    sleep => Thread

    1. 关于锁的释放

      wait会释放锁,sleep不会

    2. 使用范围不同

      wait 必须在同步代码块中;

      sleep 可以在任何地方睡;

  4. Lock锁

    package com.zyc.test;
    
    public class Tickets {
    
        public static void main(String[] args) {
    
            ticket ticket = new ticket();
    //        new Thread();
            //接口可以new
    //        new Thread(new Runnable() {
    //            @Override
    //            public void run() {
    //            }
    //        });
            //lambad表达式的使用 ()—>{}
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"A").start();
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"B").start();
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            },"C").start();
        }
    }
    
    class ticket{
    
        private Integer t = 30;
    
    
        public void sale(){
            if(t>0){
                System.out.println(Thread.currentThread().getName()+" 卖出了第"+t+" 张票,剩余:"+t+" 张票");
                t--;
            }
        }
    }
    
    

    最简单加synchronized

    公平锁: 十分公平,必须先来后到~;

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

    使用了lock锁的代码

    package com.zyc.test;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Ticketlock {
        public static void main(String[] args) {
            ticket2 ticket2 = new ticket2();
    
            new Thread(()->{for(int i=0;i<40;i++) ticket2.sale(); },"A").start();
            new Thread(()->{for(int i=0;i<40;i++) ticket2.sale(); },"B").start();
            new Thread(()->{for(int i=0;i<40;i++) ticket2.sale(); },"C").start();
        }
    }
    
    class ticket2{
    
        private Integer t2 = 30;
        //创建可重入锁
        Lock lock = new ReentrantLock();
    
    
        public void sale(){
    
            lock.lock();//加锁
    
            try {
                if(t2>0){
                    System.out.println(Thread.currentThread().getName()+" 卖出了第"+t2+" 张票,剩余:"+t2+" 张票");
                    t2--;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();//解锁
            }
    
    
        }
    
    }
    
    

一.synchronized和lock锁的区别

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

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

3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);

lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

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

二.生产者消费者问题*******(重点)

  • 使用synchronized wait notify实现
  • 生产者消费者-》就是1.等待2.业务3.通知
public class A {
    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();
    }
}
class Data{
    //数字  资源类
    private int number = 0;

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

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

}

但是如果现在线程数从两个变成4个呢?

原先使用if判断对资源进行等待,只会进行一次判断

解决办法:使用while循环,可以防止虚假唤醒问题

image-20220127140146830

  • JUC方法实现
  • 使用await替换wait,使用signal替换notify
  • 其中是使用了condition这个类

image-20220127141909024

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

        new Thread(()->{for(int i=0;i<10;i++) {
            data.increment();
        }
        },"A").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.decrement();
        }},"B").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.increment();
        }
        },"C").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            data.decrement();
        }
        },"D").start();
    }
}
class Data2{
    //数字  资源类
    private int number = 0;

    //lock锁
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //+1
    public void increment()  {
        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();
        }
    }

    //-1
    public void decrement()  {
        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实现精确通知和唤醒

我们这里每一次用signal唤醒下一个需要使用的线程,这样就实现了精确的通知

/**
 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
 */

public class C {

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

class Data3{
    //资源类
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; //1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=1){
                //等待
                condition1.await();
            }
            //操作
            System.out.println(Thread.currentThread().getName()+",AAAAA");
            //唤醒指定的线程
            number=2;
            condition2.signal(); // 唤醒2

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

三.八锁问题

八锁实例

package com.zyc.lock8;

import java.util.concurrent.TimeUnit;


//锁一:使用synchronize锁住的对象,执行过程是谁先获得锁,谁就先执行,
// 原因:并不是顺序执行!是因为synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行!另外一个则等待!
public class locktest1 {
    public static void main(String[] args) throws InterruptedException {

        phone phone = new phone();
        new Thread(()->{
            phone.sendmsg();
        },"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            phone.call();
        }).start();
    }
}
class phone{
    public synchronized void sendmsg(){
        System.out.println("send");
    }

    public synchronized  void call(){
        System.out.println("call");
    }
}
package com.zyc.lock8;

import java.util.concurrent.TimeUnit;


//锁二:给第一个方法加延迟4秒,结果依然是第一个方法先执行,原因还是因为第一个方法先拿到了锁加锁,另一个则等待
public class locktest2 {
    public static void main(String[] args) throws InterruptedException {

        phone2 phone = new phone2();
        new Thread(()->{
            try {
                phone.sendmsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            phone.call();
        }).start();
    }
}
class phone2{
    public synchronized void sendmsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send");
    }

    public synchronized  void call(){
        System.out.println("call");
    }
}
package com.zyc.lock8;

import java.util.concurrent.TimeUnit;


//锁三:增加一个普通方法,则普通方法先执行,因为普通方法没有上锁
public class locktest3 {
    public static void main(String[] args) throws InterruptedException {

        phone3 phone = new phone3();
        new Thread(()->{
            try {
                phone.sendmsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            phone.call();
        }).start();

        new Thread(()->{
            phone.hello();
        }).start();
    }
}

class phone3{
    public synchronized void sendmsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send");
    }

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

    public void hello (){
        System.out.println("hello");
    }
}
package com.zyc.lock8;

import java.util.concurrent.TimeUnit;


//锁四:使用两个对象分别执行短信的电话操作,这时候上锁所得是两个对象,所以打电话没有延迟的方法先执行
public class locktest4 {
    public static void main(String[] args) throws InterruptedException {

        phone4 phone = new phone4();
        phone4 phone2 = new phone4();
        new Thread(()->{
            try {
                phone.sendmsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class phone4{
    public synchronized void sendmsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send");
    }

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

    public void hello (){
        System.out.println("hello");
    }
}
package com.zyc.lock8;

import java.util.concurrent.TimeUnit;


//锁五、六:这里我们使用static修饰,再加锁的静态同步方法上锁锁的是这个类的模板,就是说无论创建多少个对象都是会同时使用一把锁
public class locktest5 {
    public static void main(String[] args) throws InterruptedException {
        phone5 phone = new phone5();
        phone5 phone2 = new phone5();
        new Thread(()->{
            try {
                phone.sendmsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class phone5{
    public static synchronized void sendmsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send");
    }

    public static  synchronized  void call(){
        System.out.println("call");
    }

    public void hello (){
        System.out.println("hello");
    }
}
package com.zyc.lock8;

import java.util.concurrent.TimeUnit;


//锁七八:如果我们使用一个静态方法一个普通方法的话,普通方法直接执行就ok因为没有锁,
// 如果一个静态同步方法和一个同步方法的话,那么两个对象用的并不是一把锁,所以谁快谁先不影响
public class locktest6 {
    public static void main(String[] args) throws InterruptedException {
        phone6 phone = new phone6();
        new Thread(()->{
            try {
                phone.sendmsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{
            phone.hello();
        }).start();

    }
}

class phone6{
    public static synchronized void sendmsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("send");
    }

    public static  synchronized  void call(){
        System.out.println("call");
    }

    public void hello (){
        System.out.println("hello");
    }
}

四.集合类线程问题

list

package com.zyc.synchroCollection;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class synlist {
    //java.util.ConcurrentModificationException 并发修改异常!此时线程不安全
        public static void main(String[] args) {

            List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());
            List<String> list = new CopyOnWriteArrayList<>();
            //改变方法
            /**
             * 1.修改成vertor类线程安全
             * 2.使用Collections.synchronizedList(new ArrayList<>());
             * 3.使用我们的juc中的CopyOnWriteArrayList方法
             *
             * */

            for(int i=1;i<=10;i++){
                new Thread(()->{
                    arrayList.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(arrayList);
                },String.valueOf(i)).start();
            }

        }
    }

CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;

vector 和 CopyOnWriteArrayList的比较

  • vector底层都是用synchronized实现的
  • 但是CopyOnWriteArrayList底层都是用lock锁实现的

CopyOnWriteArrayList的效率更高

set

package com.zyc.synchroCollection;

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

public class synset {
    //同理:java.util.ConcurrentModificationException
        public static void main(String[] args) {
//        Set<String> hashSet = Collections.synchronizedSet(new HashSet<>());
            Set<String> hashSet = new CopyOnWriteArraySet<>();
            /**
             * 1.Collections.synchronizedSet(new HashSet<>());
             * 2.new CopyOnWriteArraySet<>();
             * hashset的底层就是hashmap
             *
             *
             * */
            for (int i = 1; i < 100; i++) {
                new Thread(()->{
                    hashSet.add(UUID.randomUUID().toString().substring(0,5));
                    System.out.println(hashSet);
                },String.valueOf(i)).start();
            }
        }
    }

hashset的底层就是hashmap

查看底层源码我们发现add方法就是使用了hashmap的key值不能重复就成了hashset

map

package com.zyc.synchroCollection;

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

public class synmap {
    public static void main(String[] args) {
        //map 是这样用的吗?  不是,工作中不使用这个
        //默认等价什么? new HashMap<>(16,0.75);
        Map<String, String> map = Collections.synchronizedMap(new HashMap<>()) ;
        Map map1 = new ConcurrentHashMap();

        /**
         * 1.使用Collections.synchronizedMap(new HashMap<>());处理;
         * 2.使用ConcurrentHashMap进行并发处理
         *
         * */
        //加载因子、初始化容量
        for (int i = 1; i < 100; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }

}

callable

image-20220128163153000

image-20220128163217162

image-20220128163228536

package com.zyc.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 {
            for (int i = 1; i < 10; i++) {
//            new Thread(new Runnable()).start();
//            new Thread(new FutureTask<>( Callable)).start();
                MyThread thread= new MyThread();
                //适配类:FutureTask
                FutureTask<String> futureTask = new FutureTask<>(thread);
                //放入Thread使用
                new Thread(futureTask,String.valueOf(i)).start();
                //获取返回值
                String s = futureTask.get();
                System.out.println("返回值:"+ s);
            }
        }
    }

    class MyThread implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println("Call:"+Thread.currentThread().getName());
            return "String"+Thread.currentThread().getName();
        }
    }

五.常用辅助类

CountDownLatch

减法计数器,等到数字减为0的时候继续操作

使用countDown进行减操作

使用await实行等待操作

//这是一个计数器  减法
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        //总数是6
        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 door");

    }

}

CyclickBarrier

加法计数器

public class CyclicBarrierDemo {
    public static void main(String[] args) {

        //主线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙~");
        });

        for (int i = 1; i <= 7; i++) {
            //子线程
            int finalI = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 收集了第 {"+ finalI+"} 颗龙珠");
                try {
                    cyclicBarrier.await(); //加法计数 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

Semaphore

信号量

semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!

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

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

public class SemaphoreDemo {
    public static void main(String[] args) {
        //停车位为3个
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    semaphore.acquire(); //得到
                    //抢到车位
                    System.out.println(Thread.currentThread().getName()+" 抢到了车位{"+ finalI +"}");
                    TimeUnit.SECONDS.sleep(2); //停车2s
                    System.out.println(Thread.currentThread().getName()+" 离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

六.读写锁

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;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            }).start();
        }
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) {
            int finalI = i;
            new Thread(()->{
                String o = mycache.get(String.valueOf(finalI));
            }).start();
        }
    }
}

class MyCache_ReadWriteLock{
    private volatile Map<String,String> map=new HashMap<>();

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //普通锁
    private Lock lock=new ReentrantLock();

    public void put(String key,String value){
        //加锁
        readWriteLock.writeLock().lock();
        try {
            //写入
            //业务流程
            System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock(); //解锁
        }
    }

    public String get(String key){
        //加锁
        String o="";
        readWriteLock.readLock().lock();
        try {
            //得到
            System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
            o = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
        return o;
    }
}

image-20220128172144246

七.阻塞队列BlockingQueue

在多线程并发处理,或者线程池的情况下使用阻塞队列

image-20220128175751322

整个阻塞队列的家族如下:Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;

BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列

阻塞队列的四种API

方式抛出异常不会抛出异常,有返回值阻塞 等待超时 等待
添加addofferputoffer(timenum,timeUnit)
移除removepolltakepoll(timenum,timeUnit)
判断队列首elementpeek--
  • 使用add remove操作
  • 这个就是在队列超出范围会抛出异常
    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"));
        //抛出异常:java.lang.IllegalStateException: 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());
    }
  • 使用offer pull
  • 这个就是在队列超出不抛出异常会返回false和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"));
    //添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常
    System.out.println(blockingQueue.offer("d"));

    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    //弹出 如果没有元素 只会返回null 不会抛出异常
    System.out.println(blockingQueue.poll());
}
  • 使用put take
  • 这个就是在队列超出或者没有元素时会进行等待,导致程序一直等待
 /**
     * 等待 一直阻塞
     */
    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());
    }
  • 使用offer pull带参的方法
  • 可以设置对应的超时时间,如果超时的话会放弃对应的操作
/**
 * 等待 超时阻塞
 *  这种情况也会等待队列有位置 或者有产品 但是会超时结束
 */
public static void test4() throws InterruptedException {
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
    blockingQueue.offer("a");
    blockingQueue.offer("b");
    blockingQueue.offer("c");
    System.out.println("开始等待");
    blockingQueue.offer("d",2, TimeUnit.SECONDS);  //超时时间2s 等待如果超过2s就结束等待
    System.out.println("结束等待");
    System.out.println("===========取值==================");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println("开始等待");
    blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了
    System.out.println("结束等待");
}

SynchronousQueue同步队列

同步队列 没有容量,也可以视为容量为1的队列;

进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;

使用put方法 和 take方法;

SynchronousQueue和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

/**
 * 同步队列
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<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 {
                System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
//                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());
//                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+" Take "+synchronousQueue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

八.线程池

三大方法,7大参数,四种拒绝策略

因为程序使用资源会占用资源,为了优化资源使用,出现了池化技术

因为资源的创建和销毁都会消耗内存

池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

线程池的好处:

1、降低资源的消耗;

2、提高响应的速度;

3、方便管理;

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

三大方法

三大方法

  1. ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  2. ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  3. ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的,缓存线程池
//工具类 Executors 三大方法;
public class Demo01 {
    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
        ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的

        //线程池用完必须要关闭线程池
        try {

            for (int i = 1; i <=100 ; i++) {
                //通过线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+ " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

7个参数

首先分析三大方法的底层实现都是使用了ThreadPoolExecutor创建的

使用Excutors创建线程池会造成资源消耗,在阿里巴巴开发手册中也写了强制我们直接使用ThreadPoolExecutor创建线程池,节约资源消耗

  • 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.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

image-20220204211901163

7大参数的实现

  • 如果当前线程数小于corePoolSize核心线程数的话,每次任务需要线程,就创建一个新线程
  • 如果线程数大于corePoolSize但是还没有达到maximumPoolSize最大数的话,首先会尝试将任务添加到任务队列,添加成功后任务会进行等待,等到资源释放后线程在进行操作,此时最大任务容纳量就是核心线程数+阻塞队列任务数
  • 如果任务队列数大于maximumPoolSize,会创建新线程进行任务处理,此时最大容纳量就是最大线程数+阻塞队列数
  • 如果线程数大于maximumPoolSize则会进行拒绝测略

个人理解:我们可以理解为银行办理业务,线程就是柜台,我们的核心线程数就是一直营业的柜台,阻塞队列就是休息区位置,在达到核心线程数前,每来一个人办理业务就开一个柜台,当来的人多了就去休息区休息,如果人再多我们就开放柜台,直到达到最大柜台数,此时如果再来人办理业务,我们就使用拒绝测略赶走。超时等待就是如果非核心柜台开放后指定时间内没有业务我们就让他下班。

四种拒绝测略

  • new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

    超出最大承载,就会抛出异常:队列容量大小+maxPoolSize

  • new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理

  • new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。

  • new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

CPU密集型和IO密集型

也就是优化在哪里

CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

我们可以通过代码System.out.println(Runtime.getRuntime().availableProcessors());

获得cpc数量

I/O密集型:

在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

九、四大函数式接口

新要求:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口:就是内部只有一个方法的接口

满足函数式接口的类我们就可以使用lambda表达式简化开发

查看jdk1.8官方文档在juf包下我们可以看到四大函数式接口

image-20220205160351311

consumer消费型接口

​ 没有返回值只有输入的值

image-20220205160405848

function函数型接口

​ 参数一为传入值,参数二为返回值

image-20220205160413173

predicate断定型接口

​ 返回值为布尔类型,可以传参

suppier供给型接口

​ 没有参数只有返回值

十、steam流式计算

public class Test {
    public static void main(String[] args) {
        User user1 = new User(1,"a",21);
        User user2 = new User(2,"b",22);
        User user3 = new User(3,"c",23);
        User user4 = new User(4,"d",24);
        User user5 = new User(5,"e",25);
        User user6 = new User(6,"f",26);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);

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

如何开启流:对象.steam()

满足链式编程,使用steam流操作可以提高效率,建议使用流式计算

十一、ForkJoin

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:MapReduce 核心思想->把大任务拆分为小任务!

就是将任务分成子任务,子任务分别处理结果最后得到最终结果

ForkJoin的特点:工作窃取

当两条线程并行执行的时候,如果一条线程先执行完,那么这条线程会会过来抢夺另一条线程的任务

如何使用ForkJoin?

1、通过ForkJoinPool来执行

2、计算任务 execute(ForkJoinTask<?> task)

3、计算类要去继承ForkJoinTask

ForkJoin的计算类!

package com.haust.forkjoin;

import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务!
 * 3000   6000(ForkJoin)  9000(Stream并行流)
 * // 如何使用 forkjoin
 * // 1、forkjoinPool 通过它来执行
 * // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
 * // 3. 计算类要继承 RecursiveTask(递归任务,有返回值的)
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start;  // 1
    private Long end;    // 1990900000

    // 临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    // 计算方法
    @Override
    protected Long compute() {
        if ((end-start)<temp){
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }else { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列

            return task1.join() + task2.join();
        }
    }
}

测试类

package com.ogj.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();
        test2();
        test3();
    }

    /**
     * 普通计算
     */
    public static void test1(){
        long star = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 1; i < 20_0000_0000; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
        System.out.println(sum);
    }

    /**
     * 使用ForkJoin
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 20_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long aLong = submit.get();
        System.out.println(aLong);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
    }


    /**
     * 使用Stream 并行流
     */
    public static void test3(){
        long star = System.currentTimeMillis();
        //Stream并行流()
        long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
    }
}

十二、异步回调

package com.haust.future;

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

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

        // 有返回值的 supplyAsync 异步回调
        // ajax,成功和失败的回调
        // 返回的是错误信息;
        CompletableFuture<Integer> completableFuture = 
            					CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()
                               			+"supplyAsync=>Integer");
            int i = 10/0;
            return 1024;
        });

        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t); // 正常的返回结果
            System.out.println("u=>" + u); 
            // 错误信息:
            // java.util.concurrent.CompletionException: 
            // java.lang.ArithmeticException: / by zero
            
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; // 可以获取到错误的返回结果
        }).get());

        /**
         * succee Code 200
         * error Code 404 500
         */
    }
}

十三、JMM(java内存模型)

volatile:Volatile 是 Java 虚拟机提供轻量级的同步机制,类似于synchronized 但是没有其强大。

1、保证可见性

2、不保证原子性

3、防止指令重排

关于JMM的一些同步的约定:

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

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

3、加锁和解锁是同一把锁。

内存交互八种操作:

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和writ操作在某些平台上允许例外)

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操作之前,必须把此变量同步回主内存

image-20220206194428151

十四、volatile

1.保证可见性

package com.haust.tvolatile;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    // 不加 volatile 程序就会死循环!
    // 加 volatile 可以保证可见性
    private volatile static int num = 0;

    public static void main(String[] args) { // main

        new Thread(()->{ // 线程 1 对主内存的变化不知道的
            while (num==0){

            }
        }).start();

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

        num = 1;
        System.out.println(num);

    }
}

2.不保证原子性

package com.haust.tvolatile;

import java.util.concurrent.atomic.AtomicInteger;

// volatile 不保证原子性
public class VDemo02 {

    // volatile 不保证原子性
    // 原子类的 Integer
    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add(){
        // num++; // 不是一个原子性操作
        num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
    }

    public static void main(String[] args) {

        //理论上num结果应该为 2 万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000 ; j++) {
                    add();
                }
            }).start();
        }

        // 判断只要剩下的线程不大于2个,就说明20个创建的线程已经执行结束
        while (Thread.activeCount()>2){ // Java 默认有 main gc 2个线程
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() 
                           							+ " " + num);
    }
}

解决办法:使用juc下的原子类解决原子性不需要使用synchronzied和lock

// volatile 不保证原子性
 // 原子类的 Integer
 private volatile static AtomicInteger num = new AtomicInteger();

 public static void add(){
    // num++; // 不是一个原子性操作
    num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
 }

3.避免指令重排

计算机在编译代码时,指令顺序可能会进行重新排序,但是因为有关联约束所以也不是随机排序

我们使用volatile可以避免指令重排:因为其中有一个内存屏障

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

image-20220206195048469

十五、单例模式

饿汉式单例模式:

package com.zyc.playSingle;


//饿汉式单例模式
public class hungryMan {
    //私有构造器,这样就不能通过构造器创建实例了
    private hungryMan(){

    }

    private static final hungryMan HUNGR_MAN = new hungryMan();

    //只要有就给:饿汉的特点,使用static这样可以直接用类访问
    public static hungryMan getHungrMan(){
        return HUNGR_MAN;
    }
}

class t1{
    public static void main(String[] args) {
        hungryMan hungrMan = com.zyc.playSingle.hungryMan.getHungrMan();
        hungryMan hungrMan1 = hungryMan.getHungrMan();
        hungryMan hungrMan2 = hungryMan.getHungrMan();

        System.out.println(hungrMan);
        System.out.println(hungrMan1);
        System.out.println(hungrMan2);
    }
}

懒汉式单例模式:

package com.zyc.playSingle;


//懒汉式单例模式
public class lazyMan {

    private lazyMan(){

    }

    private  static lazyMan LAZY_MAN ;


    public static lazyMan getLAZY_MAN(){
        if (LAZY_MAN==null){
            LAZY_MAN= new lazyMan();
            return LAZY_MAN;
        }
        return LAZY_MAN;
    }

}
class t2{
    public static void main(String[] args) {
        lazyMan lazy_man = lazyMan.getLAZY_MAN();
        lazyMan lazy_man1 = lazyMan.getLAZY_MAN();
        System.out.println(lazy_man);
        System.out.println(lazy_man1);
    }
}

单线程情况下我们使用单例模式是安全的,那么多线程呢?

package com.zyc.playSingle;

public class DCLLazyMan {

    private  DCLLazyMan(){
      System.out.println(Thread.currentThread().getName()+"ing...."); //测试 每次打印创建单例的线程名
    }

    private static DCLLazyMan dclLazyMan;
	//懒汉模式
    public static DCLLazyMan getDclLazyMan(){
        if(dclLazyMan==null){
            dclLazyMan = new DCLLazyMan();
        }
        return dclLazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{

                DCLLazyMan.getDclLazyMan();

            }).start();
        }
    }
}

这里我们使用10个线程创建单例,结果如下:

image-20220207180605412

image-20220207180617899

image-20220207180627601

我们看到结果不同,所以多线程情况下这个是线程不安全

解决办法:加锁

//双重检测锁模式 DCL懒汉式
    public static DCLLazyMan getDclLazyMan(){
        if (dclLazyMan==null){
            synchronized (DCLLazyMan.class){
                if(dclLazyMan==null){
                    dclLazyMan = new DCLLazyMan();
                }
            }
        }

        return dclLazyMan;
    }

使用双重检测锁我们看到结果如下:

image-20220207182106292

image-20220207182118176

结果现在满足了我们的需求,那么已经完全保证线程问题了吗?

指令重排

原因:创建对象dclLazyMan = new DCLLazyMan();这行代码不是一个原子性操作,可能会造成指令重排(1.分配内存空间2.初始化对象3.对象指向内存空间//这三步的顺序会重新排序,有可能先进行3操作在进行2)

所以我们需要使用volatile避免指令重排

更改后代码如下:

package com.zyc.playSingle;

public class DCLLazyMan {

    private  DCLLazyMan(){
        System.out.println(Thread.currentThread().getName()+"ing....");
    }

    private volatile static DCLLazyMan dclLazyMan;

    //双重检测锁模式 DCL懒汉式
    public static DCLLazyMan getDclLazyMan(){
        if (dclLazyMan==null){
            synchronized (DCLLazyMan.class){
                if(dclLazyMan==null){
                    dclLazyMan = new DCLLazyMan();
                }
            }
        }

        return dclLazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{

                DCLLazyMan.getDclLazyMan();

            }).start();
        }
    }
}

使用反射破解单例模式

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        DCLLazyMan dclLazyMan1 = DCLLazyMan.getDclLazyMan();
        Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

image-20220207183514212

使用反射后可以发现创建两个对象不同;

解决办法,构造器进行判断

private static boolean flag  = false;

    private  DCLLazyMan(){
        synchronized (DCLLazyMan.class){
            if (flag==false){
                flag=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }

        System.out.println(Thread.currentThread().getName()+"ing....");
    }

image-20220207184139154

但是我们上面是用反射创建了一个对象和原本创建的对象进行比较,如果全部使用反射创建对象呢?

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();
        DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

image-20220207184432521

解决办法:使用标识符

private static boolean flag  = false;

    private  DCLLazyMan(){
        synchronized (DCLLazyMan.class){
            if (flag==false){
                flag=true;
            }
            else{
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }

        System.out.println(Thread.currentThread().getName()+"ing....");
    }

image-20220207184837259

但是如果通过反编译或者其他手段获得我们设置的标识符呢?

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        Field flag = DCLLazyMan.class.getDeclaredField("flag");
        flag.setAccessible(true);
        Constructor<DCLLazyMan> declaredConstructor = DCLLazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DCLLazyMan dclLazyMan = declaredConstructor.newInstance();

        flag.set(dclLazyMan,false);
        DCLLazyMan dclLazyMan1 = declaredConstructor.newInstance();
        System.out.println(dclLazyMan);
        System.out.println(dclLazyMan1);
    }

image-20220207185224041

通过修改标识符我们单例模式又被破坏了

解决办法:通过源码分析使用枚举

image-20220207185432060

我们下面使用枚举类型进行分析

创建枚举类然后利用反射创建对象:

package com.zyc.playSingle;

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

    //enum 是什么? 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();
            declaredConstructor.setAccessible(true);
            enumSingle instance2 = declaredConstructor.newInstance();
            System.out.println(instance1);
            System.out.println(instance2);
        }
    }

报错如下:

image-20220207195746059

我们发现结果跟源码中读的不同:

经过JAD工具进行反编译我们可以得到这里不是一个空参构造器,而是一个有参构造器

Constructor<enumSingle> declaredConstructor = enumSingle.class.getDeclaredConstructor(String.class,int.class);

修改后:image-20220207200046413

抛出异常正确,成功使用枚举类避免了反射的破坏

十六、CAS

cas是什么:java层面compareAndSet(预期值,更新值)

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

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

image-20220209162452388

image-20220209162502191

源码分析底层使用了自旋锁

自旋锁:就是不断循环直到达到条件解锁

CAS操作调用了内存进行存储效率很高,但是不是Java直接进行操纵

而是调用了unsafe这个类,这个类调用了底层的c++

image-20220209162900814

总结:

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

缺点:

  • 循环会耗时;
  • 一次性只能保证一个共享变量的原子性;
  • 它会存在ABA问题

ABA问题

就是当一个线程更改当前内存信息,进行操作后又将当前信息变更回去,但是另一个线程读取后还是原来的值,没有办法判断是否经过了操作

解决办法:使用乐观锁

设置一个时间戳,每次对当前时间戳进行++操作

十七、锁

1.公平锁和非公平锁

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

public ReentrantLock() {
    sync = new NonfairSync();
}

非公平锁:非常不公平,允许插队的,可以改变顺序。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.可重入锁也叫递归锁

拿到外面锁里面的锁就自动获得

两种实现方法

public class Demo01 {
    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()+"=> sms");
        call();//这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

//lock
public class Demo02 {

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

}
class Phone2{

    Lock lock=new ReentrantLock();

    public void sms(){
        lock.lock(); //细节:这个是两把锁,两个钥匙
        //lock锁必须配对,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}

  • lock锁必须配对,相当于lock和 unlock 必须数量相同;
  • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

3.自旋锁


    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

自旋锁的实现

public class SpinlockDemo {

    //int 0
    //thread null
    AtomicReference<Thread> atomicReference=new AtomicReference<>();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"===> mylock");

        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");
        }
    }


    //解锁
    public void myunlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }

}

测试类

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


        //使用CAS实现自旋锁
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);


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

4.死锁

package com.ogj.lock;

import java.util.concurrent.TimeUnit;

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"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+"===>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
            }
        }
    }
}

如果出现死锁问题怎么进行解决:使用idea的terminal里使用jps调试类似linux的ps -ef

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

2、使用jstack 进程进程号 找到死锁信息
lic void myunlock(){
Thread thread=Thread.currentThread();
System.out.println(thread.getName()+"===> myUnlock");
atomicReference.compareAndSet(thread,null);
}

}


测试类

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


        //使用CAS实现自旋锁
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);


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

4.死锁

package com.ogj.lock;

import java.util.concurrent.TimeUnit;

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"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+"===>get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
            }
        }
    }
}

如果出现死锁问题怎么进行解决:使用idea的terminal里使用jps调试类似linux的ps -ef

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

2、使用jstack 进程进程号 找到死锁信息

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者



你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值