Java JUC包学习笔记(java.util.concurrent)

概述

什么是JUC包

JUC包:jdk中java.util.concurrent包,从jdk1.5后开始出现,用来解决java中多线程编程问题

在学习之前先复习一下计算机操作系统进程和线程之间的区别:
进程:系统进行资源调度的基本单位。
线程:CPU进行资源调度的基本单位。

创建线程的四种方式

1)继承线程Thread类
2)实现Runable接口(常用线程实现方法)
3)实现Callable接口(这里常用于有返回值的线程实现)
4)线程池

Case 1 继承线程Thread类创建线程:

public class MyThread extends Thread{
    @Override
    /*
    * 重新给Thread类中的run方法
    * */
    public void run()
    {
        System.out.println(MyThread.currentThread().getName()+":继承Thread线程类实现的线程....");
    }
    public static void main(String[] args)
    {
        Thread thread = new MyThread();
        thread.start();
    }
}

在这里插入图片描述
Case 2 实现Runnable接口创建线程:

public class MyThread2 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":实现Runnable接口实现的线程....");
    }
    public static void main(String[] args)
    {
        /*
        * 把Runnable实例传进Thread类作为其构造方法的参数
        * */
    Thread thread = new Thread(new MyThread2());
    thread.start();
    }
}

在这里插入图片描述
Case 3 实现Callable接口创建线程:
要用到FutureTask去取线程的返回值:

/*
 *@Description:启动一个线程,将一个变量创建去然后进行加1操作后获取其返回值
 * 取返回值要用FutureTask
 *  */
public class MyThread3 implements Callable<Integer> {
    int value;
    public MyThread3(int value)
    {
        this.value = value;
    }
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+":实现Callable接口实现的线程");
        return ++value;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int InitValue = 1;
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3(InitValue));
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("初始值:"+InitValue+",线程执行后获取返回的值:"+futureTask.get());
    }
}

在这里插入图片描述

线程的状态

在java.lang.Thread类下有个枚举类State,里面定义了线程的所有状态,一共6个状态(源码在Column1742):
1)NEW 新建状态,线程创建且没有执行start方法时的状态
2)RUNNABLE 运行状态,线程已经启动,但是等待相应的资源(比如IO或者时间片切换)才能开始执行
3)BLOCKED 阻塞状态,阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态
4)WATTING 等待状态,当调用Object.wait或者Thread.join()且没有设置时间,在或者LockSupport.park时,都会进入等待状态。
5)TIMED_WAITING 计时等待状态,当调用Thread.sleep()或者Object.wait(xx)或者Thread.join(xx)或者LockSupport.parkNanos或者LockSupport.partUntil时,进入该状态
6)TERMINATED 终结状态,线程中断或者运行结束的状态

模拟并发Demo

售票Demo,模拟并发两种实现方式(防止超卖)

1) 加synchronized 关键字

/*
* 创建一个资源类,表示票数
* */
class Ticket{
    //表示有100张票可以卖
     private  static int numbers = 100;
     //定义卖票的行为方法,加synchronzied 关键字
     public static synchronized void  sellTicket()
     {
         if(numbers>0)
         {
             numbers --;
             System.out.println(Thread.currentThread().getName()+":卖出一张票,还剩"+numbers+"张");
         }
         else{
             System.out.println(Thread.currentThread().getName()+":票售完......");
         }
     }
}
class Sailor implements  Runnable{
    public void run()
    {   for(int i=0;i<50;i++)
    {
       Ticket.sellTicket();
    }
}
}
public  class TicketSell {
      //3个售票线程模拟并发
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Sailor(), "售票窗口1");
        Thread thread2 = new Thread(new Sailor(), "售票窗口2");
        Thread thread3 = new Thread(new Sailor(), "售票窗口3");
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

2)用ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

/*
 * 创建一个资源类,表示票数
 * */
