JUC并发编程

1.传统的synchronized

我们实际上去公司写线程的代码时,不能让我们的资源类去继承Thread或者实现Runnable接口,我们应该将资源类完全隔离开来,它里面就只有属性和方法。

//基本的买票例子
//真正的多线程开发,公司中的开发一定要降低耦合性,线程就是一个单独的资源类,没有任何的附属操作
//里面包含有1.属性  2.方法
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发:多个线程操作同一个资源类
        Ticket ticket = new Ticket();

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

//资源类 OOP编程
class Ticket{
    //属性,方法
    private int number = 50;

    //买票的方式
    //synchronize本质就是一个队列
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);
        }
    }
}

   

2.Lock接口

2.1 Lock接口的实现类

  • ReentrantLock:可重入锁(常用的)
  • ReadLock:读锁
  • WriteLock:写锁

 

 

 我们通过运用Lock的实现类来加锁

2.2 ReentrantLock

我们先来看一下ReentrantLock的构造器,它默认是返回一个非公平锁,这让的目录就是提高运行效率,比如说

 

 

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

非公平锁:十分不公平,可以插队

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

//基本的买票例子
//真正的多线程开发,公司中的开发一定要降低耦合性,线程就是一个单独的资源类,没有任何的附属操作
//里面包含有1.属性  2.方法
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //并发:多个线程操作同一个资源类
        Ticket2 ticket = new Ticket2();

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

//资源类 OOP编程
//1.new ReentrantLock
//2.加锁
//3.解锁
class Ticket2{
    //属性,方法
    private int number = 50;

    Lock lock = new ReentrantLock();

    //买票的方式
    //synchronize本质就是一个队列
    public void sale(){
        lock.lock();//加锁
        try{
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

  

3. Lock和synchronized的区别

  1. synchronized是一个内置的java关键字,Lock是一个java类
  2. synchronized无法判断获取锁的状态,Lock可以判断是否获得锁了
  3. synchronized会自动释放锁,lock必须要自动释放锁,如果不释放,死锁
  4. synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);lock锁就不一定等待下去通过lock.tryLock()
  5. synchronized 默认是可重入锁,不可以中断的,非公平。因为它是一个关键字,这些特性都是不能更改的。Lock,可重入锁,可以判断锁,非公平(可以自己设置)
  6. synchronized 适合锁少量的代码同步问题,Lock适合锁大量的资源。

4. 生产者和消费者问题

4.1 synchronized 版本(下面这种只有一个生产者消费者那么就没有问题,如果有两个生产者两个消费者或者更多那么就会产生很多问题)

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 20; 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()+"加操作");
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"减操作");
        this.notifyAll();
    }
}

  

 

 这里一定要用while,if只会判断一次,等待应该总是出现在循环中。不能说判断条件中途改变了就立刻不wait了

 

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

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

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

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

}

//等待,业务,通知
class Data{//数字 资源类
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        while (number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"加操作");
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        while (number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"减操作");
        this.notifyAll();
    }
}

  

4.2 JUC版本(Condition接口和Lock平级)

 

 condition就是替代同步监视,替换掉我们之前用的Synchronized的版本

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

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"A").start();

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

        },"B").start();

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

        },"C").start();

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

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

}

//等待,业务,通知
class Data2{//数字 资源类
    private int number = 0;

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

    //+1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0){
                condition.await();
            }
            number++;
            condition.signalAll();
            System.out.println(Thread.currentThread().getName()+"加操作");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0){
                condition.await();
            }
            number--;
            condition.signalAll();
            System.out.println(Thread.currentThread().getName()+"减操作");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

  

那么为什么要用condition呢?

condition除了可以替换wait和notifyall更重要的一点功能使它强于sychronized就是它可以精准的通知和唤醒线程,notify()只能随机唤醒一个线程,是由线程调度器随机分配的,notifyall它默认唤醒所有线程。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//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.print1();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.print2();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data3.print3();
            }
        },"C").start();
    }
}

class Data3{//资源类

    private int number = 1;//1A 2B 3C

