JUC -java.util.current

JUC基础回顾

1.sleep与wait的区别:

  1. 来自不同的的类
    • sleep来着Thread
    • wait来着Object
  2. 关于锁的释放
    • wait会释放锁
    • sleep抱着锁睡觉,不会释放锁
  3. 使用的位置不同
    • wait必须在同步代码块中使用
    • 而sleep可以在任何地方使用

2.线程的6中状态

  1. NEW新生
  2. RUNNABLE运行
  3. BLOCKED阻塞
  4. WAITING等待,死死的等待
  5. TIMED_WAITING超时等待,超时后不再等待
  6. TREMINATED终止

3.写多线程的正确姿势

  1. synchronized写法

    //正确的所线程写法,不要通过实现Runnable接口,这样会使程序的耦合性增大
    public class TestTicket {
        public static void main(String[] args) {
            //1.创建资源对象
            Ticket ticket=new Ticket();
            //2.创建线程,通过Lambda表达式  Runnable为函数式接口
            new Thread(()->{
                for (int i = 0; i < 20; i++) {
                    ticket.sale();
                }
            },"A").start();
            new Thread(()->{
                for (int i = 0; i < 20; i++) {
                    ticket.sale();
                }
            },"B").start();
            new Thread(()->{
                for (int i = 0; i < 20; i++) {
                    ticket.sale();
                }
            },"C").start();
        }
    
    
    }
    //资源类 OOP 票
    class Ticket{
        //属性 方法
        private int Number=30;
    
         synchronized  void  sale(){
            if(!(Number<=0)){
              /*  try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                System.out.println(Thread.currentThread().getName()+"获得了第"+Number+"票,还剩-----》"+(--Number));
            }
        }
    }
    
  2. Lock写法

      import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestTicket1  {
        public static void main(String[] args) {
            //1.创建资源对象
            Ticket1 ticket=new Ticket1();
            //2.创建线程,通过Lambda表达式  Runnable为函数式接口
            new Thread(()->{
                for (int i = 0; i < 20; i++) {
                    ticket.sale();
                }
            },"A").start();
            new Thread(()->{
                for (int i = 0; i < 20; i++) {
                    ticket.sale();
                }
            },"B").start();
            new Thread(()->{
                for (int i = 0; i < 20; i++) {
                    ticket.sale();
                }
            },"C").start();
        }
    
    
    }
    //资源类 OOP 票
    class Ticket1{
        //属性 方法
        private int Number=30;
        private Lock lock=new ReentrantLock();
    
        //lock.lock(); 放在方法外,不行
          void  sale(){
              lock.lock();
              try {
                  if(!(Number<=0)){
                      System.out.println(Thread.currentThread().getName()+"获得了第"+Number+"票,还剩-----》"+(--Number));
                  }
              } catch (Exception e) {
                  e.printStackTrace();
              }
              finally {
                  lock.unlock();
              }
          }
    
    }
    

4.synchronized和Lock的区别:

  1. synchronized时Java内置关键字,而Lock是java类
  2. synchronized无法判断是否获取到锁,而Lock可以
  3. synchronized可以自动释放锁,而Lock需要手动释放锁,否则会发生死锁
  4. synchronized如果线程1获得锁或者阻塞则对于线程2就是等待,而Lock就不一定会等待下去
  5. synchronized可重入锁,不可以中断的,非公平,Lock可重入锁,非公平

5. 传统的生产者消费者问题,防止虚假唤醒

package day8_2.PC;

public class PCtrad {
    public static void main(String[] args) {
        Data data=new Data();
        //实现A B两个进程之间的通信
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 20; i++) {
                    data.decrement();
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}
//pc问题的模板:判断是否等待  业务  唤醒
class Data{//资源类
    private int number=0;

   // +1
    synchronized  void  increment() throws InterruptedException {
        if(number!=0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知唤醒另一进程
        this.notifyAll();
    }
    // -1
   synchronized  void  decrement() throws InterruptedException {
        if(number==0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知唤醒另一进程
        this.notifyAll();
    }
}

问题:如果存在A,B,C,D等多个线程时,可能产生的虚假唤醒的问题,何为虚假唤醒,即线程有可能在不被通知的情况下唤醒,虽然这种情况很少发生,但是还是要通过不断测试应该使线程唤醒的条件来防范问题的发生。即将if条件改为while条件。

6.Lock版生产者消费者

package LockPC;

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

public class A {
    public static void main(String[] args) {
        Data data=new Data();
        //实现A B两个进程之间的通信
      new Thread(()->{
            for (int i = 0; i < 3; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 3; i++) {
                    data.decrement();
                }

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

     new Thread(()->{
        try {
            for (int i = 0; i < 3; i++) {
                data.increment();
            }

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

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

}
}

//Condition作用替换wait和notifyall
//condition.await()代替wait()
//conditon.signalAll()代替notifyAll
class Data{//资源类
    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();//理解为信号量的pv操作?
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            //通知唤醒另一进程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    // -1
 public    void  decrement() throws InterruptedException {
         lock.lock();
        try {
            while(number==0){
                //等待
                condition.await();//理解为信号量的pv操作?
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            //通知唤醒另一进程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

7.解决顺序问题

package LockPC;

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

public class ConditionPC {
    public static void main(String[] args) {
        Data1 data=new Data1();
        new Thread(()->{
            for (int i = 0; i < 2; i++) {
                data.PrintA();
            }
         },"A").start();
        new Thread(()->{
            for (int i = 0; i < 2; i++) {
            data.PrintB();
        }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 2; i++) {
                data.PrintC();
            } },"C").start();
    }

}
class Data1{
    //A通知B执行,B通知C执行,C通知A执行
    int number=1;
    Lock lock=new ReentrantLock();
    Condition condition1= lock.newCondition();
    Condition condition2=lock.newCondition();
    Condition condition3=lock.newCondition();
    public  void PrintA(){
        lock.lock();
        try {
            //业务代码;
            while (number!=1){
                condition1.await();
            }
            number=2;
            System.out.println(Thread.currentThread().getName()+"执行完,通知B");
            condition2.signal();//通知B执行
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public  void PrintB(){
        lock.lock();
        try {
            while (number!=2){
                condition2.await();
            }
           number=3;
            System.out.println(Thread.currentThread().getName()+"执行完,通知C");
            condition3.signal();//通知C执行
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public  void PrintC(){
        lock.lock();
        try {
          while (number!=3){
              condition3.await();
          }
          number=1;
            System.out.println(Thread.currentThread().getName()+"执行完,通知A");
            condition1.signal();//唤醒A
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

8.集合类不安全

  1. list不安全

    通过以下三种方式解决

    List list1=new Vector();
    
     List list2= Collections.synchronizedList(new ArrayList<>());
    
     List<String> list3=new CopyOnWriteArrayList<>();
    
    public class A {
        public static void main(String[] args) throws InterruptedException {
    
          // List list=new ArrayList<>(); //线程不安全
          /List<String> list=new CopyOnWriteArrayList<>();
            CountDownLatch countDownLatch=new CountDownLatch(5000);
    
                for (int i = 0; i < 5000; i++) {
                    new Thread(()->{
                        list.add(Thread.currentThread().getName());
                        countDownLatch.countDown();
                    },String.valueOf(i)).start();
                }
                countDownLatch.await();//添加此方法让5000个子线程执行完之后再执行以下两句,否则子线程和主线程(main)并发执行,可能输出的大小不是5000
               // System.out.println(list);
                System.out.println("---->"+list.size());
    
    
        }
    }
    
    
  2. set不安全

    通过以下两种方式解决

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

    HashSet底层是HashMap可以查看hashset的构造方法,里面创建了一个HashMap

  3. HashMap不安全

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

9.Callable创建线程

  1. call()方法有返回值,可以抛出异常

    public class TestCallable {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // new Thread(new FutureTask<>(callable)).start
            //  FutureTask为Runable实现类,构造方法的参数为Callable对象
            Data data=new Data();
            FutureTask futureTask=new FutureTask(data);
            new Thread(futureTask,"A").start();
            new Thread(futureTask,"B").start();
    
            Integer o=(Integer) futureTask.get();
            System.out.println(o);
    
        }
        
    }
    class  Data implements Callable<Integer> {//泛型的参数为call方法的返回值类型。
    
        @Override
        public Integer call() throws Exception {
            System.out.println("call方法");
            return 1024;
        }
    }
    

10.CountDownLatch

**简介:**可理解为减法计数器,当CountDown()方法被调用而导致当前计数值为零时,await()方法之后的预计才执行

public class TestCountDown {
    public static void main(String[] args) throws InterruptedException {
        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.countDown(); 此语句不能写在线程run方法之外
        }
        countDownLatch.await();//当计数器为零时向下执行
        System.out.println("执行结束");
    }
}

11.CyclicBarrier

简介:当指定个数的线程达到共同屏障点时,才向下执行。即可以形象的理解为人都到齐了,再行动

//一个线程实现从1到100的相加,另一个线程实现101到200的相加
//最后将两个结果相加
//共同的屏障点:await
public class CyclicBarrierTest {
 static CyclicBarrier barrier = new CyclicBarrier(3);
 static int sum1 = 0; // 1 到 100 相加
 static int sum2 = 0; // 101 到 200 相加
 public static void main (String[] args) {
 // 线程 1
 new Thread(new Runnable() {
 @Override
 public void run() {
 // 执⾏ 1 到 100相加
 for (int i = 1; i <= 100; i++) {
 sum1 += i;
 }
 // 进⼊等待
 try {
 barrier.await();			// 1
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }).start();
 // 线程2
 new Thread(new Runnable() {
 @Override
 public void run() {
 // 执⾏ 101 到 200 相加
 for (int i = 101; i <= 200; i++) {
 // 假设每次执⾏相加都会花⼀些时间
 sum1 += i;
 }
 // 进⼊等待
 try {
 barrier.await();		//2
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }).start();
 // 等到两个线程都执⾏完,在把他们相加
 try {
 barrier.await();		//3
 System.out.println("最终的结果:" + (sum1 + sum2));
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
}
public class TestCB {
    public static void main(String[] args) {
        //集齐七颗龙珠召唤神龙
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("龙珠集齐,召唤神龙!!!!");
        });//当达到共同屏障点之后,执行lambda表达式书写的线程
        for (int i = 0; i < 7; i++) {
            final  int m=i+1;
            new Thread(()->{

                System.out.println("正在收集第"+m+"龙珠");//lambda表达式中的变量必须是final类型
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
           
        }
    }
}

12.Semaphore

信号量,semaphore.acquire()和semaphore.release()相当于操作系统中的pv操作

package com.javademo.JUC;

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

public class TestSemaphore {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(3);//某个资源最多可以被三个线程使用
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();        //p
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(4);
                    System.out.println(Thread.currentThread().getName()+"退出车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finally {
                    semaphore.release();    //v
                }

            },String.valueOf(i)).start();

        }
    }
}


13.读写锁

ReadWriteLock(接口)

实现类:ReentrantReadWriteLock

读可以多个线程读,写只能有一个线程写

public class TestReadWriteLock {
    public static void main(String[] args) {
        MyCacheLock myCacheLock=new MyCacheLock();
        for (int i = 0; i < 10; i++) {//
            final  int tempt=i;
            new Thread(()->{
                myCacheLock.put(tempt+"",tempt);
            },String.valueOf(i)).start();
        }

        for (int i = 0; i < 10; i++) {
            final int tempt=i;
            new Thread(()->{
                myCacheLock.get(tempt+"");
            },String.valueOf(i)).start();
        }
    }
}

class MyCacheLock{
    private volatile Map<String,Object> map=new HashMap<>();
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //写
    public void put(String key,Object  value){
        try {
            readWriteLock.writeLock().lock();
            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){
        try {
            readWriteLock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"开始读取"+key);
            Object o=map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取成功"+o);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

}

14 阻塞队列

BlockingQueue

主要的实现类:LinkedBlockingQueue,ArrayBlockingQueue,SynchronousQueue

方式 抛出异常的方法 有返回值,不抛出异常 阻塞等待 超时等待
添加 add offer put 队列没有位置,一直阻塞 offer
移除 remove poll take 没有元素可以取出,一直阻塞 poll
检测队首元素 element peek
public class TestBlockingQueue {
    public static void main(String[] args) {
        ArrayBlockingQueue arrayBlockingQueue=new ArrayBlockingQueue(3);//队列大小为3
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));
        //System.out.println(arrayBlockingQueue.add("e"));
        System.out.println("======");

        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
    }
}

15.线程池

**线程池要点:**三大方法,七大参数,四种拒绝策略

程序运行的本质是占用系统资源,为了优化资源的使用出现池化技术。

池化技术:实现准备好一些资源,使用的时候就来,使用完之后归还

1. Executors工具类的三大创建线程池的方法

(不推荐使用,使用原生的创建方式)

public class TestPol {
    public static void main(String[] args) {
        //ExecutorService threadPool= Executors.newSingleThreadExecutor();//拥有单个线程的线程池
        ExecutorService threadPool=Executors.newFixedThreadPool(5);//创建一个拥有固定线程数的线程池
        // ExecutorService threadPool=Executors.newCachedThreadPool();//可伸缩的线程池,遇强则强,遇弱则弱
        try {
            for (int i = 0; i < 100; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"----》oooook");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }

    }
}

2. ThreadPoolExecutor方法创建线程的七个参数

public ThreadPoolExecutor(int corePoolSize,
    //核心线程池大小,类比银行窗口为始终开放的窗口
                           int maximumPoolSize,
   //最大核心线程池大小,银行窗口的总数                       
                            long keepAliveTime,
    //银行新开设的窗口,超时关闭,时间大小                      
                              TimeUnit unit,
    //时间单位  TimeUnit.SECONDS   
                          BlockingQueue<Runnable> workQueue,
    //阻塞队列,银行候客区的大小                      
                              ThreadFactory threadFactory,
   //线程工厂,一般使用默认的,Executors.defaultThreadFactory()                       
                          RejectedExecutionHandler handler
  //拒绝策略,最大承载数为maximumPoolSize+workQueue,以用来对超出最大达承载数的线程采用的拒绝策略                        
                         
                         )

3.四种拒绝策略

  new ThreadPoolExecutor.AbortPolicy()//银行满了,不处理新来的人,抛出异常
        new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪
        new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了尝试去和最早的竞争
        new ThreadPoolExecutor.DiscardPolicy()//队列满了,丢掉任务,不抛出异常

使用

import java.util.concurrent.*;

public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService threadPool=new ThreadPoolExecutor(2,
                Runtime.getRuntime().availableProcessors(),//cpu密集型的任务设置最大核心线程数为计算机的处理机个数
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy() );
        try {
            for (int i = 0; i < 100; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"--->ooook");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

16.四大函数接口

只要是函数式接口就可用lambda表达式简化

1. Function函数式接口

**特点:**有一个输入参数,有一个输出

Function <T ,R>中的方法
   R apply(T t)
package com.javademo.JUC;

import java.util.function.Function;

public class TestFuntion {
    public static void main(String[] args) {
        //第一个泛型参数为apply方法的参数,第二个泛型参数T为方法apply的返回值类型,
        Function<String,String > function=(str)->{ return str;};//lambda表达式重写了Function接口并且创建了对象
        System.out.println(function.apply("chenweibin 你好"));
    }
}

2.Predicate断定型接口

**特点:**有一个输入参数,返回值只能是布尔值

Predicate<T>中的方法
boolean test(T t)
import java.util.function.Predicate;

public class TestFuntion {
    public static void main(String[] args) {
        Predicate<String> predicate=(Str)->{return  Str.isEmpty();};
        System.out.println(predicate.test(""));
    }
}

3.Consumer消费型接口

**特点:**只有输入没有返回值

Consumer<T> 的方法
void accept(T t);
public class TestFuntion {
    public static void main(String[] args) {
        Consumer<String> consumer=(Str)->{
            System.out.println("我是消费性接口,我消耗了====》"+Str);};
        consumer.accept("chen");
    }
}

4.Supplier供给型接口

**特点:**没有参数只有输出

Supplier<T>的方法
T get();
public class TestFuntion {
    public static void main(String[] args) {
        Supplier<String> supplier=()->{
           return "chen";};
        System.out.println(supplier.get());
    }
}

注:也可以用匿名内部类重写接口

public class TestFuntion {
    public static void main(String[] args) {
        Supplier<String> supplier=new Supplier<String>() {
            @Override
            public String get() {
                return "chen";
            }
        };
        System.out.println(supplier.get());
    }
}

17.Stream流式计算(含链式编程)

什么是流式计算

1.大数据就是存储加计算

2.MySQL和集合的本质就是存储数据,而计算则交给流来做

public class TestStreamC {
    public static void main(String[] args) {
        User user1=new User(1,"chen",23);
        User user2=new User(2,"su",21);
        User user3=new User(3,"xin",19);
        User user4=new User(4,"liang",23);
        List<User> list= Arrays.asList(user1,user2,user3,user4);//把数组转化为集合
        list.stream()
                .filter(u->u.age>20)//有提示可知参数为Predicate接口的对象,可用lambda表达式
                .map(u->{ return u.name.toUpperCase();})//map映射,参数为Function接口对象
                .sorted((u1,u2)->{return u1.compareTo(u2);})//@FunctionalInterface  public interface Comparator也是函数接口里面有方法叫compare  u2与u1交换位置排序方式发生变化
                .forEach(System.out::println);//::就是把方法当作参数传到stream内部,是stream的每个元素都传入到该方法里面执行一下 ::就是调用静态方法
    }
}

18.ForkJoin

ForkJoinTask<V>的子类有:
    RecursiveAction//递归事件,没有返回值
    RecursiveTask//递归任务,有返回值

如何使用ForkJoin

1.通过ForkJoinPool执行

2.提交计算任务forkjoinpool.submit(ForkJoinTask task)

3.计算类要继承ForkJoinTask

package com.javademo.JUC;

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

public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    public ForkJoinDemo(Long start,Long end){
        this.end=end;
        this.start=start;
    }
    private Long temp=10_000l;//临界值,超过这个值就分割
    @Override
    protected Long compute() {
        if((end-start)<temp){
            Long sum=0l;
            for(Long i=start;i<=end;i++){
                sum+=i;
            }
            return sum;
        }
        else{
            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();
        }
    }
    public static  void Test1(){//普通计算
        Long sum=0l;
        Long start=System.currentTimeMillis();

        for(Long i=1l;i<=100_000_000l;i++){
            sum+=i;
        }
        Long end=System.currentTimeMillis();

        System.out.println("结果为:"+sum+"用时:"+(end-start));
    }
    public static void Test2() throws ExecutionException, InterruptedException {//ForkJoin
        Long start=System.currentTimeMillis();

        ForkJoinDemo task=new ForkJoinDemo(1l,100_000_000l);
        ForkJoinPool forkJoinPool=new ForkJoinPool();
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum=submit.get();

        Long end=System.currentTimeMillis();

        System.out.println("结果为:"+sum+"用时:"+(end-start));

    }

    public static void Test3(){//Stream并行流
        Long start=System.currentTimeMillis();

        long sum= LongStream.rangeClosed(0l,100_000_000l).parallel().reduce(0,Long::sum);//注意范围

        Long end=System.currentTimeMillis();

        System.out.println("结果为:"+sum+"用时:"+(end-start));
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test1();
        Test2();
        Test3();
    }
}

19.异步回调

public class TestAsyn {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // public static CompletableFuture runAsync(Runnable runnable) 方法参数传入一个线程
        //没有返回值的异步回调用runAsync   泛型参数为Void
        CompletableFuture completableFuture=CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName()+"runAsync====>void");
        });

        System.out.println("main Thread execute");
        //如果不显示的调用get方法,则无法输出异步回调的结果
        completableFuture.get();
    }
}

20.JMM

java memory model java内存模型

每个线程都有自己的工作内存,同时计算机都有主内存。线程操作一个数据的时候,都需要read 然后load到自己的工作内存。同时修改之后进行store 且write回内存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rd55007i-1607248214303)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200824115657232.png)]

以上的内存模型会存在一个问题,此问题具体可见以下代码,初始num等于零,当num等于零时子线程一直循环,main线程,会将num值赋为1,按照普通想法我们会理所当然的认为,子线程会停止执行。然而各自线程都有自己的工作内存,子线程对内存变化不可知。Volatile可以保证可见性,通过对变量num添加volatile,可以使程序正常,是符合我们的普通想法。

public class TestVolatile {
    private  static int num=0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0);
        }).start();
        TimeUnit.SECONDS.sleep(2);//保证子线程先执行
        num=1;
        System.out.println(num);//输出1 程序还未停止
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDzJGnNh-1607248214308)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200824191145148.png)]

21.Volatile

  1. 保持可见性 (解释见上)
  2. 不保证原子性
  3. 禁止指令重排

1.保持可见性

public class TestVolatile {
    private  static volatile int num=0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0);
        }).start();
        TimeUnit.SECONDS.sleep(2);//保证子线程先执行
        num=1;
        System.out.println(num);//输出1 程序停止
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gI8ZreI8-1607248214310)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200824191026138.png)]

2.不保证原子性

例如:设置一个num,创建20个线程各自对其进行一千次的自加,添加了Volatile之后,内存中的num对每一个线程都是可见的,然而最后的num并不是理论上的两万,因为自加操作不是原子操作,且Volatile不保证原子性,通过原子类来解决原子性问题

public class TestAtomic {
    private  static  volatile  int num=0;
    private  static void  add(){
        num++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2)//保证所有的子线程执行完 不然会在某些线程之间输出num的值    程序始终存在mian线程和gc线程
            Thread.yield(); //线程让步,将cpu的执行权让给其他线程或者继续让给自己
        System.out.println(num);
    }
}
 

执行结果:
    19968
    不是两万

注:反编译 通过javap -c Demo.class

解决方法使用原子类

将变量 用原子类进行声明和实例化,例如 int 对应AtomicInteger; boolean 对应AtomicBoolean;Long 对应AtomicLong

private volatile static AtomicInteger num  =new AtomicInteger();
public class TestAtomic {
    private  static  volatile AtomicInteger num=new AtomicInteger();
    private  static void  add(){
        num.getAndIncrement();//num加一
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount()>2)//保证所有的子线程执行完 不然会在某些线程之间输出num的值    程序始终存在mian线程和gc线程
            Thread.yield(); //线程让步,将cpu的执行权让给其他线程或者继续让给自己
        System.out.println(num);
    }
}

3.禁止指令重排

程序在执行的时候可能要进行指令重排

22.单例模式

总结:

  • 恶汉式 (线程安全,因为多个线程访问方法的时候对象已经创建好了)
  • 线程不安全的懒汉式
  • 通过synchronized修饰方法的懒汉式 (线程安全 开销大)
  • DCL懒汉式(双重锁检验,不要一上来就锁方法,而是当创建的对象为空时锁对象,因为锁的是对象所以设置volatile防止创建对象时的指令重排)
  • 静态内部类实现单例 (线程安全 有jvm机制保证线程安全)
  • 可以用反射来破坏单例
  • 枚举实现单例,同时防止反射破坏单例

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

介绍

**意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。

**主要解决:**一个全局使用的类频繁地创建与销毁。

**何时使用:**当您想控制实例数目,节省系统资源的时候。

**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

**关键代码:**构造函数是私有的(只暴露接口来创建对象);静态方法;静态变量

final 和static关键字

final:

 /*
 final关键字可以用来修饰引用、方法和类。

  1、用来修饰一个引用
 如果引用为基本数据类型,则该引用为常量,该值无法修改;
 如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。
 如果引用为类的成员变量,则必须当场赋值,否则编译会报错(在方法内可以不用当场赋值)
 
 2.用来修饰一个方法
    当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。
    
 3.用来修饰类
 当用final修改类时,该类成为最终类,无法被继承。简称为“断子绝孙类”。
 */
final class Person {
    String name ="zs";    //3. 此处不赋值会报错
    //final int age;
    final int age = 10;  
}
public class Demo01 {
    public static void main(String[] args) {        //1. 基本数组类型为常量,无法修改
        final int i = 9;
        //i = 10;               //2. 地址不能修改,但是对象本身的属性可以修改
        Person p = new Person();
        p.name = "lisi";
        final int[] arr = {1,2,3,45};
        arr[3] = 999;
        //arr = new int[]{1,4,56,78};
    }
}

static:方便在没有创建对象的情况下来进行调用(方法/变量)

static不能修饰外部类只能修饰内部类。

1.static修饰方法

​ static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

2.static 修饰变量(只能修饰成员变量,不能修饰方法内的属性,即局部变量,语法错误,再者这样各对象无法共享此变量啊)

3.static修饰代码块

4.案例理解

public class Main {  
    static int value = 33;
 
    public static void main(String[] args) throws Exception{
        new Main().printValue();
    }
 
    private void printValue(){
        int value = 3;
        System.out.println(this.value);
    }
}

/*
这里面主要考察队this和static的理解。this代表什么?this代表当前对象,那么通过new Main()来调用printValue的话,当前对象就是通过new Main()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。
*/

1.饿汉式(先直接创建)

package com.javademo.JUC;

public class Hungry {
    private Hungry(){//构造私有

    }
    private final static Hungry hungry =new Hungry();//static修饰变量:它当且仅当在类初次加载时会被初始化
    public static Hungry getInstance(){//通过静态方法
        return  hungry;
    }
}


public class MianApplication {
    public static void main(String[] args) {
        Hungry instance1 = Hungry.getInstance();
        Hungry instance2 = Hungry.getInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance1.equals(instance2));
        System.out.println(instance1==instance2);
    }
}
com.javademo.JUC.Hungry@14ae5a5
com.javademo.JUC.Hungry@14ae5a5
true
true

饿汉式存在的问题

如果在Hungry类中创建多个开销巨大的数组,在对象一上来就会加载这些数组,会耗费巨大的内存

2.懒汉式

青铜级懒汉式(手动滑稽)

package com.javademo.JUC;

public class LazyMan {
    private  LazyMan(){
        
    }
    private static  LazyMan lazyMan;
    public  static  LazyMan getInstance(){
        if(lazyMan==null){//静态方法中只能引用静态成员变量
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
}

问题:

单线程下此懒汉式单例没有问题,但是并发多线程之下,是否能继续保证单例呢?进行验证

package com.javademo.JUC;

public class LazyMan {
    private  LazyMan(){
        System.out.println(Thread.currentThread().getName()+"--->ooook");//验证对象是否只创建了一次
    }
    private static  LazyMan lazyMan;
    public  static  LazyMan getInstance(){
        if(lazyMan==null){//静态方法中只能引用静态成员变量
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }

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

此懒汉式并不是线程安全的,多线程下无法保证单例

解决方法:双重检测锁模式

DCL懒汉式(双重检测锁模式)(线程安全)

public class LazyMan {
    private  LazyMan(){
        System.out.println(Thread.currentThread().getName()+"--->ooook");//验证对象是否只创建了一次
    }
    private static volatile LazyMan lazyMan;
    public  static    LazyMan getInstance(){

        if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
            synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
                if (lazyMan==null)
                    //new LayMan() 并不是一个原子性操作,其经过了三个步骤
                    //1.分配内存空间
                    //2.执行构造方法,初始化对象
                    //3.把这个对象指向这个空间
                    //当存在指令重排时 ,可能执行132,
                    //若A线程执行13
                    //B线程进入,此时Layman!=null但是并没有完成构造,而B线程直接执行了return lazyman
                    // 这样的话  所以应该用volatile来禁止指令重排
                    lazyMan=new LazyMan();
            }

        }
        return lazyMan;
    }

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

3.静态内部类实现单例(线程安全)

public class Holder{
    //构造器私有
    private Holder(){
        
    }
    public static Holder getInstance(){
        return InnnerClass.holder;
    }
    public static class InnerClass{//用静态内部类来创建外部类的对象
        private static final Holder holder=new Holder();
    }
}

package com.javademo.JUC;

public class Holder{
    //构造器私有
    private Holder(){
        System.out.println(Thread.currentThread().getName()+"-->ook");
    }
    public static Holder getInstance(){
        return InnerClass.holder;
    }
    public static class InnerClass{//用静态内部类来创建外部类的对象
        private static final Holder holder=new Holder();
    }

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

有以上代码可测试静态内部类实现的单例是线程安全的

静态内部类的一些特点

​ 静态内部类和非静态内部类一样,都不会在外部类加载的时候而加载,而静态内部类加载的时候,会加载外部类的静态代码块,非静态内部类加载的时候,除了加载外部类的静态代码块之外还要加载外部类的构造方法,因为创建非静态内部类对象时先要创建外部类的对象,即创建的形式是 外部类对象.new 非静态内部类()

但是以上代码都是不安全的,因为存在反射技术

用反射破坏单例

package com.javademo.JUC;

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

public class LazyMan {
    private  LazyMan(){
        System.out.println(Thread.currentThread().getName()+"--->ooook");//验证对象是否只创建了一次
    }
    private static volatile LazyMan lazyMan;
    public  static    LazyMan getInstance(){

        if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
            synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
                if (lazyMan==null)
                    lazyMan=new LazyMan();
            }
        }
        return lazyMan;
    }
//反射破坏单例
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//存在的是空参构造器,通过getDeclaredConstructor获得空参构造器
        declaredConstructor.setAccessible(true);//无视私有构造器,从而通过反射来创建对象
        LazyMan lazyMan = declaredConstructor.newInstance();
        LazyMan lazyMan1=LazyMan.getInstance();
        System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan==lazyMan1);
    }
main--->ooook
main--->ooook
com.javademo.JUC.LazyMan@14ae5a5
com.javademo.JUC.LazyMan@7f31245a
false

如何防止以上类型的破坏呢?因为是通过空参构造方法来创建对象,因此在空参构造方法中进行一次判断

public class LazyMan {
    private  LazyMan(){
       synchronized (LazyMan.class){
           if(lazyMan!=null){
               throw new RuntimeException("不用试图用反射来破坏单例");
           }
       }
    }
    private static volatile LazyMan lazyMan;
    public  static    LazyMan getInstance(){

        if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
            synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
                if (lazyMan==null)
                    lazyMan=new LazyMan();
            }
        }
        return lazyMan;
    }
//反射破坏单例
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazyMan lazyMan1=LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//存在的是空参构造器,通过getDeclaredConstructor获得空参构造器
        declaredConstructor.setAccessible(true);//无视私有构造器,从而通过反射来创建对象
        LazyMan lazyMan = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan==lazyMan1);
    }
}

