JUC并发编程笔记

juc并发编程

1、回顾

1、并发 并行

并发(多个线程操作同一个资源)

  • CPU一核,一次只能执行一个线程,所以就模拟多个线程同时进行;这就出现了:操作系统的调度:时间片轮转

并行(真正的同时进行):

  • 基于CPU多核,多个线程同时进行:线程池

public class Test01 {

   public static void main(String[] args) {
       
       //获取CPU核数
       System.out.println(Runtime.getRuntime().availableProcessors());

  }
}

2、wait sleep区别

1、来自不同类

wait-->Object

sleep-->Thread

2、关于释放锁

wait()释放,sleep不释放

3、使用的范围

wait 必须在同步代码块中

sleep可以在任何地方

4、是否需要捕获异常

wait不需要捕获异常

sleep必需捕获异常

2、Lock锁(重点)

Synchronized

用synchronized解决买票问题

public class Test02 {

   public static void main(String[] args) {
       Ticket1 ticket1 = new Ticket1();

       //lamod表达式
       new Thread(()->{
           for (int i = 0; i < 40; i++) {
               ticket1.sale();
          }
      },"窗口A").start();
       new Thread(()->{
           for (int i = 0; i < 40; i++) {
               ticket1.sale();
          }
      },"窗口B").start();
       new Thread(()->{
           for (int i = 0; i < 40; i++) {
               ticket1.sale();
          }
      },"窗口C").start();
  }
}
//线程共享得就是一个单独资源,尽量减少耦合
class Ticket1{
   private int num=30;

   //加synchronized 自动
   public synchronized void sale(){
       if (num>0){
           System.out.println(Thread.currentThread().getName()+"买了第"+num+"张票");
           num--;
      }
  }
}

Lock

用lock解决买票问题

public class Test03 {
   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 int num=30;
   //手动
   private ReentrantLock lock = new ReentrantLock();
   public  void sale(){
       
       //加锁就必须i解锁 必须是成对出现
       lock.lock();//加锁
       try{
           if (num>0){
               System.out.println(Thread.currentThread().getName()+"买了第"+num+"张票");
               num--;
          }
      }catch (Exception e){

      }finally {
           lock.unlock();//解锁
      }
  }
}

Synchronized 和 Lock的区别

1、Synchronized是内置的关键字,Lock是对象

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

3、synchronized会自动释放锁,Lock必须手动释放锁:不释放就死锁

4、Synchronized 线程1获得锁的时候,线程2只能等。Lock就不一定(tryLock()尝试获取锁)

5、Synchronized 可重入锁,不可以中断的,非公平。Lock,可重入,可判断锁的状态,自己设置是否公平

6、Synchronized 适合锁少量代码块 Lock大量

啥是锁? 怎么判断锁的是谁?

3、生产者消费者问题

加一个减一个

synchronized版

public class Test04 {

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

//等待,业务,通知
class Data{

    int num=0;
   synchronized void  increment() throws InterruptedException {

        //此处要循环判断,防止虚假唤醒
       while (num!=0){
           //等待
           this.wait();
      }
       num+=1;
       System.out.println(Thread.currentThread().getName()+"getNum->"+num);
       //通知
       this.notifyAll();
  }
   synchronized void decrement() throws InterruptedException {

       while(num==0){
           this.wait();
      }
       num-=1;
       System.out.println(Thread.currentThread().getName()+"getNum->"+num);
       this.notifyAll();
  }

}

Lock(JUC)版

public class Test05  {

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

//等待,业务,通知
class Data2{

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