    private Lock lock = new ReentrantLock();
    //创建三个不同的监视器,通过监视器来判断我们到底应该唤醒的是哪个人
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void print1(){
        lock.lock();
        try {
            //业务,判断是否等待的过程,判断是否执行,通知
            while (number!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"在执行");
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print2(){
        lock.lock();
        try {
            //业务,判断是否等待的过程,判断是否执行,通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"在执行");
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void print3(){
        lock.lock();
        try {
            //业务,判断是否等待的过程,判断是否执行,通知
            while (number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"在执行");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }


}

  

这种精准调用的应用:生产线:下单--》支付--》交易--》物流

5. 八锁现象

锁的是谁?

  • new 出来的对象
  • new 出来所依据的class模板

5.1 synchronized用在方法上锁的是谁?(调用方法的对象!!!)

import java.util.concurrent.TimeUnit;

/**
 * 标准情况下,是先打印发短信还是先打印打电话呢?
 * 那我们要是在这个sendSms中去加一个sleep呢,结果又会怎样呢?
 * 其实这里无论怎么变化都是先会打印发短信,再打印打电话的。
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

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

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

class Phone{

    //synchronized其实锁的是方法的调用者,这也就不难解释上面我们只创建了一个Phone对象
    public synchronized void sendSms() {
        System.out.println("发短信");
    }

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

 

 

 

 

5.2 增加一个普通方法的情况,关于主进程是否需要拿到锁中的对象呢

import java.util.concurrent.TimeUnit;

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

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

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

class Phone2{

    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call() {
        System.out.println("打电话");
    }
    
    //这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

 

 

 

 

5.3 加了static 静态同步方法的情况

import java.util.concurrent.TimeUnit;

public class Test3 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();

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

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

class Phone3{

    //static 静态方法
    // 类一加载了就有了,加了static的同步方法锁的就是方法调用者的class
    // 当然我们也知道无论有多少个对象都只有一个class
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

  

 

 

5.4 普通同步方法静态同步方法同时在

import java.util.concurrent.TimeUnit;

/**
 * 锁的东西都不一样
 */
public class Test4 {
    public static void main(String[] args) {
        Phone4 phone = new Phone4();
        new Thread(()->{
            phone.sendSms();
        },"A").start();

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

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

class Phone4 {
    //静态同步方法
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    //普通同步方法
    public synchronized void call() {
        System.out.println("打电话");
    }
}

  

 

 6. 集合类不安全

6.1 CopyOnWriteArrayList

首先Vector是线程安全的,ArrayList是线程不安全的

 

 

 

 

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.lang.reflect.Array;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

// java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
    public static void main(String[] args) {

        /**
         * 原本:List<String> list = new ArrayList<>();
         * 解决方法:
         * 1.将ArrayList换成Vector List<String> list = new Vector<>();
         * 2.采用集合的顶级工具类来进行一个转化 List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3.采用JUC包下的 CopyOnWrite:写入时复制(提高计算机程序设计领域的一种优化策略)List<String> list = new CopyOnWriteArrayList<>();
         * 为什么了要有这个东西呢,因为在多线程写入的时候可能出现写入覆盖的问题,在写入的时候呢我们先copy一份再写入再放回去。
         * 读写分离 (写入的时候复制一份来写)
         * CopyOnWriteArrayList 比 Vector 牛逼在那里,只要有synchronized方法效率就比较低,Vector用的是synchornized
         * CopyOnWriteArrayList 用的是Lock
         */
        List<String> list = new CopyOnWriteArrayList<>();

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

        }
    }
}

 

6.2 CopyOnWriteArraySet

 

 

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

public class SetTest {
    public static void main(String[] args) {
        Set<String> set1 = new HashSet<>();
        //方法一
        Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
        //方法二
        Set<String> set3 = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}

  

扩展:HashSet的底层就是一个HashMap,就是用了它的这个key,key是不重复的。

 

 

 HashSet.add(),其实就是将你要存的数据当作HashMap的key存进去罢了。key不会重复,set不允许有重复的值。

 

PRESENT其实就是一个固定的不变的值。

 

 

 6.3 ConcurrentHashMap

HashMap构造方法中:

 

 

 

 

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

public class MapTest {
    public static void main(String[] args) {
        //map是这样用的吗? 不是,工作中不用这样的HashMap
        // 默认等价于什么? Map<String, Object> map = new HashMap<>(16,0.75);
        Map<String, Object> map = new HashMap<>();

        //方法一:
        Map<Object, Object> objectObjectMap = Collections.synchronizedMap(new HashMap<>());

        //方法二:
        Map<String,Object> map1 = new ConcurrentHashMap<>();
        //加载因子,初始容量

        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}

  

7.Callable

 

 

  1.  可以有返回值
  2. 可以抛出异常
  3. 方法不同,run()/call()

 

 

 

 

 

 

 又因为线程只能通过Thread启动,怎么样才可以让Thread启动callable呢,就要想办法让callable和runnable扯上关系。

  • 下面是Runnable的相关接口和实现类:

 

  • 然后找到这个FutureTask实现类:

  • 然后找到FutureTask它的构造方法:

 

 

 

 

 

 

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

public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>(Callable)).start();

MyThread myThread = new MyThread();
//适配类
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会缓存,不会打印两遍的!!

//获取callable的返回结果
Object o = futureTask.get();//这个get方法可能产生阻塞,如果是耗时的操作要等待,放在最后或者异步通讯来处理
System.out.println(o);
}
}

class MyThread implements Callable<String> {
@Override
public String call() {
System.out.println("call()");
return "123";
}

}

  

细节:

  1. 有缓存
  2. 拿返回值结果可能会有阻塞

8.JUC常用的辅助类(必会)

8.1 CountDownLatch(减法计数器,一般在必须要执行的任务的时候去使用)

这里我举一个例子来解释一下这个,比如说有6个人在教室里,那么老师呢就必须等到这六个学生都出教室了才会把门给锁上,也就是说要等这六个线程都执行完毕了才可以。

import java.util.concurrent.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()+"  go out!");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }


        //等待计数器归零,再向下执行,这个一定要有没有的话会出现门关了人还没出来完
        countDownLatch.await();
        System.out.println("close door!");
        //倒计时完毕会执行什么操作
        //countDownLatch.countDown();-1
    }
}

  