class Ticket{
    //表示有100张票可以卖
    private  static int numbers = 100;
    static ReentrantLock lock = new ReentrantLock();
    //定义卖票的行为方法,加synchronzied 关键字
    public static void sellTicket()
    {   lock.lock();
        if(numbers>0)
        {
            numbers --;
            System.out.println(Thread.currentThread().getName()+":卖出一张票,还剩"+numbers+"张");
        }
        else{
            System.out.println(Thread.currentThread().getName()+":票售完......");
        }
        lock.unlock();

    }
}
class Sailor implements  Runnable{
    public void run()
    {   for(int i=0;i<50;i++)
    {
        Ticket.sellTicket();
    }
    }
}
public class TicketSell {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Sailor(), "售票窗口1");
        Thread thread2 = new Thread(new Sailor(), "售票窗口2");
        Thread thread3 = new Thread(new Sailor(), "售票窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

加减Demo,共享一个资源类,模拟并发进行对其修改

1) 加synchronized 关键字

/*
 *定义一个共享资源类
 */
class ShareResource {

    private int value = 0;

    public synchronized void add() throws InterruptedException {
            while (value != 0) {
                    this.wait();
            }
            value++;
            this.notifyAll();
            System.out.println(Thread.currentThread().getName()+":"+value);


    }

    public synchronized void sub() throws InterruptedException {


            while (value != 1) {
                this.wait();
            }
            value--;
            this.notifyAll();
            System.out.println(Thread.currentThread().getName()+":"+value);
        }
    }

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

        },"加法线程").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)
                    resource.sub();
            }catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        },"减法线程").start();
    }
}

2)用ReentrantLock

/*
*定义一个共享资源类
*/
class ShareResource {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private int value = 0;

    public void add() throws InterruptedException {
        lock.lock();
        try{
        while (value != 0) {
            condition.await();
        }

        value++;
        System.out.println(Thread.currentThread().getName()+":"+value);
        condition.signalAll();}
        finally{
            lock.unlock();
        }
    }

    public void sub() throws InterruptedException {
        lock.lock();
        try{
        while (value != 1) {
            condition.await();
        }
        value--;
        System.out.println(Thread.currentThread().getName()+":"+value);
        condition.signalAll();
    }finally {
            lock.unlock();
        }
        }

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

       },"加法线程").start();
       new Thread(()->{
       try {
          for(int i=0;i<10;i++)
          resource.sub();
       }catch (InterruptedException e)
       {
           e.printStackTrace();
       }
    },"减法线程").start();
}
}


总结:加synchronized关键字不用手动解锁,而如果用ReentrantLock 则要手动解锁,不然该线程会一直锁定该资源,其他线程无法进行访问

线程间的定制化通信

概述:启动三个线程,要求如下:
线程1打印5次,线程2打印10次,线程3打印15次依次类推

class ShareResource{
    //flag=1,2,3
    ~~int flag =1;
    ReentrantLock lock = new ReentrantLock();
    Condition c1  = lock.newCondition();
    Condition c2  = lock.newCondition();
    Condition c3 =  lock.newCondition()~~ ;
    public void print5(int loop) throws InterruptedException {
          lock.lock();
          while(flag!=1)
          {
              c1.await();
          }
          for(int i=1;i<=5;i++)
          {   System.out.println(Thread.currentThread().getName()+"::"+i+",loop:"+loop);}
          flag = 2;
          c2.signal();
          lock.unlock();
    }
    public void print10(int loop) throws InterruptedException {
        lock.lock();
          while(flag!=2)
          {
              c2.await();
          }
        for(int i=1;i<=10;i++)
        {   System.out.println(Thread.currentThread().getName()+"::"+i+",loop:"+loop);}
           flag = 3;
          c3.signal();
          lock.unlock();
    }
    public void print15(int loop) throws InterruptedException {
        lock.lock();
         while(flag!=3)
         {
             c3.await();
         }
        for(int i=1;i<=15;i++)
        {   System.out.println(Thread.currentThread().getName()+"::"+i+",loop:"+loop);}
         flag=1;
         c1.signal();
         lock.unlock();
    }
}
public class ThreadDemo {
    public static void main(String[] args)
    {
        ShareResource resource = new ShareResource();
        new Thread(()->{
            for(int i=1;i<=5;i++) {
                try {
                    resource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    },"AA").start();
         new Thread(()->{
             for(int i=1;i<=5;i++) {
                 try{
                     resource.print10(i);
                 }catch (InterruptedException e)
                 {e.printStackTrace();}
             }
         },"BB").start();
        new Thread(()->{
            for(int i=1;i<=5;i++) {
                try{
                    resource.print15(i);
                }catch (InterruptedException e)
                {e.printStackTrace();}
            }
        },"CC").start();
    }

}

集合类的线程安全问题

ArrayList的线程安全问题

ArrayList线程的不安全演示:
ArrayList源码的add方法(Column 463)方法上没有加synchronized关键字
ArrayList的add方法
在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<>();
        for(int i=0;i<30;i++)
        {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }

出现异常:
在这里插入图片描述

解决方案-Vector
Vector的add方法(Column 785):
在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args)
    {
        Vector<String> vector = new Vector<>();
        for(int i=0;i<30;i++)
        {
            new Thread(()->{
                vector.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(vector);
            },String.valueOf(i)).start();
        }
    }
}