**问题:**但是如果不首先用LazyMan lazyMan1=LazyMan.getInstance();来创建对象,而是一开始就用反射创建对象,也会破坏单例

**解决方法:**红绿灯,设标志位

防止反射破坏单例(设标志位)

反射就是获得类对象,而类对象可以操作:

  • 类的成员变量,
  • 成员方法,
  • 构造方法,
  • 获取全类名,
  • 获取加载器,
  • 通过空参构造方法创建对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWyaqUts-1607248214313)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20201117131425116.png)]

public class LazyMan {
    private static Boolean flag=false;
    private  LazyMan(){//只能让对象创建一次,但是不管是谁创建第一次,都可以,如果反射第一次创建的依旧能破坏 因此确保不是反射创建对象
       synchronized (LazyMan.class){
           if(flag==false){
               flag=true;
           }else{
               throw new RuntimeException("不用试图用反射来破坏单例");
           }
       }
    }
    private static volatile LazyMan lazyMan;
    public  static    LazyMan getInstance(){

        if(lazyMan==null){//要事先判断,不然每次都会让线程持有锁,耗费资源,只有当对象为空的时候,才让线程持有锁
            synchronized (LazyMan.class){//锁LayMan,保证这个类只有一个
                if (lazyMan==null)
                    lazyMan=new LazyMan();
            }
        }
        return lazyMan;
    }
//反射破坏单例
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//存在的是空参构造器,通过getDeclaredConstructor获得空参构造器
        declaredConstructor.setAccessible(true);//无视私有构造器,从而通过反射来创建对象
        LazyMan lazyMan = declaredConstructor.newInstance();
      LazyMan lazyMan1=LazyMan.getInstance();//也会使用私有构造方法 是此处抛出的异常
        System.out.println(lazyMan);
        System.out.println(lazyMan1);
        System.out.println(lazyMan==lazyMan1);
    }
}