原理:

  1. countDownLatch.countDown();-1
  2. countDownLatch.await();等待计数器归零,再向下执行。

8.2 CyclicBarrier(加法计数器)

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

public class CyclicBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        /**
         * 集齐七颗龙珠召唤神龙
         */
        //这里CyclicBarrier的构造器中第二个参数是一个runnable接口,可以写成一个lambda表达式
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });

        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            //lambda表达式是new的一个类是拿不到我们这里的i的,只能通过一个final来拿
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集了第"+temp +"个龙珠");
                try {
                    //这个要放在里面因为这个方法会执行-1,
                    // 放在外面永远减不了1,这个方法并不是只承担了普普通通的判断结束任务哦!
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

  

8.3 Semaphore(信号量)

 

 

 我举个例子来解释一下:就好比抢车位,就好比有六个车子有三个车子想去停,那么只有拿到这个车位了才可以去停车

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

public class SemaphoreDemo {
    public static void main(String[] args) {
        //默认线程数量:把它理解成停车位 限流的时候我们也会用到
        Semaphore semaphore = new Semaphore(3);

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

  

原理:

  1. acquire()获取,假设已经满了,就会等待被释放位置
  2. release()释放,会将当前的信号量释放,然后唤醒等待的线程

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

9.读写锁(共享锁和独占锁)

 

 

 

 

 

 

 

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 myCache = new MyCache();

        //这五个线程只做写入的操作
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp);
            },String.valueOf(i)).start();
        }

        //这五个线程只做读取的操作
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }

}

/**
 * 自定义缓存
 */

class MyCache{
    //读写锁比ReentrantLock有更加细粒度的控制罢了,你拿ReentrantLock也可,但是我们读写的时候都会用这个
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private volatile Map<String,Object> map = new HashMap<>();

    //存,写的过程 ,写入的时候只希望同时只有一个线程在写
    public void put(String key,Object value){
        //可以看到和lock的操作非常相似
        readWriteLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+"写入ok");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }

    }

    //取,读的过程,读的时候所有人都可以读
    public void get(String key){
        readWriteLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"读入"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读入ok");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }

    }

}

  

那么加入这个读入锁的意义在哪呢?

读锁是读的时候共享,在读-写的时候是互斥的,也就是说,在读的时候不能写,共享的读锁是为了锁住写线程啊

10.阻塞队列

首先队列有个特性FIFO,先进先出

 

队列的不得不阻塞:

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

 

 

 

 

  •  那么BlockingQueue到底是个什么东西呢?

它其实是和List还有Set是同级的,都是继承自Collection接口,那么BlockingQueue下面同样也有ArrayBlockingQueue和LinkedBlockingQueue。

 

  • 什么情况下还会使用BlockingQueue呢?

比如在多线程并发的时候A要调用B,要等待B先执行完,线程池中也经常用到。

 

上面的图我还还可以发现这个BlockingQueue还有一个父接口叫做Queue,我们来看一下这个Queue,Queue其实是作为Collection和BlockingQueue中间的一个接口存在的

 

 

 

 

 

 总览图:

 

 

 