Vector 方法不常用,jdk1.0就已经出现

解决方案-Collections
查看源码可以知道
在这里插入图片描述
在这里插入图片描述
这里的add方法出现了一个mutex,这个是什么呢?继续找到他继承的类SynchronizedCollection,可以知道mutex是一个Object对象,而且构造方法中,mutex就是我们的list,所以Collection是对整个链表进行加锁
在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args)
    {  List<String> arrayList = new ArrayList<>();
       List<String> list = Collections.synchronizedList(arrayList);
        for(int i=0;i<30;i++)
        {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

但是这种也不太常用

解决方案-CopyOnWriteArrayList(写时复制技术)
源码:(column 434)
在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args)
    {  List<String> list = new CopyOnWriteArrayList<>();
        for(int i=0;i<30;i++)
        {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }

支持并发读,独立写。解决了并发修改的问题
先复制一个跟原来集合一样的内容,之前的人仍然读之前的那份,写完之后再去跟之前的内容进行一个合并

HashSet的线程安全问题

在这里插入图片描述
可以看到HashSet集合的add方法也是没有加synchronized关键字的,仍旧会出现并发修改问题
同样Set也有 CopyOnWriteArraySet 去实现线程安全
HashSet的底层就是一个HashMap

public class ThreadDemo {
    public static void main(String[] args)
    {
    CopyOnWriteArraySet set = new CopyOnWriteArraySet(new HashSet<>());

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

HashMap 线程安全问题

源码:put方法也是没有synchronized关键字
在这里插入图片描述
解决办法:ConcurrentHashMap

public class ThreadDemo {
    public static void main(String[] args)
    {
     Map<String,String> map = new ConcurrentHashMap<>();

    for(int i=0;i<30;i++)
    {   String key = String.valueOf(i);
        new Thread(()->{
          map.put(key,UUID.randomUUID().toString().substring(0,8));
          System.out.println(map);
        },String.valueOf(i)).start();
    }
    }
}

多线程锁

公平锁和非公平锁

在ReetranLock的构造方法中有个boolean字段的fair参数,当为true时,表明当前锁是公平锁,反之为非公平锁
源码:
在这里插入图片描述

class ShareResource{
    //可重入锁默认是非公平锁,不管该资源有没有被占用,先到线程都会直接进入抢占
    //公平锁,在抢占时,会先询问,该资源有没有被占用
    //剩余打印次数
    int num = 10;
    private  final ReentrantLock lock  = new ReentrantLock(false);
    public void print()
    { lock.lock();
    if(num>0)
    {
      System.out.println(Thread.currentThread().getName()+"::"+"执行了打印方法");
      num--;
    }
      lock.unlock();
    }
}
public class ThreadDemo {
    public static void main(String[] args)
    {
        ShareResource resource = new ShareResource();
       new Thread((new Runnable() {
           @Override
           public void run() {
               for(int i=0;i<50;i++) {
                   resource.print();
               }
           }
       }),"A线程").start();
        new Thread((new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<50;i++) {
                    resource.print();
                }
            }
        }),"B线程").start();
        new Thread((new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<50;i++) {
                    resource.print();
                }
            }
        }),"C线程").start();


}
}