   int num=0;
   void increment() throws InterruptedException{
       //此处要循环判断,防止虚假唤醒
       lock.lock();

       try {
           while (num!=0){
               //等待
               condition.await();
          }
           num+=1;
           System.out.println(Thread.currentThread().getName()+"getNum->"+num);
           //通知
           condition.notifyAll();
      } catch (InterruptedException e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
   void decrement() throws InterruptedException {

       lock.lock();
       try {
           while(num==0){
               condition.await();//等待
          }
           num-=1;
           System.out.println(Thread.currentThread().getName()+"getNum->"+num);
           condition.notifyAll();//唤醒全部
      } catch (InterruptedException e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }

}

lock新技能、指定唤醒

public class Test06 {

   public static void main(String[] args) {
       Data3 data3 = new Data3();
       
       //开启三个线程
       new Thread(() -> {
           try {
               for (int i = 0; i < 6; i++) {
                   data3.printA();
              }
          } catch (Exception e) {
               e.printStackTrace();
          }
      }, "a").start();
       new Thread(() -> {
           try {
               for (int i = 0; i < 6; i++) {
                   data3.printB();
              }
          } catch (Exception e) {
               e.printStackTrace();
          }
      }, "b").start();
       new Thread(() -> {
           try {
               for (int i = 0; i < 6; i++) {
                   data3.printC();
              }
          } catch (Exception e) {
               e.printStackTrace();
          }
      }, "c").start();
  }
}
class Data3{
   ReentrantLock lock = new ReentrantLock();
   
   //可以看作每个模块绑带一个状态
   Condition condition1 = lock.newCondition();
   Condition condition2 = lock.newCondition();
   Condition condition3 = lock.newCondition();
   int num=1;
   void printA(){
       lock.lock();
       try {
           while(num!=1){
               condition1.await();//代表此模块绑定condition1 下同
          }
           System.out.println("A->"+num);
           num=3;
           condition3.signal();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
   void printB(){
       lock.lock();
       try {
           while(num!=2){
               condition2.await();
          }
           System.out.println("B->"+num);
           num=1;
           condition1.signal();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
   void printC(){
       lock.lock();
       try {
           while(num!=3){
               condition3.await();
          }
           System.out.println("C->"+num);
           num=2;
           condition2.signal();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
}

4、八锁

package com.tt.juc;

import java.util.concurrent.TimeUnit;

//1、问先打印打电话还是发短信
// 这个就是要注意 synchronized修饰的方法,锁的是调用此方法的调用者
   //下面一定是先A执行,再B执行(此处不要纠结于线程调度先调用谁,A.start之后,再放入,基本上都会先调度A)
   //所以我们大可认为每次都是先调用A,
   //当A执行时,即使在call内睡眠,B线程也得等着A,直到执行完毕释放锁;
   // 因为B和A需要的是同一个锁,也就是synchronized修饰的方法的调用者
//2、是先发扣扣还是打电话

public class Test07 {

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

       //第一问
//       DoSomething doSomething = new DoSomething();
//       new Thread(()->{
//           doSomething.call();
//
//       },"A").start();
//
//       System.out.println("睡5s");
//       //juc里的睡眠
//       TimeUnit.SECONDS.sleep(1);
//       System.out.println("睡眠结束");
//
//       new Thread(()->{
//           doSomething.send();
//       },"B").start();


       //第2问
       DoSomething dm1 = new DoSomething();

       new Thread(()->{
           dm1.send();
      },"C").start();

       new Thread(()->{

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

}
class DoSomething{

   public synchronized void call(){

       try {
           TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(Thread.currentThread().getName()+"打电话");


  }

   public synchronized void send(){
       System.out.println(Thread.currentThread().getName()+"发短信");
  }

   public void qq(){

       System.out.println(Thread.currentThread().getName()+"发QQ");

  }
}
package com.tt.juc;

import java.util.concurrent.TimeUnit;

//1、先发信息还是发QQ
   //这个就是先发执行D,发QQ
   //然后C发短信,因为D线程根部不需要锁

public class Test08{

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


       //第1问
       DoSomething2 dm1 = new DoSomething2();

       new Thread(()->{
           dm1.send();
      },"C").start();

       new Thread(()->{

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

}
class DoSomething2{

   public synchronized void call(){

       try {
           TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(Thread.currentThread().getName()+"打电话");


  }

   public synchronized void send(){
       try {
           TimeUnit.SECONDS.sleep(3);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(Thread.currentThread().getName()+"发短信");
  }

   public void qq(){

       System.out.println(Thread.currentThread().getName()+"发QQ");

  }
}
package com.tt.juc;

import java.util.concurrent.TimeUnit;
//第1问 先打打电话还是发信息
   //搞明白锁的对象,很容易判断先是D发短信
//第2问是先打微信还是发快手
   //其实八锁问题就围绕一点:到底谁是锁
   //还要注意 wait是会释放锁的,把示例中的sleep变成wait,结果就不一样
   //另外wait()和notify(),这两方法,需synchronized加锁方法进行同步
public class Test09{

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


       //第1问 先打打电话还是发信息
       //搞明白锁的对象,很容易判断先是D发短信
//       DoSomething3 dm1 = new DoSomething3();
//       DoSomething3 dm2 = new DoSomething3();

//       new Thread(()->{
//           dm1.call();
//       },"C").start();
//
//       new Thread(()->{
//
//           dm2.send();
//       },"D").start();

       //第2问是先打微信还是发快手
       //此时搞明白锁的是模板就够了,
       DoSomething3 dm = new DoSomething3();
       new Thread(()->{
           dm.weixin(dm);
      },"A").start();
       TimeUnit.SECONDS.sleep(1);
       new Thread(()->{
           dm.kuaishou();
      },"B").start();



  }

}
class DoSomething3 {

   public synchronized void call() {
       try {
           TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(Thread.currentThread().getName() + "打电话");


  }

   public synchronized void send() {

       System.out.println(Thread.currentThread().getName() + "发短信");

  }
   public synchronized static void weixin(DoSomething3 dm) {

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

       System.out.println(Thread.currentThread().getName() + "打微信");


  }

   public synchronized static void kuaishou() {

       System.out.println(Thread.currentThread().getName() + "发快手");
  }
}

5、集合类不安全

1、ArrayList

public class Test01 {

   public static void main(String[] args) {

       //ConcurrentModificationException 并发修改 异常
       //1、线程安全:Vector<>()底层实现:
                       // public synchronized void setSize(int newSize) {...}

       //2、线程安全:Collections.synchronizedList(new ArrayList<>());底层实现:
                       // public static <T> List<T> synchronizedList(List<T> list) {
       //                   return (list instanceof RandomAccess ?
       //                       new SynchronizedRandomAccessList<>(list) :
       //                       new SynchronizedList<>(list));

       //3、JUC下的:CopyOnWriteArrayList<>()
           //读的时候固定   写入时复制 COW 计算机程序设计领域的一种优化策
               //写的时候会先复制一份之前的然后给调用者,写完之后再放回去(防止覆盖)
               //使用了Lock
       //为啥不用vector用CopyOnWriteArrayList
           //vector用的是synchronized比较慢
//       CopyOnWriteArrayList<>():用的是Lock
//           public boolean add(E e) {
//       final ReentrantLock lock = this.lock;
//       lock.lock();
       List<Object> arrayList = new CopyOnWriteArrayList<>();

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

}

2、HashSet

public class Test02 {

   public static void main(String[] args) {

       //他和list没啥两样 就是他没有 vector

       // 1、Collections.synchronizedSet(new HashSet<>());
       // 2、new CopyOnWriteArraySet<>();
     

       Set<Object> set = Collections.synchronizedSet(new HashSet<>());

       for (int i = 1; i <= 10; i++) {
           new Thread(()->{
               set.add(UUID.randomUUID().toString().substring(1,5));
               System.out.println(Thread.currentThread().getName()+" "+set);
          },Integer.toString(i)).start();
      }

       //HashSet底层实现:就是new了一个 map,HashSet其实就是HashMap的key
       //   public HashSet() {
       //       map = new HashMap<>();
       //   }
       //看看add
       //     public boolean add(E e) {
       //       return map.put(e, PRESENT)==null;
       //   }
       //         PRESENT 就是一个类,反正不用private static final Object PRESENT = new Object();
  }
}

3、hashMap

public class Test03 {

   public static void main(String[] args) {

       //new HashMap<>(16,0.75);
       // initialCapacity loadFactor
       //Map<String,String> map = new HashMap<>();

       ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();

       for ( int i=1; i <= 10; i++) {
           new Thread(()->{
               map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(1,5));
               System.out.println(Thread.currentThread().getName()+" "+map);
          },Integer.toString(i)).start();
      }
  }
}
4、ConcurrentHashMap底层原理

6、callable

public class Test04 {

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

       //创建callable
       MyCallable myCallable = new MyCallable();

       //所有线程启动只有一种方式:
       //   new Thread().start();
       //所以想启动 callable线程就必须得想办法把callable放进去
       //而Runnable得子类里面有一个 FutureTask:它就可以作为桥梁把callable放进Thread
       FutureTask task = new FutureTask<Integer>(myCallable);

       //注意
       // 1、此时只会输出一遍,因为callable有缓存
       new Thread(task,"A").start();
       new Thread(task,"B").start();
       Integer result = (Integer) task.get(); // 2、get()可能会阻塞 因为它要等待线程资源返回结果,可以使用异步通信


  }

}
class MyCallable implements Callable<Integer> {

   @Override
   public Integer call() throws Exception {
       System.out.println(Thread.currentThread().getName()+"打印数据");

       return 666;
  }
}

7、常用辅助类(必会)

1、CountDownLatch(计数 减 门闩)
        //1、模拟: 当所有人出教室之后再关门
       //计数器
//       CountDownLatch countDownLatch = new CountDownLatch(10);//10 就表示有六个学生
//       for (int i = 1; i <=10; i++) {
//           final int count=i;
//           new Thread(()->{
//               try {
//                   TimeUnit.SECONDS.sleep(2);
//               } catch (InterruptedException e) {
//                   e.printStackTrace();
//               }
//               //要把所有想要在计数器为零前必须执行的业务放在countDownLatch.countDown()之前
//               //因为多线程下,当最后一个线程执行到countDownLatch.countDown()的时候前面的线程有可能还没执行到该句下面的东西
//               //例如我让线程TimeUnit.SECONDS.sleep(2);
//               countDownLatch.countDown();//减一
//               System.out.println(Thread.currentThread().getName()+"出教室了");//减一
//
//           },String.valueOf(count)).start();
//       }
//       countDownLatch.await();//当计数器不为零时 则一直等待
//       System.out.println("关门了");

2、CyclicBarrier(周期性 阻力)不够不走

//2、模拟集齐七龙珠召唤神龙
//       CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
//           System.out.println("神龙出世");
//       });
//       for (int i = 1; i <=7; i++) {
//           final int count=i;
//           new Thread(()->{
//               System.out.println("已收集第"+count+"颗");
//               try {
//                   TimeUnit.SECONDS.sleep(2);
//               } catch (InterruptedException e) {
//                   e.printStackTrace();
//               }
//               try {
//                   cyclicBarrier.await();//等待七个线程全部执行到这,才会输出我是神龙并且往下继续往下执行
//               } catch (InterruptedException | BrokenBarrierException e) {
//                   e.printStackTrace();
//               }
//           },String.valueOf(count) ).start();
//       }

3、Semaphore 信号量

        //3、信号量:
       // 模拟抢车位
       Semaphore semaphore = new Semaphore(3);//假设有5个车位

       //二十个车要停车
       for (int i = 1; i <= 6; i++) {
           final int count=i;
           new Thread(()->{
               try {
                   semaphore.acquire();//获得车位 如果满了就等待释放
                   System.out.println(Thread.currentThread().getName()+"在车位");
                   TimeUnit.SECONDS.sleep(2);

              } catch (InterruptedException e) {
                   e.printStackTrace();
              }finally {
                   System.out.println(Thread.currentThread().getName()+"离开车位");
                   semaphore.release();
              }

               semaphore.release();//释放车位
          },String.valueOf(count)).start();

      }

8、读写锁

package com.tt.juc;

import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* @author 86176 shkstrat
* @date 2020/11/21 - 15:31
*/
//模拟缓存
// 独占锁(写锁)(一次只有一个锁可以占有)
// 共享锁(读锁)(一次可由多个锁占有)


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

       MyLockCache cache = new MyLockCache();
       for (int i = 1; i <=5 ; i++) {
           int count=i;
           new Thread(()->{
               cache.put(String.valueOf(count),String.valueOf(count));
          },String.valueOf(count)).start();
      }
       for (int i = 1; i <=5 ; i++) {
           int count=i;
           new Thread(()->{
               cache.read(String.valueOf(count));
          },String.valueOf(count)).start();
      }
  }
}
class MyCache{

   private static HashMap<String,String> map = new HashMap<>();
   public void put(String key,String value){

       System.out.println(Thread.currentThread().getName()+"开始写入");
       map.put(key,value);
       System.out.println(Thread.currentThread().getName()+"写入完毕");
  }

   public void read(String key){
       System.out.println(Thread.currentThread().getName()+"开始读");
       String s = map.get(key);
       System.out.println(Thread.currentThread().getName()+"读完毕");

  }
}

class MyLockCache{

   private static HashMap<String,String> map = new HashMap<>();
   ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

   public void put(String key,String value){

       try {
           readWriteLock.writeLock().lock();
           System.out.println(Thread.currentThread().getName()+"开始写入");
           map.put(key,value);
           System.out.println(Thread.currentThread().getName()+"写入完毕");
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           readWriteLock.writeLock().unlock();
      }
  }

   public void read(String key){
       readWriteLock.readLock().lock();
       System.out.println(Thread.currentThread().getName()+"开始读");
       String s = map.get(key);
       System.out.println(Thread.currentThread().getName()+"读完毕");
       readWriteLock.readLock().unlock();

  }
}

9、阻塞队列

阻塞队列和其他集合的一些关系

阻塞队列一些子类

阻塞队列父类: Queue

存、取 四大Api

 抛异常有返回值阻塞等待(一直等)超时等待(设定等待时间)
add(Object value) offer(Object value) put(Object value) offer(Object value,long timeout,TimeUnit unit)
remove() poll() take() poll(long timeout,TimeUnit unit)
得到队列头 element peek    
1、抛出异常
public class Api1 {

   public static void main(String[] args) {
       /*
           抛异常 add和remove
           在存入时:如果队列满了再写入 会抛异常:
                   java.lang.IllegalStateException: Queue full
                   正常时会返回true
           在取时:如果队列为空 会抛异常:
                   java.util.NoSuchElementException

       */
       ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);


       //添加
       try {
           System.out.println(blockingQueue.add("1"));
           System.out.println(blockingQueue.add("2"));
           System.out.println(blockingQueue.add("3"));
           System.out.println(blockingQueue.add("4"));
      } catch (Exception e) {
           e.printStackTrace();
      }
       //拿出
       System.out.println(blockingQueue.remove());
       System.out.println(blockingQueue.remove());
       System.out.println(blockingQueue.remove());
       blockingQueue.remove();
  }
2、不会抛出异常 有返回值
package com.tt.juc.blocking_queue;

import java.util.concurrent.LinkedBlockingQueue;

/**
* @author 86176 shkstrat
* @date 2020/11/22 - 20:16
*/
/*
   有返回值 不抛出异常
   存入时:返回值为false和true 代表失败和成功
           存入null会抛出异常:java.lang.NullPointerException
           //源码
           public boolean offer(E e) {
               if (e == null) throw new NullPointerException();
   读取: 返回值为数据和null null就代表队列空

   看源码会发现: add內部实现其实就是offer 只是当存入失败是add会选择抛异常
                           public boolean add(E e) {
                               if (offer(e))
                                   return true;
                               else
                                   throw new IllegalStateException("Queue full");
                 remove内部实现其实就是 poll():
                            public E remove() {
                                E x = poll();
                                if (x != null)
                                    return x;
                                else
                                    throw new NoSuchElementException();
   }

   }

*/
public class Api2 {

   public static void main(String[] args) {

       /*
           offer: 存入数据
             存入的时候 如果队列满,存入失败会返回false
           poll()不加参数是参数时 队列空,取出失败:返回false
        */
       LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
       System.out.println(blockingQueue.offer(null));
       System.out.println(blockingQueue.offer("2"));
       System.out.println(blockingQueue.offer("3"));
       System.out.println(blockingQueue.offer("4"));//队列满,存入失败:返回false

       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());
       System.out.println(blockingQueue.poll());//队列空,取出失败:返回false
  }

}
3、阻塞等待(一直等)
package com.tt.juc.blocking_queue;

import java.util.concurrent.ArrayBlockingQueue;

/**
* @author 86176 shkstrat
* @date 2020/11/22 - 20:36
*/
/*
阻塞等待:(一直等)
写入时: 队列满,则一直等待有空位的时候写入
读取时: 队列空 则一直等待有数据写入时读
*/
public class Api3 {
   public static void main(String[] args) throws InterruptedException {

       ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
       //put可检测异常 无返回值
       blockingQueue.put(1);
       blockingQueue.put(2);
       blockingQueue.put(3);
       blockingQueue.put(4);//一直等
       //底层实现:
//             try {
//                 while (count == items.length)
//                     notFull.await();
//                 enqueue(e);
//             } finally {
//                 lock.unlock();
//               }

       System.out.println(blockingQueue.take());
       System.out.println(blockingQueue.take());
       System.out.println(blockingQueue.take());
       System.out.println(blockingQueue.take());//一直等



  }
}
4、超时等待(限定等待时间)
package com.tt.juc.blocking_queue;

import org.omg.CORBA.INTERNAL;

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

/**
* @author 86176 shkstrat
* @date 2020/11/22 - 20:45
*/
//超时等待 设计等待时间
   //offer(v1,v2,v3) 且满的时候返回false
   //poll(v1,v2) 且为空时会返回null
public class Api4 {
   public static void main(String[] args) throws InterruptedException {

       ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);

       blockingQueue.offer(1,2, TimeUnit.SECONDS);
       blockingQueue.offer(2,2, TimeUnit.SECONDS);
       blockingQueue.offer(3,2, TimeUnit.SECONDS);
       blockingQueue.offer(4,2, TimeUnit.SECONDS);

       System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
       System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
       System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
       System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));


  }
}

10、同步队列

不存储元素(或者说只存一个),必须写一个取一个

//同步队列 SynchronousQueue
   //不存储元素,必须写一个取一个
// 它其中的一个父类就是blockingQueue 所以他也有四大api
//     SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
public class Test01 {
   public static void main(String[] args) {

       SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>();
//put take模拟:存一个之后 不取不存
       new Thread(()->{
           try {
               System.out.println(Thread.currentThread().getName()+"存入1");
               synchronousQueue.put(1);
               System.out.println(Thread.currentThread().getName()+"存入2");
               synchronousQueue.put(2);
               System.out.println(Thread.currentThread().getName()+"存入3");
               synchronousQueue.put(3);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }).start();

       new Thread(()->{
           try {
               TimeUnit.SECONDS.sleep(2);
               System.out.println(synchronousQueue.take() + "读取");
               TimeUnit.SECONDS.sleep(2);
               System.out.println(synchronousQueue.take() + "读取");
               TimeUnit.SECONDS.sleep(2);
               System.out.println(synchronousQueue.take() + "读取");
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }).start();
  }

}

11、线程池 (重点)

池化技术

对一些资源的来回创建和销毁十分浪费资源,

池化技术:事先准备好这些资源,要了直接拿,哟用完直接放进去

比如:连接池,内存池等等。。。

线程池的好处:

1、降低资源消耗

2、提高响应速度

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

学习可从三个方面下手:

三大方法、7大参数、四大策略

三大方法

// Executors.newSingleThreadExecutor();//只放一个线程的线程池 // Executors.newFixedThreadPool(3);//指定线程数的线程池 // Executors.newCachedThreadPool();//伸缩性,需要几个就有几个

注意:线程池不允许使用Exeutors去创建,而是通过Thread PoolExecutor的方式创建,

Exeutors的弊端:

1、FixedThreadPool和SingleThreadPool:

允许的请求队列长度为Integer.MAX_VALUE(约21个亿),可能会堆积大量的请求,造成OOM(堆内存溢出)

2、CachedThreadPool和ScheduledThreadPool:

允许创建的数量为Integer.MAX_VALUE(约21个亿),可能会创建大量的线程,造成OOM(堆内存溢出)

看到七大参数就明白了

package com.tt.juc.pool;

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

/**
* @author 86176 shkstrat
* @date 2020/11/24 - 18:55
*/
//线程池三大方法
   //Executors:工具类
public class Test01 {

   public static void main(String[] args) {

       //三大方法:
//       Executors.newSingleThreadExecutor();//只放一个线程的线程池
//       Executors.newFixedThreadPool(3);//指定线程数的线程池
//       Executors.newCachedThreadPool();//伸缩性,需要几个就有几个

       // 模拟用线程池工作
       // 假设有五个任务需要完成

       //1、
//       ExecutorService threadExecutor = Executors.newSingleThreadExecutor();//只放一个线程的线程池
//
//       try {
//           for (int i = 1; i <= 5; i++) {
//               //以前是启动一个线程然后传入runnable
//               //现在直接把runnable放入线程池
//               threadExecutor.execute(()->{
//                   System.out.println(Thread.currentThread().getName() + "参与任务");
//               });
//               pool-1-thread-1参与任务
//               pool-1-thread-1参与任务
//               pool-1-thread-1参与任务
//               pool-1-thread-1参与任务
//               pool-1-thread-1参与任务
//               都是一个线程在执行
//           }
//       } catch (Exception e) {
//           e.printStackTrace();
//       } finally {
//           threadExecutor.shutdown();
//       }


//     2、
//       ExecutorService threadExecutor = Executors.newFixedThreadPool(3);//指定线程数的线程池
//       try {
//           for (int i = 1; i <=5 ; i++) {
//               threadExecutor.execute(()->{
//                   System.out.println(Thread.currentThread().getName() + "参与任务");
//               });
//           }
////           pool-1-thread-1参与任务
////           pool-1-thread-2参与任务
////           pool-1-thread-3参与任务
////           pool-1-thread-2参与任务
////           pool-1-thread-1参与任务
////             三个线程执行任务
//       } catch (Exception e) {
//           e.printStackTrace();
//       } finally {
//           threadExecutor.shutdown();
//       }

//       3、
       ExecutorService threadExecutor = Executors.newCachedThreadPool();//伸缩性,需要几个就有几个
       try {
           for (int i = 1; i <=5 ; i++) {
               threadExecutor.execute(()->{
                   System.out.println(Thread.currentThread().getName() + "参与任务");
              });
          }
//           pool-1-thread-1参与任务
//           pool-1-thread-4参与任务
//           pool-1-thread-2参与任务
//           pool-1-thread-3参与任务
//           pool-1-thread-5参与任务
//             五个线程执行任务
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           threadExecutor.shutdown();
      }
  }
}

七大参数

// int corePoolSize, 线程数核心大小(相当于银行正常工作窗口) // int maximumPoolSize, 线程数最大值(相当于最大工作窗口) // long keepAliveTime, 等待时间(窗口没人办理业务的时间) // TimeUnit unit, 时间单位() // BlockingQueue<Runnable> workQueue, 阻塞队列(相当于候客区座位数) // ThreadFactory threadFactory, 线程工厂:创建线程,一般不动 // RejectedExecutionHandler handler) 拒绝策略:四大策略 候客区都满了之后,对再进进银行

package com.tt.juc.pool;

import java.util.concurrent.*;

/**
* @author 86176 shkstrat
* @date 2020/11/24 - 20:27
*/
//七大参数
public class Test02 {

   public static void main(String[] args) {

       //来,先看三大方法的源码:
//       public static ExecutorService newSingleThreadExecutor() {
//           return new Executors.FinalizableDelegatedExecutorService
//                   (new ThreadPoolExecutor(1, 1,
//                           0L, TimeUnit.MILLISECONDS,
//                           new LinkedBlockingQueue<Runnable>()));
//       }
//       public static ExecutorService newFixedThreadPool(int nThreads) {
//           return new ThreadPoolExecutor(nThreads, nThreads,
//                   0L, TimeUnit.MILLISECONDS,
//                   new LinkedBlockingQueue<Runnable>());
//       }
//       public static ExecutorService newCachedThreadPool() {
//           return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
//                   60L, TimeUnit.SECONDS,
//                   new SynchronousQueue<Runnable>());
//       }
//       很明显,内部实现都是用:
//       new ThreadPoolExecutor(0, Integer.MAX_VALUE,
//                           60L, TimeUnit.SECONDS,
//                           new SynchronousQueue<Runnable>());
//       现在看看:ThreadPoolExecutor
//public ThreadPoolExecutor(
//       int corePoolSize,                     线程数核心大小(相当于银行正常工作窗口)
//       int maximumPoolSize,                 线程数最大值(相当于最大工作窗口)
//       long keepAliveTime,                   等待时间(窗口没人办理业务的时间)
//       TimeUnit unit,                       时间单位()
//       BlockingQueue<Runnable> workQueue,   阻塞队列(相当于候客区座位数)
//       ThreadFactory threadFactory,         线程工厂:创建线程,一般不动
//       RejectedExecutionHandler handler)     拒绝策略:四大策略 候客区都满了之后,对再进进银行人的处理办法
       //七大参数出现了,

       //现在根据七大参数,自定义一个线程池
       //模拟银行办理业务:
       // 正常开放两个窗口(核心线程数),候客区(阻塞队列)有三个座位,窗口有人办理业务时再进来的人去候客区等待,
       // 当候客区(阻塞队列)满了之后,开启另外三个窗口(最大线程数减去核心);
       // 如果业务办完之后如果窗口超过一定时间没人来(等待时间),就关闭窗口;
       // 如果窗口全部开放而且,客区仍然满,再来的人,就想办法处理(拒绝策略),其实能承受的最大人数就是 阻塞队列大小加最大线程数

       //创建线程池
       ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
               2,
               5,
               3,
               TimeUnit.SECONDS,
               new LinkedBlockingQueue<>(3),
               Executors.defaultThreadFactory(),
               new ThreadPoolExecutor.DiscardOldestPolicy());//尝试去和最早执行任务的线程竞争

       try {
           for (int i = 1; i <=9 ; i++) {
               poolExecutor.execute(()->{
                   System.out.println(Thread.currentThread().getName() + "参与业务办理");
              });

          }
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           poolExecutor.shutdown();
      }
  }
}

四大策略

//    四大策略
//   new ThreadPoolExecutor.AbortPolicy():抛出异常:RejectedExecutionException
//   new ThreadPoolExecutor.CallerRunsPolicy());//哪来的回哪去,这个就是main线程执行了此任务
//   new ThreadPoolExecutor.DiscardPolicy());//直接丢弃
//   new ThreadPoolExecutor.DiscardOldestPolicy());//尝试去和最早执行任务的线程竞争

拓展:线程池的最大大小怎么确定

1、cpu密集

电脑是几核cpu就定几(Runtime.getRuntime().availableProcessors())

保证最大运行效率

2、IO密集度

大于程序中十分消耗IO的线程数量(通常二倍)

12、新时代程序员必备

lambda表达式、函数式接口、链式编程·、Stream流式计算

四大函数式接口:

函数式接口:只有一个方法的接口,只要是函数式接口都可以用lambda表达式表示

public class Test01 {

   public static void main(String[] args) {

//只要是函数时接口都可以用lambda表达式表示
       //1、Function 一个输入参数,有返回值

       // public interface Function<T, R> {
       //   R apply(T t);

       //看源码:第一个参数表示参数类型,第二个是返回值类型
//       Function<String, String> stringFunction = new Function<String, String>() {
//           @Override
//           public String apply(String o) {
//               return o;
//           }
//       };
       //lambda表达式:
       Function<String,String> stringFunction=(str)->{
           return str;
      };
       System.out.println(stringFunction.apply("function"));

       //2、断定型接口 只有一个输入参数,返回值只能是Boolean值
       //源码:

       // public interface Predicate<T> {
       //   boolean test(T t);

//       Predicate<Boolean> booleanPredicate = new Predicate<Boolean>() {
//           @Override
//           public boolean test(Boolean a) {
//               return false;
//           }
//       };
       //lambda表达式
       Predicate<Boolean> booleanPredicate = (bool)->{
         return bool;
      };
       System.out.println(booleanPredicate.test(true));

       //3、Consumer 消费型接口 有一个参数,没有返回值
       //源码

       //public interface Consumer<T> {
       //   void accept(T t);
//
//       Consumer<Integer> consumer = new Consumer<Integer>() {
//           @Override
//           public void accept(Integer integer) {
//               System.out.println(integer);
//           }
//       };
       //lambda
       Consumer<Integer> consumer = (integer)->{
           System.out.println(integer);
      };
       consumer.accept(10);

       //4、Supplier供给型接口 没有参数,只有返回值
       //源码

       //public interface Supplier<T> {
       //   T get();

//       Supplier supplier = new Supplier<Integer>() {
//           @Override
//           public Integer get() {
//               return null;
//           }
//       };

       //lambda
       Supplier<Integer> supplier = ()->{
         return 3;
      };
       System.out.println(supplier.get());

Stream流式计算

什么是Stream流式计算

大数据:存储+计算

集合、MySQL本质就是存储东西的;

计算都应该交给流来操作!

 /**
    *题目要求:一分钟内完成此题,只能用一行代码实现!
    *现在有5个用户!筛选:
    *1、ID必须是偶数
    *2、年龄必须大于23岁
    *3、用户名转为大写字母
    *4、用户名字母倒着排序
    *5、只输出一个用户!
    */
   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);
       User u6=new User(6,"f",26);
       //集合就是存储
       List<User> userList = Arrays.asList(u1, u2, u3, u4, u5,u6);

       //计算交给 Stream流
       userList.stream()
              .filter((user) -> {
                   return user.getP() % 2 == 0;
              })
              .filter(user -> {
                   return user.getAge() > 23;
              })
              .map(user-> {return user.getName().toUpperCase();})
              .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
              .limit(1)
              .forEach(System.out::println);
  }

13、JMM

关于JMM的一些同步的约定

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

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

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

什么是JMM

JMM是一种概念,就是多线程顺利通信的一种规范、约定

下图是粗略示意

细分有八种操作

上图其实是有问题的:

当AB线程读取资源之后,如果B修改了,那A不知道啊

volatile应运而生

14、volatile

volatile是Java虚拟机提供轻量级的同步机制

volatile三大特性:

1、可见性

2、不保证原子性

3、防止指令重排

1、可见性

当一个共享内存被一个线程修改时,要让其他所有线程知道此线程所做的修改

package com.tt.juc.volatileTest;

import java.util.concurrent.TimeUnit;

/**
* @author 86176 shkstrat
* @date 2020/11/29 - 20:44
*/
//volatile的可见性
   //当共享资源被一个线程修改后,会立刻通知其他线程它已经做了修改
public class Test01 {

   private static volatile int num=0;//不加volatile A线程一直再while循环内
   public static void main(String[] args) throws InterruptedException {

       new Thread(()->{
           while (num==0){

          }
      },"A").start();
       TimeUnit.SECONDS.sleep(1);
       num=1;
       System.out.println("num已经修改 num=>"+num);
  }

}

2、不保证原子性

原子性:不可分割

一个线程执行任务时,不能被其他线程影响,要么全部成功,要么全部失败

package com.tt.juc.volatileTest;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author 86176 shkstrat
* @date 2020/11/29 - 20:51
*/
//volatile不保证原子性
public class Test02 {
   //原子性: 一个线程执行任务时,不能被其他线程影响,要么全部成功,要么全部失败
   //private static int sum=0;
   private static AtomicInteger sum = new AtomicInteger();

   static void  add(){
       sum.getAndDecrement();
  }
   public static void main(String[] args) {

       for (int i = 0; i < 20; i++) {
           new Thread(()->{
               for (int j = 0; j < 2000; j++) {

                   sum++;
                   //它不是一个原子性操作:
                   //一个num++有三部:
                                  //1、获得这个值
                                  //2、+1
                                  //3、写回
                   //所以当这个线程刚刚获得的时候,令一个线程可能过来捣乱,那么这个线程对他的这次修改将到此结束
              }
          }).start();
      }

       while(Thread.activeCount()>2){
           Thread.yield();
      }
       System.out.println("所有线程执行完了,sum为-》"+sum);//结果各种各样
  }

}

不加synchronized,Lock怎样解决不保证原子性问题;

引入原子类:

package com.tt.juc.volatileTest;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author 86176 shkstrat
* @date 2020/11/29 - 20:51
*/
//volatile不保证原子性
public class Test02 {
   //原子性: 一个线程执行任务时,不能被其他线程影响,要么全部成功,要么全部失败
   //private static int sum=0;
   private static AtomicInteger sum = new AtomicInteger();

   static void  add(){
       sum.getAndDecrement();
  }
   public static void main(String[] args) {

       //不使用synchronized Lock
       //使用原子类解决上述问题
       // private static AtomicInteger sum = new AtomicInteger();
       for (int i = 0; i < 20; i++) {
           new Thread(()->{
               for (int j = 0; j < 2000; j++) {
                   sum.getAndIncrement();

                   //它不是一个原子性操作:
                   //一个 num++ 有三步:
                                  //1、获得这个值
                                  //2、+1
                                  //3、写回
                   //所以当这个线程刚刚获得的时候,令一个线程可能过来捣乱,那么这个线程对他的这次修改将到此结束

              }
          }).start();
      }

       while(Thread.activeCount()>2){
           Thread.yield();
      }
       System.out.println("所有线程执行完了,sum为-》"+sum);//
  }

}

3、禁止指令重排

指令重排:程序不按自己编写的顺序执行

比如

int x=2;
int y;
x=3;
y=x;

正常:1、2、3、4 //可能是2、1、3、4 1、3、2、4

//但不会是3、4、1、2 因为它会检测依赖 3、4 依赖着1、2

volatile怎样禁止指令重排:

这里要了解一个概念:内存屏障

作用:

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

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

image-20201201213745788

最能体现volatile的一个地方:单例模式(DCL懒汉模式体现的很全面)

15、彻底玩转单例模式

饿汉式

package com.tt.juc.volatileTest;

import java.util.ArrayList;

/**
* @author 86176 shkstrat
* @date 2020/11/30 - 11:03
*/
//饿汉模式
public class Hungry {

//   ArrayList list = new ArrayList<String>(100000);
//   ArrayList list2 = new ArrayList<String>(100000);
//   ArrayList list3 = new ArrayList<String>(100000);
//   比如这种,太占空间


   private Hungry(){

  }
   private static Hungry hungry = new Hungry();
   public static Hungry getHungry(){
       return hungry;
  }

   public static void main(String[] args) {
       System.out.println(Hungry.getHungry());
       System.out.println(Hungry.getHungry());
       System.out.println(Hungry.getHungry());
  }
}

问题:

浪费空间,不论对象会不会用到都会先生成 懒汉模式来了:

懒汉模式(加DCL懒汉模式:双重检测锁模式)

package com.tt.juc.volatileTest;

import com.sun.org.apache.xerces.internal.xs.ItemPSVI;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author 86176 shkstrat
* @date 2020/11/30 - 11:09
*/
//懒汉模式
public class LazyMan {


   private LazyMan(){
       if (temp==true){
           temp=false;
      }else {
           throw new RuntimeException("别想用反射破坏");
      }
//       System.out.println(Thread.currentThread().getName()+"执行");
  }

   //刚开始并不创建对象,只定义;使用的时候再创建
   private static volatile LazyMan lazyMan;
   //这里必须要加volatile
   //创建一个类实例实际,他并不是原子性操作,需要三步:
   // 1、分配内存空间
   // 2、执行构造方法、初始化对象
   // 3、把这个对象指向这个空间
// 如果不加volatile,不禁止指令重排:
  //他很肯会A线程指行 132指行
  //如果此时B线程又来指行,lazyMan并不为空,但是lazyMan也没有构造完成

   private static boolean temp=true;//红绿灯机制
                                       //但事实上反射连它也能破坏

   public static LazyMan getLazyMan(){
       //双重检测锁模式 DCL volatile
       if (lazyMan==null){
           synchronized (LazyMan.class){
              if (lazyMan==null){
                   lazyMan = new LazyMan();
              }
          }
      }
       return lazyMan;
  };

   public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
       //并发下,懒汉模式会出错
       //所以出现了 DCL
       for (int i = 0; i < 6; i++) {
           new Thread(()->{
               System.out.println(LazyMan.getLazyMan());
          }).start();

      }
  }
}

反射破坏单例模式的一些举例(结合上面代码)

package com.tt.juc.volatileTest;

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

/**
* @author 86176 shkstrat
* @date 2020/12/1 - 16:27
*/
public class Test04 {
   public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//       //并发下,懒汉模式会出错
//       //所以引出了DCL双重检测锁模式
//       for (int i = 0; i < 6; i++) {
//           new Thread(()->{
//               System.out.println(LazyMan.getLazyMan());
//           }).start();
//       }

       //1、反射破坏无参构造函数的外部无法访问
       Class aClass =LazyMan.class;
       Constructor declaredConstructor = aClass.getDeclaredConstructor(null);
       declaredConstructor.setAccessible(true);//关闭安全检测机制
       LazyMan lazyMan1 = (LazyMan) declaredConstructor.newInstance();
       LazyMan lazyMan2 = (LazyMan) declaredConstructor.newInstance();
       System.out.println(lazyMan1.hashCode());//结果
       System.out.println(lazyMan2.hashCode());//不一样

       //此时再加一个红绿灯机制,防止这种破坏

       //但是通过反射可以把红绿灯也破坏。。
       //道高一尺魔高一丈



//
//
//       Method getLazyMan = aClass.getMethod("getLazyMan");
//       getLazyMan.setAccessible(true);//关闭程序安全检测机制
//       LazyMan lazyMan = (LazyMan) getLazyMan.invoke(LazyMan.class);
//       System.out.println(lazyMan);
  }
}

枚举 防止反射破坏单例模式

package com.tt.juc.volatileTest;

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

/**
* @author 86176 shkstrat
* @date 2020/12/1 - 16:57
*/
public enum EnumSignal {
   INSTANCE;
   public static EnumSignal getInstance(){
       return INSTANCE;
  }

   public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
       //System.out.println(EnumSignal.getInstance().getClass());
       //试试枚举的单例模式 能不能被反射破坏
      Class c= EnumSignal.class;
       Constructor declaredConstructor = c.getDeclaredConstructor(String.class, int.class);
       declaredConstructor.setAccessible(true);
       Object o = declaredConstructor.newInstance(String.class, int.class);
       System.out.println(o);
   
       //报错
//       Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
// at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
// at com.tt.juc.volatileTest.EnumSignal.main(EnumSignal.java:22)
  }
}

深入理解CAS

package com.tt.juc.casTest;

import java.util.concurrent.atomic.AtomicInteger;

/**
* @author 86176 shkstrat
* @date 2020/12/1 - 19:22
*/
public class test01 {
   //CAS 比较并交换
   public static void main(String[] args) {
       AtomicInteger atomicInteger = new AtomicInteger(2020);
       System.out.println(atomicInteger.compareAndSet(2020, 2021));

//     compareAndSet()底层实现unsafe.compareAndSwapInt
//       源码
//         public final boolean compareAndSet(int expect, int update) {
//             return (this, valueOffset, expect, update);
//         }

       System.out.println(atomicInteger.get());
       atomicInteger.getAndDecrement();
//       getAndDecrement(); 实现:unsafe.getAndAddInt(this, valueOffset, -1);
//       源码:
//       public final int getAndDecrement() {
//         return unsafe.getAndAddInt(this, valueOffset, -1);
//       }

//       unsafe.getAndAddInt(this, valueOffset, -1);
//       源码:
//       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;
//   }

  }
}

主要理解unsafe类

以及:

 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;
//   }

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

缺点:

  1. 循环会耗时

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

  3. ABA问题

16、ABA问题

(狸猫换太子)

A线程操作一个变量,B线程也操作这个变量;假如B把变量先换成其他值,然后再换回来;当A去换的时候虽然还是它所期望的值,但实际上已经被做了手脚

看代码

package com.tt.juc.abaTest;

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

/**
* @author 86176 shkstrat
* @date 2020/12/1 - 22:07
*/
public class Test01 {

   public static void main(String[] args) {

       //模拟ABA问题
       AtomicInteger atomicInteger = new AtomicInteger(2021);

       //捣乱线程
       System.out.println(atomicInteger.compareAndSet(2021, 2020));//true
       System.out.println(atomicInteger.compareAndSet(2020, 2021));//true
       
       //其实 此刻的2021已非那时的2021
       System.out.println(atomicInteger.compareAndSet(2021, 666));//true
  }
}

问题解决:原子引用

原子引用

加个版本 乐观锁思想

package com.tt.juc.abaTest;

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

/**
* @author 86176 shkstrat
* @date 2020/12/1 - 22:07
*/
public class Test01 {

   public static void main(String[] args) {
       //解决ABA问题 思想:乐观锁
       AtomicStampedReference atomicStampedReference = new AtomicStampedReference<Integer>(2,1);
       new Thread(()->{
           int stamp = atomicStampedReference.getStamp();
           System.out.println(Thread.currentThread().getName()+"得到(版本)时间戳"+stamp);
           System.out.println(Thread.currentThread().getName()+"得到此时的值"+atomicStampedReference.getReference());
           try {
               TimeUnit.SECONDS.sleep(2);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           System.out.println("A线程第一次修改"+atomicStampedReference.compareAndSet(2, 10, stamp, stamp + 1));

           stamp = atomicStampedReference.getStamp();
           System.out.println("A线程第二次修改"+atomicStampedReference.compareAndSet(10, 2, stamp, stamp + 1));


      },"A").start();

       new Thread(()->{
           int stamp = atomicStampedReference.getStamp();
           System.out.println(Thread.currentThread().getName()+"得到时间戳"+stamp);
           System.out.println(Thread.currentThread().getName()+"得到此时的值"+atomicStampedReference.getReference());
           try {
               TimeUnit.SECONDS.sleep(2);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           System.out.println("B线程比较并修改"+atomicStampedReference.compareAndSet(2, 10, stamp, stamp + 1));
           
      },"B").start();
//A得到时间戳1
//A得到此时的值2
//B得到时间戳1
//B得到此时的值2
//A线程第一次修改true
//A线程第二次修改true
//B线程比较并修改false        
       
  }
}

17、公平锁、非公平锁

公平锁

线程调度严格遵先来后到,排队

非公平锁(基本都用非公平锁)

线程调度不用遵先来后到,可插队(前面3小时,后面1s 你说按公平锁合适吗)

new ReentrantLock();

public ReentrantLock() {

   sync = new NonfairSync();
   //默认就是非公平锁
}

new ReentrantLock(true);

public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
   //fair为true就是公平锁,防止==否则就是非公平锁
}

18、可重入锁

获得外面的锁,自动获得里面的所有锁

看代码理解

synchronized版

package com.tt.juc.locksTest;

import java.util.concurrent.TimeUnit;

/**
* @author 86176 shkstrat
* @date 2020/12/2 - 10:44
*/
public class Test01 {
   public static void main(String[] args) {
       Phone phone = new Phone();
       new Thread(()->{
           phone.call();//调用打电话
           //输出结果永远是
           //A打电话
           //A发信息
           // B打电话
           //B发信息
           //肯定是一次性执行两把锁,然后一起释放
      },"A").start();

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

}
class Phone{
   synchronized void call(){
       System.out.println(Thread.currentThread().getName() + "打电话");
       send();//这里也有一把锁
  }
   synchronized void send(){
       System.out.println(Thread.currentThread().getName() + "发信息");
  }
}

lock版

package com.tt.juc.locksTest;

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

/**
* @author 86176 shkstrat
* @date 2020/12/2 - 10:58
*/
public class Test02 {
   public static void main(String[] args) {
       Phone2 phone = new Phone2();
       new Thread(()->{
           phone.call();//调用打电话
           try {
               TimeUnit.SECONDS.sleep(3);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }

           //睡眠三秒,调用的是call锁的是实例对象,
           // 而send锁的是类,所以即使我不释放锁call锁,按理说也不会影响类锁
           // 让B线程去调用发信息,看看他能不能有机会
           //输出结果永远是
           //A打电话
           //A发信息
           //B发信息
      },"A").start();

       new Thread(()->{
           Phone2.send();
      },"B").start();
  }

}
class Phone2{
   void call(){
       ReentrantLock lock = new ReentrantLock();
       lock.lock();
       try {
           System.out.println(Thread.currentThread().getName() + "打电话");
           send();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
   static void send(){
       ReentrantLock lock = new ReentrantLock();
       lock.lock();
       try {
           System.out.println(Thread.currentThread().getName() + "发信息");
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
}

19、自旋锁

自定义锁

package com.tt.juc.locksTest;

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

/**
* @author 86176 shkstrat
* @date 2020/12/2 - 11:22
*/
//自定义自旋锁
public class SpinLockDemo {

   //引入自旋
   private AtomicReference<Thread> atomicInteger = new AtomicReference<>();
   //加锁
   void myLock(){
       Thread thread = Thread.currentThread();
      // System.out.println(Thread.currentThread().getName() + "myLock");
       while (!atomicInteger.compareAndSet(null,thread)){

      }
  }

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

}

测试

package com.tt.juc.locksTest;

import sun.security.provider.ConfigFile;

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

/**
* @author 86176 shkstrat
* @date 2020/12/2 - 11:22
*/
public class Test03 {
   //验证一下

   public static void main(String[] args) {
       //SpinLockDemo mylock = new SpinLockDemo();
       ReentrantLock reentrantLock = new ReentrantLock();
       Tickets tickets = new Tickets();
       for (int i = 0; i < 10; i++) {
           new Thread(()->{
               tickets.add();
          }).start();

      }
       for (int i = 0; i < 20; i++) {
           new Thread(()->{
               try {
                   TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               tickets.add1();
          }).start();
      }
       while (Thread.activeCount()>2){

      }
       System.out.println("Tickets.temp "+Tickets.temp);
       System.out.println("Tickets.temp2 "+Tickets.temp2);
  }
}
class Tickets{
   static  int temp=0;
   static int temp2=0;
   static int temp3=0;
   Lock reentrantLock = new ReentrantLock();
   SpinLockDemo spinLockDemo =  new SpinLockDemo();
   void add(){
       reentrantLock.lock();
       temp++;
       reentrantLock.unlock();
  }

   void add1(){
       spinLockDemo.myLock();
       temp2++;
       spinLockDemo.myUnLock();
  }
}

20、死锁

什么是死锁

设计一个死锁

设计思路

        // 假如此时先执行x,x进去之后,线程x拿到变量a这把锁,然后睡眠,等待y进去
       // y进去之后拿到了b这把锁,然后y又睡眠两秒
       // x睡眠结束,执行synchronized,想要获得b这把锁,可是他却被y拿着,x只好等待y放弃
       // y睡眠结束,执行synchronized。想要获得a这把锁,可是他却被x拿着,y也只好等x放弃
package com.tt.juc.locksTest;

import java.util.concurrent.TimeUnit;

/**
* @author 86176 shkstrat
* @date 2020/12/2 - 13:57
*/
public class DeadLock {

   public static void main(String[] args) {
       String a="A";
       String b="B";
       new Thread(new Resource(a,b),"x").start();
       new Thread(new Resource(b,a),"y").start();
       // 假如此时先执行x,x进去之后,线程x拿到变量a这把锁,然后睡眠,等待y进去
       // y进去之后拿到了b这把锁,然后y又睡眠两秒
       // x睡眠结束,执行synchronized,想要获得b这把锁,可是他却被y拿着,x只好等待y放弃
       // y睡眠结束,执行synchronized。想要获得a这把锁,可是他却被x拿着,y也只好等x放弃

  }

}
class Resource implements Runnable{

   String a;
   String b;
   Resource(String a,String b){
       this.a=a;
       this.b=b;
  }

   @Override
   public void run() {
       synchronized (this.a){
           System.out.println("锁"+this.a + "要获得" + this.b);
           //睡两秒,让下一个线程进来
           try {
               TimeUnit.SECONDS.sleep(2);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           synchronized (this.b){

          }
      }
  }
//   检查死锁办法
   //Terminal下 使用 jps -l定位进程号
   //使用 jstack 进程号 检查程序出现的问题
}

解决办法:

在程序允许的条件下

在Terminal控制台输入

1、

jps -l 定位执行程序的进程号

2、

jstack 进程号 查看错误



 

 

如果有错,还请指出

posted on 2021-03-11 21:57  bkytian  阅读(82)  评论(0编辑  收藏  举报

导航