10.1 阻塞队列的四组常用API(针对实现类ArrayBlockingQueue)

 

 

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class TestBlockingQueue {
    public static void main(String[] args) throws InterruptedException {
//        test1();
//        test2();
//        test3();
        test4();
    }

    /**
     * 第一种:抛出异常
     */
    public static void test1(){
        //这里<>里面写的不是泛型,是要写队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.add("A"));
        System.out.println(arrayBlockingQueue.add("B"));
        System.out.println(arrayBlockingQueue.add("C"));

        //查看队首元素是谁
        System.out.println(arrayBlockingQueue.element());
        //会抛出异常IllegalStateException: Queue full
        System.out.println(arrayBlockingQueue.add("D"));


        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        //会抛出异常:java.util.NoSuchElementException
        System.out.println(arrayBlockingQueue.remove());
    }

    /**
     * 第二种:不抛出异常,有返回值
     */
    public static void test2(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(arrayBlockingQueue.offer(1));
        System.out.println(arrayBlockingQueue.offer(2));
        System.out.println(arrayBlockingQueue.offer(3));

        //检测队首元素
        System.out.println(arrayBlockingQueue.peek());

        //不抛出异常返回一个false
        System.out.println(arrayBlockingQueue.offer(3));

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        //不抛出异常返回一个null
        System.out.println(arrayBlockingQueue.poll());
    }

    /**
     * 等待,阻塞(一直阻塞)
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        //一直阻塞,没有返回值
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");
        //队列一直阻塞,没有位置了,也不会报错也不会停止运行
        arrayBlockingQueue.put("d");
        //检测队首元素的方法用的就比较少了

        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        //它会一直等待一直阻塞,也不会报错也不会停止运行
        System.out.println(arrayBlockingQueue.take());
    }

    /**
     * 等待,阻塞(等待超时,就是超过时间了就不等待了)
     */
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        
        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        arrayBlockingQueue.offer("c");
        //后面两个参数分别是超时时间和超时单位,等待超过两秒就退出
        arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        //跟上面是一样的
        arrayBlockingQueue.poll(2,TimeUnit.SECONDS);
    }
}

  

10.2 同步队列(BlockingQueue的有一个实现类SynchronousQueue)

没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素,往里面put一个元素必须的先取出来,才能往里面再put,可以看到打印结果一定是这样交替执行的

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

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(()->{

            try {
                System.out.println(Thread.currentThread().getName()+" put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName()+" put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName()+" put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1").start();

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

  

 

 

11. 线程池(重点(三大方法、7大参数、4种拒绝策略))

不要忘了线程池里是通过阻塞队列来管理运行多个线程在跑哦!!

11.1 池化技术

程序的运行,本质:占用系统的资源!优化资源的使用!因此演变出来池化技术

线程池,连接池,内存池,对象池。。。。。。创建和销毁非常的消耗我们的资源

 

池化技术:事先准备好一些资源,然后有人要用就来我这个地方拿,然后用完之后还给我。

 

线程池的好处:

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

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

11.2 线程池三大方法

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

public class Demo01 {
    public static void main(String[] args) {
        //Executors其实就是一个工具类

        //单个线程,这个线程池只有一个线程处理,也就是说执行结果只会显示一个线程在跑
//      ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //创建一个固定的线程池的大小,我这里参数是5,也就是说会有5个线程在跑
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        //可伸缩的,遇强则强,遇弱则弱,这个就很好理解了,不理解你再自己跑一遍
        ExecutorService threadPool = Executors.newCachedThreadPool();

        try {
            for (int i = 0; i < 10; i++) {
//            new Thread().start(); 使用了线程池之后就不能用传统的方法来创建线程了,要用线程池的方式来创建
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"运行");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }

    }
}

  

11.3 七大参数

  • Executors.newSingleThreadExecutor():
 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,//这里两个参数分别代表核心线程数和最大线程数都是1
                                    0L, TimeUnit.MILLISECONDS,//
                                    new LinkedBlockingQueue<Runnable>()));
    }

  

  • ExecutorService threadPool = Executors.newFixedThreadPool(5);
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,//这里就是固定的
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

  

  • ExecutorService threadPool = Executors.newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//核心是0个,最大约等于21亿,很可能造成oom,线程溢出
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

  

综上三个方法其实本质上都是new了一个ThreadPoolExecutor,那么我们再来看看ThreadPoolExecutor:

 //可以看到这里核心的有7大参数
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; }

  

我们来举个例子来解释一下上面这7个参数(这里的人就好比是一个个的线程要进来办理一个个业务,但是这里Thread.currentThread().getName()获得的是柜台的编号哦~):

 

我们再来解释一下keepAliveTime,//超时了 没有人调用就会释放:

 

11.4 四种拒绝策略

四种拒绝策略实现类:

 

 

 我们根据上面这个例子来自己手动创建一个线程池:

import java.util.concurrent.*;

public class Demo02 {
    public static void main(String[] args) {
        //这就是我们自定义的线程池,必须熟记这7个参数
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                //拒绝策略,银行满了也就是队列满了还有人进来,这里的拒绝策略就对应的阻塞队列的四种策略

                //new ThreadPoolExecutor.AbortPolicy()这种会抛出异常
                //new ThreadPoolExecutor.CallerRunsPolicy()哪来的去哪里,也就是说我们这里比如下面的9号来了,银行已经满了那就执行不了了,这个9号是从main线程来的,那么这个9号就有main线程来办理业务
                // new ThreadPoolExecutor.DiscardPolicy()队列满了,丢掉那个多的任务,不会执行它,不会抛出异常
                new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试去和最早的竞争,也就是和第一个去竞争,竞争成功了就会执行,竞争不成功,它就会不执行也不会抛出异常,就是有了一个常使的过程,不会立刻抛弃掉,是一种升级的优化策略
        );