非公平锁,可以看到A线程抢先进入了共享资源,独占了共享资源,而其他线程会有饿死的情况发生
在这里插入图片描述
公平锁,其他线程均有执行,解决了线程饿死的问题,在共享资源少的情况下,仍然可能会出现和上面非公平锁一样的情况,这是因为创建线程需要时间,可以把共享资源的值调大
在这里插入图片描述

Synchronized关键字锁的情况

主要分清锁的是哪个,是对象还是整个Class,静态方法锁的是整个Class

//共享资源类里面有两个方法,一个打印的是MethodA,一个打印的是MethodB
class ShareResource{
    public synchronized void MethodA()
    {
        System.out.println("MethodA......");
    }
    public synchronized void MethodB()
    {
        System.out.println("MethodB......");
    }

}
public class ThreadDemo {
    public static void main(String[] args)
    {
        ShareResource resource = new ShareResource();
    new Thread(()->{
         resource.MethodA();
    },"线程A").start();
    new Thread(()->{
        resource.MethodB();
    },"线程B").start();
    }
}

可重入锁

synchronied(隐式的可重入锁)和 ReentrantLock(显式)都是可重入锁,也叫递归锁
隐式:不用手动加锁和解锁
显式:需要手动加锁和解锁
1、可以自由进入的锁,可以用同步代码块,或者同步方法验证synchronied的可重入特点
2、ReentrantLock虽然可以在进行递归上锁的时候不解锁,这样虽然不会影响本个线程进行,但是由于没有解锁,会影响其他线程获取该锁,使另外线程需要该资源的一直在等待解锁。

死锁

1、什么是死锁?
两个或者两个以上的进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去。

class ShareResource{
   private ShareResource shareResource;
   public void setShareResource(ShareResource shareResource)
   {
       this.shareResource = shareResource;
   }