Exception in thread "main" java.lang.RuntimeException: 不要试图用反射来破坏单例
	at com.javademo.JUC.LazyMan.<init>(LazyMan.java:13)
	at com.javademo.JUC.LazyMan.getInstance(LazyMan.java:23)
	at com.javademo.JUC.LazyMan.main(LazyMan.java:33)

道高一尺魔高一丈

同样的你也可以通过反射来获取flag字段,来修改其值,从而破坏单例

Field flag=LazyMan.class.getDeclaredField("flag");
//改变私有权限
flag.setAccessible(true);
flag.set(lazyMan,false);//lazyman是创建的第一个对象

防止破坏单例的终极方法:枚举

用枚举来实现单例

枚举中的实例(枚举常量)默认为单例,

public enum EnumSingle {
    singleTon;
    public EnumSingle getSingleTon(){
        return  singleTon;
    }
}

尝试使用反射来破坏枚举,其中获取的是空参构造器,出现NoSuchMethodException,而不是通过反射创建对象时抛出的Cannot reflectively create enum objects,说明要通过有参构造来尝试破坏。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xN3y6xt1-1607248214316)(C:\Users\hello\AppData\Roaming\Typora\typora-user-images\image-20200826141322519.png)]

使用jad来进行反编译,查看对应的有参构造方法,将jad放入对应的字节码文件夹下,然后执行一下代码

   jad -sjava EnumSingle.class