        try {
            //最大承载:阻塞队列+max--》3+5=8
            //超过了最大承载就会抛出异常:java.util.concurrent.RejectedExecutionException这里也就是行驶了拒绝策略实
            // 可以变换策略来选择四种中的哪一种实可以不抛出异常的
            for (int i = 1; i <= 9; i++) {
//            new Thread().start(); 使用了线程池之后就不能用传统的方法来创建线程了,要用线程池的方式来创建
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"运行");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

  

11.5 调优

上面我们可以看到定义了线程池的最大数量,那么这个最大线程池到底该怎么定义呢?

1. cpu 密集型,几核的cpu就定义为几,这样可以保证cpu运行效率最高,打开你的电脑就可以获取,但是不同电脑的核数是不一样的,因此这里我们要去通过如下代码获取:

Runtime.getRuntime().availableProcessors()//返回的就是本电脑的cpu核数

  

2. io 密集型 比如一个程序有15个大型任务,io十分占用资源。所以io密集型就是通过判断你的程序中十分消耗io的线程数,然后你设置的最大池的数量大于它就可以了。但是一般都设置的是两倍。

12. 四大函数式接口

只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
//@FunctionalInterface超级超级多
//简化编程模型,在新版本中的框架底层大量的应用
//foreach(消费者类型的函数式接口)

  

以下是四个原生的函数式接口,当然也有其他的函数式接口,不过那些都是复合的。

 

12.1 Function(函数型接口)

 

 

import java.util.function.Function;

public class FunctionDemo {
    public static void main(String[] args) {
        //下面我们就利用这个Function函数式接口来来创造了一个传入一个字符串返回一个字符串
         Function function = new Function<String,String>(){
             @Override
             public String apply(String str) {
                 return str;
             }
        };
        //lambda简化写法
         Function<String,String> function1 = ((str)->{
             return str;
         });
         
        System.out.println(function.apply("asd"));
    }
}

  

12.2 Predicate(断定型接口)

 

 

import java.util.function.Predicate;

public class PredicateDemo {
    public static void main(String[] args) {
        //有一个输入参数,返回值只有一个布尔值
        //判断一个字符串是否为空
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String o) {
                return o.isEmpty();
            }
        };
        //注意这里的泛型一定要写上,如果不写上那么会报错,不信你试一试
        Predicate<String> predicate1 = (o)->{
            return o.isEmpty();
        };

        System.out.println(predicate.test("asd"));
    }
}

  

12.3  Consumer(消费型接口)

 

 

import java.util.function.Consumer;

public class ConsumerDemo {
    public static void main(String[] args) {
        //消费型接口,只有输入没有输出
        //这里就完成了一个打印字符串的功能,同样也可以使用lambda表达式
        Consumer consumer = new Consumer<String>() {
            @Override
            public void accept(String o) {
                System.out.println(o);
            }
        };
        Consumer<String> consumer1 = (o)->{
            System.out.println(o);
        };

        consumer.accept("Hello!");
    }
}

  

12.4 Supplier(供给型接口)

 

 

import java.util.function.Supplier;

public class SupplierDemo {
    public static void main(String[] args) {
        //供给型接口
        Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return 1024;
            }
        };

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

        System.out.println(supplier.get());
    }
}

 

那么我们学习完了上面四个原生的函数型接口,那么学这个的意义是什么呢?

就是为了简化编程!! 

13. Stream流式计算

什么是Stream流式计算

就是在大数据的时代包括的其实就是两大部分存储和计算,那么集合、MySQL等等是用来存储的,那么计算的部分都应该交给我们的流来操作。

 

 

 

package stream;

public class User {
    private int id;
    private String name;
    private int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

  

package stream;


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

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

//计算交给流

//1.将list转换为stream,然后再用filter过滤,
// filter里面就是一个断定型接口,这里的u实际上自动就是指的list中的元素
//map它也是一个函数型接口,它可以转换的功能
//sort是排序的意思,里面有个compareTo这个方法可以两个东西调换位置
//limit只输出指定的字符
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);
//这就是链式编程
}
}

  

14. ForkJoin

什么是ForkJoin?

从jdk1.7开始出现的,并行执行任务!提高效率,在大数据量的时候很有用的。说白了就是把大任务拆分成一个个的小任务来完成。然后再汇总。

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

 

 ForkJoin的特点?

工作窃取,就是比如说下面有两个任务,线程a来执行任务一,线程b来执行任务二,当线程b执行完之后发现线程a还没有把任务一执行完,那么它就会把任务一里还没有执行的任务拿过来执行。可以提高效率避免了线程的等待。这个里面维护的都是双端队列,就是两边都可以拿取。

 

 ForkJoin的操作:

 

 

 

 可以看到我们需要去自己去继承它

 

 

 

 我们打开这个RecursiveTask发现它是一个抽象类,我们需要去重写里面的这个compute这个抽象方法