   public synchronized void print() throws InterruptedException {      
       System.out.println(Thread.currentThread().getName()+"::"+"持有了本对象的锁");
       System.out.println(Thread.currentThread().getName()+"::"+"等待获取另外的锁");
       Thread.sleep(100);
       //尝试获取B线程的锁
       shareResource.print();
       System.out.println(Thread.currentThread().getName()+"::"+"获取了另外的锁....");
   }
}
public class ThreadDemo {
    public static void main(String[] args)
    {
        ShareResource resource1 = new ShareResource();
        ShareResource resource2 = new ShareResource();
       resource1.setShareResource(resource2);
       resource2.setShareResource(resource1);
       new Thread(()->{
           try {
               resource1.print();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       },"线程A").start();
        new Thread(()->{
            try {
                resource2.print();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程B").start();
    }
}

在这里插入图片描述
可以看到两个线程,都在等待对方释放锁,一直循环等待就处于一个死锁的状态

2、产生死锁的原因:
1)系统资源不足
2)进程运行推进的顺序不合适
3)资源分配不当

3、验证是否是死锁
(1)jps命令 类似linux ps -ef
(2)jstack 查看堆栈信息,跟踪堆栈信息,jvm里面自带堆栈跟踪工具
先在java的bin目录下,执行jps -l
在这里插入图片描述
找到该程序的pid号,图中7476就是本程序的进程号
再进行jstack [pid] 命令查看堆栈信息:
在这里插入图片描述
在这里插入图片描述
可以发现在信息的最后面发现有一行信息 Found 1 deadlock,就是发现一个死锁

Callable接口

复习线程创建的四种方式:在文章目录中有

Runnable 接口 和 Callable 接口对比

一、是否有返回值(Runnable接口没有,Callable接口有)
二、是否抛出异常(Callable如果没法返回计算结果,会抛出异常)
三、实现方法名称不同(Runnable要实现的方法是Run,Callable实现的方法是Call)

Callable接口不能直接替换Runable接口去创建线程,因为线程的构造方法里面只有Runnable接口,所以找一个类,和Runnable接口有关系,又和Callable接口也有关系
Runnable 接口有实现类FutureTask
FutureTask构造可以传递Callable

JUC强大的辅助类

减少计数CountDownLatch

1、CountDownLatch 类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句
2、CountDownLatch 主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
其它线程调用countDown 方法会将计数器减1(调用countDown方法的线程不会阻塞)
3、当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
验证demo

/*
* 6个同学陆续离开教室后值班的同学才可以关门
* */


class ClassRoom{
    CountDownLatch countDownLatch = new CountDownLatch(6);
    public void leave()
    {   this.countDownLatch.countDown();
        System.out.println(Thread.currentThread().getName()+"离开教室.....");
    }
    public void close() throws InterruptedException {
        this.countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"关门....");
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        ClassRoom classRoom = new ClassRoom();
        new Thread((() -> {
            try {
                classRoom.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }), "值班学生").start();

        new Thread(classRoom::leave, "学生A").start();
        new Thread(classRoom::leave, "学生B").start();
        new Thread(classRoom::leave, "学生C").start();
        new Thread(classRoom::leave, "学生D").start();
        new Thread(classRoom::leave, "学生E").start();
        new Thread(classRoom::leave, "学生F").start();

    }
}

因为值班学生调用了await方法,而普通学生调用了countDown方法,普通学生的线程不会阻塞,在计数器变为0之前,值班学生一直在等待,等待计数器为0,值班学生才开始做他的事情
在这里插入图片描述

循环栅栏CyclicBarrier

CyclicBarrier 的构造方法第一个参数是目标障碍数,才会执行cyclicBarrier.await()之后的语句,可以将CyclicBarrier 理解为加1操作

信号灯 Semaphore

在acquire()调用之前,没有调用acquire方法的线程都将处于阻塞状态,构造方法的数字表示只有3个许可,当acqurie的次数超过3个许可时,其他的线程都将等待,等待release方法调用,空出来一个许可,其他车才可停进去(也就是其他线程才会执行)

import java.util.concurrent.Semaphore;

/*
*6辆汽车停3个停车位
* */
public class ThreadDemo {
   public static void main(String[] args)
   {   //3个停车位
       Semaphore semaphore =new Semaphore(3);
       for(int i=1;i<=6;i++)
       {
           new Thread(()->{
               try {
                   semaphore.acquire();
                   System.out.println(Thread.currentThread().getName()+"停进了停车场");
                   Thread.sleep(5000);
                   semaphore.release();
                   System.out.println(Thread.currentThread().getName()+"开出了停车场");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           },"汽车"+String.valueOf(i)).start();

       }
   }
}

内存可见性

volatile 关键字

线程在运行时,假设两个线程会共用一个共享资源,cpu会给每个线程分配独立的内存空间,线程的内存空间会对主存的资源进行拷贝,线程对其修改后,才会去合并主存,这时候,如果某个线程修改了值,但是另外一个线程在其合并前也拷贝了主存的值,就会两个不一样。虽然可以用synchronized加锁,但是加锁效率会很低,这时候volatile关键字,就类似实时刷新主存,线程之间的内存同步主存,做到线程之间的内存是可见的。

读写锁 ReentrantReadWriteLock

常见锁的概念

悲观锁:每次操作都要上锁解锁,好处是能解决并发中的各种问题,缺点是不支持并发操作,效率低
乐观锁:支持并发操作,要加一个版本号,每次线程提交事务都要检查版本号,看提交时的版本号是否和读取时的版本号一致,不一致说明在此之前有其他线程提交了事务,就不能提交事务了
表锁:对数据库里面某张表的某条记录进行操作时,对整张表进行上锁,其他人就无法对这张表进行操作了
行锁:对数据库里面的某张表的某行记录进行操作时,对该行记录进行上锁,其他人无法对该行记录进行操作,但是对于其他行记录仍然可以正常操作
读锁:共享锁,会发生死锁
写锁:独占锁,会发生死锁
读写锁:一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享

没加读写锁时Demo

class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    public void put(String key,Object value)  {