文件下会产生一个Java文件,可以看到一下内容

private EnumSingle(String s, int i)
    {
        super(s, i);
    }
package com.javademo.JUC;

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

public enum EnumSingle {
    singleTon;
    public EnumSingle getSingleTon(){
        return  singleTon;
    }
}
//尝试用反射破坏单例
class TestSingle{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        System.out.println(declaredConstructor.newInstance());
        System.out.println(declaredConstructor.newInstance());
    }
}

结果:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.javademo.JUC.TestSingle.main(EnumSingle.java:17)

23.各种锁

分类:

按照是否加锁分为:

1.悲观锁:重量级锁,自旋锁,自适应自旋锁

2.乐观锁(采用CAS机制,compareAndSet方法):轻量级锁(每次进入和出去都需要用CAS改变标志状态),偏向锁(实现假设只会有他给一个线程执行方法,进入的时候和轻量级锁一样改变标志状态,同时记录线程的id。但是离开的时候不改变标志状态,再次进入只需比对id值如果id值相同,直接进入,不同说明已经有两个线程要使用此方法呢,偏向锁不在适用,应该升级为轻量级锁)

CAS机制:
    例如i++,不是原子性操作,多线程下容易出错,以往的方法是给方法添加synchronized,进入时就加锁,可是这样开销太大,因为这其中会有大量的线程进行阻塞,唤醒,就绪等操作开销太大。于是就有了,CompareAndSet方法,例如CompareAndSet(i,k,j):k先记录i的初始值,然后j进行一些运算,最后比较i于k的值,如果相等,说明i的值没有线程对其修改,则将j值写回内存,如果发生了改变,则重新跳回,继续进行这样的工作。
    虽然CompareAndSet方法,有三个步骤,但是他是一条硬件指令,故不存在原子性问题

按照是否可以插队分为:

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

2.非公平锁:可以插队(默认为非公平锁)

**另外:**可重入锁:如果一个带锁的方法里面调用另一个方法,而这个方法也带有锁,那么拿到外面的锁之后,就可以拿到里面的锁,自动获取锁。

24.排除死锁

在IDEA的terminate中输入以下命令

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

2.使用jstack 进程号找到死锁问题

你可能感兴趣的:(java,java,多线程)