import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务
 * 如何使用ForkJoin
 * 1.forkjoinPool通过这个pool来执行
 * 2.我们要新建一个计算任务 forkjoinPool.execute(ForkJoinTask<?> task)
 * 3.因为我们这里需要有返回值所以我们去继承这个RecursiveTask
 * 4.重写里面的compute方法
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start; //1

    private Long end; //199990999

    //临界值
    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 {
            //要是我们要求和的数量大于10000我们就采用ForkJoin分支计算的方法来做

            //1.先来计算一下start和end的一个中间值
            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();
            //综上说白了就像是递归
        }
    }
}

  

我们来测试比较一下三种方式:

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

public class TestForkJoin {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }
    //普通程序员
    private static void test1(){
        long sum = 0;
        long start = System.currentTimeMillis();
        for (long i = 1L; i <= 10_0000_0000L; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start)+" 结果为:"+sum);
    }
    //会使用ForkJoin的
    private static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //因为RecursiveTask继承了ForkJoinTask
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        //forkJoinPool.execute(task);这个是执行任务是没有返回值的,
        // 我们这里想看返回值就用submit跟这个是一样的,它的返回值 是一个ForkJoinTask
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start)+" 结果为:"+sum);
    }
    //会使用并行流的
    private static void test3(){
        long start = System.currentTimeMillis();
//        range():()    rangeClosed():(] Long::sum 这里的双冒号代表的是方法的引用的意思
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end-start)+" 结果为:"+sum);
    }
}

  

15. 异步回调

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

异步回调说白了就和我们的之前用于客户端和服务器端的ajax异步通讯很像,然而其实java本身就有一个可以实现线程异步调用的一个功能

 

 

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

/**
 * 这个玩意说白了就和Ajax一样,java里用的就是CompletableFuture
 * 1.异步回调
 * 2.成功回调
 * 3.失败回调
 */
public class futureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.没有返回值的
        // 发起一个请求
        //这里的Void是void的一个包装类,表示这里的没有返回值,runAsync里面传入的是一个Runnable
//        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();
        //获取执行结果这里的这一步并不会影响上面这个1111的执行,
        // 因为它是通过上面runAsync这种异步的方式来完成的


        //2.有返回值的
        //下面supplyAsync它是一个供给型的之前生产者消费者里的生产者,没有传入参数有返回值
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
            int i = 1/0;
            return 1024;
        });

        //成功执行完whenComplete和失败后的处理exceptionally
        // t代表返回的结果
        // u返回错误是什么
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t:" + t);
            System.out.println("u:" + u);
        }).exceptionally((e) -> {
            e.printStackTrace();
            return 233;
        }).get());


    }
}

  

说白了就是异步来完成一些东西,提高效率

16. JMM

请你谈谈对Volatile的理解

Volatile是java内置的一个关键字,java虚拟机提供的一个轻量级的同步机制,和synchronize很像,但是没有synchronize那么强大。

1. 保证可见性

2. 不保证原子性

3. 禁止指令重排

什么是JMM?

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

关于JMM的一些同步约定:

1. 线程解锁前,必须把共享变量立刻刷回主存(因为线程在操作的时候实际上操作的是从内存中复制的一份资源)

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

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

 

综上线程其实就是涉及到两个概念,工作内存主内存,在我们的JMM中有8种操作

 

 

 

 

 

 

 为了让我们的线程可以时时刻刻知道我们主内存种的值发生了改变因为我们引出了Volatile

17. Volatile

17.1 保证可见性

import java.util.concurrent.TimeUnit;

public class VolatileDemo {
    //这里如果不加volatile程序就会死循环,保证程序的可见性
    private volatile static int number = 0;
    public static void main(String[] args) { //main线程

        //线程一
        new Thread(()->{
            while (number==0){

            }
        }).start();

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

        number = 1;
        System.out.println(number);
    }
}

  

17.2 不保证原子性

原子性:不可分割

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

//不保证原子性
public class VolatileDemo02 {
    // synchronized 保证原子性,volatile修饰的不会保证原子性,
    // 其他线程还是可以拿到你在操作的东西,就好像没有加锁一样但是它是锁了只是一个轻量级锁
    private volatile static int num = 0;

    public static void add(){
        num++;
    }
    public static void main(String[] args) {

        //理论上num结果应该为2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){//程序中至少有一个main线程和gc线程在跑
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+num);

    }
}

  

如果不加lock和synchronized如何保证原子性?

num++它并不是一个原子性的操作,其实它需要完成三步操作,可能会有多个线程同时进来

 

 我们要使用原子类来解决原子性的问题(单独领出来这个原子类,它比锁高效非常多倍,保证原子性,直接和操作系统挂钩,Unsafe类是一个很特殊的存在)

 

 