        try {
            System.out.println(Thread.currentThread().getName()+"正在写操作,key:"+key);
            map.put(key,value);
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName()+"写完了,key:"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public Object get(String key)
    {         Object result = null;

        try {
            System.out.println(Thread.currentThread().getName()+"正在读操作,key:"+key);
            result = map.get(key);
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName()+"读完了,key:"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return  result;
    }
};

public class ThreadDemo {
         public static void main(String[] args)
         {
             MyCache cache = new MyCache();
             for(int i=0;i<=6;i++)
             {   final int num = i;
                 new Thread(()->{
                     cache.put(num+"",num+"");
                 },"WriteThread"+ i).start();
             }

                 for(int i=0;i<=6;i++)
                 {
                     final int num = i;
                     new Thread(()->{
                         cache.get(num+"");
                     },"ReadThread"+i).start();
                 }
             }
         }

在这里插入图片描述
可以看到还没写完就开始读了,这样肯定会读不到数据的.

加入读写锁后的Demo


class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    private final ReentrantReadWriteLock ReadWriteLock = new ReentrantReadWriteLock();
    public void put(String key,Object value)  {
        //加入写锁
        ReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"正在写操作,key:"+key);
            map.put(key,value);
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName()+"写完了,key:"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            ReadWriteLock.writeLock().unlock();
        }
    }
    public Object get(String key)
    {         Object result = null;
        //加入读锁
        ReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"正在读操作,key:"+key);
            result = map.get(key);
            TimeUnit.SECONDS.sleep(2);
            System.out.println(Thread.currentThread().getName()+"读完了,key:"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            ReadWriteLock.readLock().unlock();
        }
        return  result;
    }
};

public class ThreadDemo {
         public static void main(String[] args)
         {
             MyCache cache = new MyCache();
             for(int i=0;i<=6;i++)
             {   final int num = i;
                 new Thread(()->{
                     cache.put(num+"",num+"");
                 },"WriteThread"+ i).start();
             }

                 for(int i=0;i<=6;i++)
                 {
                     final int num = i;
                     new Thread(()->{
                         cache.get(num+"");
                     },"ReadThread"+i).start();
                 }
             }
         }

在这里插入图片描述

读写锁的演变过程

第一、无锁 多线程抢占资源 乱
第二、添加锁 使用synchronized和ReentrantLock 都是独占的 包括读读、读写、写写
第三、读写锁 ReetrantReadWriteLock 读读可以共享,提升性能,同时多人进行操作,但是读写,写写是独占的

读写锁的缺点

(1)造成锁饥饿,一直读,没有写操作
(2)读时候,不能写,只有读,完成之后,才可以写,写操作可以读

读写锁的降级

将写入锁降级为读锁
jdk8说明了锁的降级过程:
1)获取写锁
2)获取读锁
3)释放写锁
4)释放读锁
提高了数据的可见性,读锁不能升级为写锁
增加删除修改 —写操作
查询 —读操作

public class ThreadDemo {
    public static void main(String[] args)
    {
        //可重入读写锁的对象
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//读锁
        //锁降级
        //获取写锁
        writeLock.lock();
        System.out.println("获取写锁....");
        //获取写锁
        readLock.lock();
        System.out.println("获取读锁....");
        //释放写锁
        writeLock.unlock();
        System.out.println("释放写锁....");
        //释放读锁
        readLock.unlock();
        System.out.println("释放读锁....");

    }
}

阻塞队列 BlockingQueue

先复习一下队列和栈的特点:
队列:先进先出
栈:后进先出

概述

阻塞队列,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;

阻塞队列特点

1)当队列是空的,从队列中获取元素的操作将会被阻塞
2)当队列是满的,从队列中添加元素的操作将会被阻塞
3)试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
4)试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者已经完全清空,使队列变得空闲起来并后续新增

多线程领域:阻塞,指的是在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起

什么时候需要BlockingQueue?
某些场景下,我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,类似生产线上一样,仓库满了就不能生产东西了,仓库为空的时候就不能提货(生产者和消费者模型)

阻塞队列应用:模拟生产者消费者模型Demo

(10个生产者,10个消费者)

/*
*阻塞队列的应用:
* 生产者消费者模型
*/
//生产车间,生产者调用生产方法,而消费者调用消费方法,生产者和消费者都要往这个工厂里面进行一系列操作
class Factory{
    //最多能容纳10个产品存放
   ArrayBlockingQueue<Object> ObjectFactory = new ArrayBlockingQueue<>(10);
   public void produce()
   {
       try {
           String product = Thread.currentThread().getName()+"生产的产品";
           System.out.println(Thread.currentThread().getName()+"生产完成,等待把产品放到了仓库...");
           TimeUnit.SECONDS.sleep(5);
           ObjectFactory.put(product);
           System.out.println(Thread.currentThread().getName()+"把产品放到了仓库...");
       } catch (InterruptedException e) {
           e.printStackTrace();
       }

   }
   public void consumer()  {
       try{
       System.out.println(Thread.currentThread().getName()+"等待获取可以消费的产品....");
       String product = (String) ObjectFactory.take();
           TimeUnit.SECONDS.sleep(6);
        System.out.println(Thread.currentThread().getName()+"获取到消费的产品:"+product);}
       catch (Exception e)
       {
           e.printStackTrace();
       }
   }

}
public class ThreadDemo {
    public static void main(String[] args)
    {
    Factory factory  = new Factory();
for(int i=0;i<=10;i++)
{
    new Thread(()->{
      while(true)
      {
       factory.produce();
      }
    },"生产者"+i).start();
    }
for(int i=0;i<=10;i++)
{
    new Thread(()->{
        while(true)
        {
            factory.consumer();
        }
    },"消费者"+i).start();
}
    }
}

在这里插入图片描述
阻塞队列的分类:
1)ArrayBlockingQueue,基于定长数组的阻塞队列,常用
2)DelayQueue ,使用优先级队列实现的延迟无界阻塞队列
3)LinkedBlockingDeque,由链表组成的双向阻塞队列
4)LinkedBlockingQueue,链表组成的无界阻塞队列,常用
5)PriorityBlockingQueue,使用优先级排序的无界阻塞队列
6)SynchronousQueue,不存储元素的阻塞队列,也即单个元素的队列

线程池

概述

一种线程使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性能,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,这避免了在处理短时间任务时创建和销毁线程的代价,线程池不仅能够保证内核的充分利用,还能过分调度

优势

线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程将排队等候,等其他线程执行完毕,再从队列中取出任务来

主要特点

降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

架构

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类

使用方式

一池n个线程:
Executors.newFixedThreadPool(n);
特征:
1)线程池中的线程处于一定的量,可以很好的控制线程的并发量
2)线程可以重复被使用,在显示关闭之前,都将一直存在
3)超出一定量的线程被提交时候需要在队列中等待

一个任务一个任务执行,一池一线程
Executors.newSingleThreadExecutor();

线程池根据需求创建线程,可扩容
Executors.newCachedThreadPool();

底层原理

上面三个方式底层都是new一个ThreadPoolExecutor完成线程池的创建

线程池的7个参数

int corePoolSize:线程池中常驻或者核心的线程数量
int maximumPoolSize:线程池中最大的线程数量
long keepAliveTime:不是常驻的线程的存活时间,搭配TimeUnit单位
TimeUnit unit:不是常驻的线程存活时间的单位
BlockingQueue workQueue:阻塞队列,超过了最大线程数量,就会发到阻塞队列
ThreadFactory threadFactory:线程工程,用于创建线程
RejectExecutionHandler handler:拒绝策略

线程池底层工作流程

1)在执行execute方法时才会新创建线程,在new线程池的时候不会创建
2)先用常驻线程,如果常驻不够,则会进入阻塞队列中等待,如果阻塞队列满了,再新建线程,如果到达线程池所设定的容纳的最大线程数,则会调用拒绝策略

自定义线程池

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值