那么原子类为什么可以这样呢?

import java.util.concurrent.atomic.AtomicInteger;

//不保证原子性
public class VolatileDemo02 {
    // synchronized 保证原子性,volatile修饰的不会保证原子性,
    // 其他线程还是可以拿到你在操作的东西,就好像没有加锁一样但是它是锁了只是一个轻量级锁
    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add(){
        //num++;不是一个原子性操作
        num.getAndIncrement();//AtomicInteger +1 方法,这个方法底层用的是CAS,cpu的并发原理
    }
    public static void main(String[] args) {

        //理论上num结果应该为2万
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2){//程序中至少有一个main线程和gc线程在跑
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+num);

    }
}

  

17.3 禁止指令重排

什么是指令重排?

你写的程序,计算机并不是按照你写的顺序来执行的。

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

 

 

只要你加了Volatile就可以避免指令重排:

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

1. 保证特定操作的执行顺序

2. 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

 

 

接下来只要聊到指令重排就要聊到单例模式了(DCL懒汉式)~

18. 单例模式

饿汉式:

package single;

//饿汉式
public class Hungry {

    //饿汉式一上来不管你用不用就会把这些都new好,所以有可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    //单例模式说白了就是构造器私有
    private Hungry(){

    }
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

  

懒汉式:

package single;

//懒汉式
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }

    private volatile static LazyMan lazyMan;

    private static LazyMan getInstance(){
        //双重检测锁模式的懒汉式单例 DCL懒汉式
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//不是原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 这样就有可能造成指令重排,线程a可能已经把内存空间占用了但是还没有new出来,
                     * 但是线程b会认为这个对象已经new出来了,但是结果返回的是一个空的,虚无的东西
                     * 所以一定要加上volatile
                     */
                }
            }
        }
        return lazyMan;
    }

    //单线程下单例是可以的,但是多线程就不可以了

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

  

内部类的方式:

package single;

//静态内部类实现
public class Holder {
    private Holder(){

    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

  

通过反射破坏单例模式如何解决?

package single;

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

//懒汉式
public class LazyMan {
    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图用反射来破坏");
            }
        }
        System.out.println(Thread.currentThread().getName());
    }

    private volatile static LazyMan lazyMan;

    private static LazyMan getInstance(){
        //双重检测锁模式的懒汉式单例 DCL懒汉式
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//不是原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 这样就有可能造成指令重排,线程a可能已经把内存空间占用了但是还没有new出来,
                     * 但是线程b会认为这个对象已经new出来了,但是结果返回的是一个空的,虚无的东西
                     * 所以一定要加上volatile
                     */
                }
            }
        }
        return lazyMan;
    }

    //单线程下单例是可以的,但是多线程就不可以了

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//这个方法可以无视私有的构造器
        LazyMan lazyMan = declaredConstructor.newInstance();

        System.out.println(instance.hashCode());
        System.out.println(lazyMan.hashCode());

    }
}

  

更进一步我们该如何解决呢?

package single;

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

//懒汉式
public class LazyMan {

    private static boolean yaoyao = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (yaoyao==false){
                yaoyao = true;
            }else {
                throw new RuntimeException("不要试图用反射来破坏");
            }
        }
        System.out.println(Thread.currentThread().getName());
    }

    private volatile static LazyMan lazyMan;

    private static LazyMan getInstance(){
        //双重检测锁模式的懒汉式单例 DCL懒汉式
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();//不是原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 这样就有可能造成指令重排,线程a可能已经把内存空间占用了但是还没有new出来,
                     * 但是线程b会认为这个对象已经new出来了,但是结果返回的是一个空的,虚无的东西
                     * 所以一定要加上volatile
                     */
                }
            }
        }
        return lazyMan;
    }

    //单线程下单例是可以的,但是多线程就不可以了

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//这个方法可以无视私有的构造器
        LazyMan lazyMan1 = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan1.hashCode());
        System.out.println(lazyMan2.hashCode());

    }
}

  如此往下可以不断地通过反射来破坏单例模式,因此我们需要对源码来进行分析

 

 

 我们来写一个枚举类测试一下:

package single;

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

//enum是一个什么?枚举本身也是一个class类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();

    }
}

 

19. 深入理解CAS

什么是CAS?

大厂你必须深入研究底层!

package cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {

    //CAS compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //public final boolean compareAndSet(int expect, int update)
        //可以看到这个方法有两个形参,第一个代表期望的意思,第二个代表更新的意思
        //也就是说如果我期望的值达到了,那么就更新,否则就不更新,CAS是cpu的一个并发原语
        //你看我这里就先设置了2020,说明已经达到了2020,所以就变成了2021了
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();
    }
}

  

 

 

 

 

 

 这就是一个典型的自旋锁~以后探究~

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

CAS中有三个操作数:期望的值,比较的值,和更新的值

CAS的缺点:

1. 由于底层是自旋锁会耗时

2. 由于是cpu操作,一次性只能保证一个共享变量的原子性

3 会存在ABA问题

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

 

 

 

package cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {

    //CAS compareAndSet:比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        
        //对于我们平时写的sql来说:乐观锁!
        //=============捣乱的线程===============
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());
        //=============期望的线程===============
        System.out.println(atomicInteger.compareAndSet(2020, 6666));
        System.out.println(atomicInteger.get());
    }
}

  

如何解决上面的ABA问题呢?那么我们就要讲到原子引用了。

 20. 原子引用

带版本号的原子操作!

 

 

 

 

 

 

package cas;

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

public class CASDemo {

    //CAS compareAndSet:比较并交换
    public static void main(String[] args) {
        //这里的第二个参数就是一个版本号相当于,Integer的值一旦超过了-128--127的时候,
        // 你要去复用的时候它就会重新创建一个Integer对象,导致我们引用的两个对象并不是指向同一个的。
        //所以注意下面的泛型是一个包装类,要注意对象的引用问题,一般像下面这个泛型其实都是一个对象比如说User,User对象是不是原来的那个User
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1,1); new Thread(()->{ int stamp = atomicInteger.getStamp();//获得版本号 System.out.println("a1: "+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //乐观锁每次执行完了之后就会给version+1,那么我们这里也是一样的,会将后面那个版本号+1 atomicInteger.compareAndSet(1,2, atomicInteger.getStamp(),atomicInteger.getStamp()+1); stamp = atomicInteger.getStamp(); System.out.println("a2: "+stamp); atomicInteger.compareAndSet(2,1, atomicInteger.getStamp(),atomicInteger.getStamp()+1); stamp = atomicInteger.getStamp(); System.out.println("a3: "+stamp); },"a").start(); new Thread(()->{ int stamp = atomicInteger.getStamp(); System.out.println("b1: "+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //下面会判断我们的版本号已经不是刚开始的了,已经被上面那个线程a操作过了 // 所以这里会执行失败的,不信你输出一下就知道了 atomicInteger.compareAndSet(1,2, stamp,stamp+1); stamp = atomicInteger.getStamp(); System.out.println("b2: "+stamp); },"b").start(); } }

  

21. 各种锁的理解

21.1 公平锁和非公平锁

公平锁:非常的公平,不能够插队,线程必须先来后到。

非公平锁:非常的不公平,可以插队,后面来的可以插队进来。(Lock和synchronized都是默认的都是非公平锁)可以提高效率比如说三分钟可以执行完的东西可以先执行。

 

 

 

 

 

 

 

 

 

 

21.2 可重入锁

所有的锁都是可重入锁,它也可以叫做递归锁。

 

 

 

 

synchronized版本:
package Lock;

//synchronized
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();

        //A一定是先执行完的,因为就相当于拿到了大门的钥匙,然后又拿到里面的钥匙,
        // 但是大门的钥匙你还在用着在,只有当整个都执行完了大门的钥匙才会被释放
    }
}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"sms");
        call();//外面是sms本身就是一个锁这里也是一把锁
    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");
    }
}

  

Lock版本:

package Lock;

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

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

        //A一定是先执行完的,因为就相当于拿到了大门的钥匙,然后又拿到里面的钥匙,
        // 但是大门的钥匙你还在用着在,只有当整个都执行完了大门的钥匙才会被释放
    }
}

class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        //这里你拿到的lock其实下面的那个call()里面的锁也拿到了
        lock.lock();
        lock.lock();
        //lock 锁必须配对否则就会锁在里面 比如在这里加了两个lock。lock那么你也要解两遍
        try {
            System.out.println(Thread.currentThread().getName()+"sms");
            call();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
            lock.unlock();
        }
    }

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

    }
}

  

21.3 自旋锁(不断循环遍历迭代)

package Lock;

import java.util.concurrent.atomic.AtomicReference;

//自旋锁
public class SpinlockDemo {
    //默认值是一个null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"-->myLock");
        //自旋
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println("我在自旋");
        }
    }

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

  

package Lock;

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

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


        //使用我们自己写的自旋锁
        SpinlockDemo lock = new SpinlockDemo();

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

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


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



    }
}

  

21.4 死锁

死锁是什么?

就是两个东西去互相抢夺资源。

 

 死锁测试,怎么排除死锁

package Lock;

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {
    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);
            }
        }
    }
}

  

1. jps-l 定位发生死锁进程号

 2.jstack 查看进程信息

 

 

 

posted @ 2021-02-04 22:15  Yaoyaoo  阅读(111)  评论(0)    收藏  举报