Welcome to 发呆鱼.|

发呆鱼

园龄:3年4个月粉丝:1关注:0

# JUC 学习笔记

学习自 遇见狂神说 JUC并发编程最新版通俗易懂

wait 与 sleep

  • 来自不同的类

    wait——Object

    sleep——Thread

  • wait 会释放锁

    sleep不会释放锁

  • 使用范围

    wait wait必须在同步代码快中

    sleep 可以在任何地方使用

  • 异常捕获

    wait 不需要捕获异常

    sleep 需要捕获异常

1 lock 锁

Synchronized

/**
 *线程就是一个单独的资源类,没有任何附属的操作
 * 属性和方法
 */
public class SaleTick {

    public static void main(String[] args) {
        //并发,操作同一个资源类,把资源丢入线程
        Tick ticket = new Tick();

        //@FunctionalInterface 函数式接口 lamba表达式 (参数)->{}
        new Thread(()->{
            for(int i = 0; i < 50; i++){
                ticket.saleTick();
            }
        },"a").start();

        new Thread(()->{
            for(int i = 0; i < 50; i++){
                ticket.saleTick();
            }
        },"b").start();

        new Thread(()->{
            for(int i = 0; i < 50; i++){
                ticket.saleTick();
            }
        },"c").start();
        
    }
}


//资源类 OOP
class Tick{

    private int tickNum = 50;


    //synchronized 本质就是排队
    public synchronized void saleTick(){

        if(tickNum > 0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickNum--)+"剩余"+tickNum);
        }
    }
    
}

lock

image-20220310215550233

image-20220310215608530

image-20220310215852710

共平锁: 非常公平,先来后到

非公平锁:不公平,可以插队(默认)

public class SaleTick2 {

    public static void main(String[] args) {
        //并发,操作同一个资源类,把资源丢入线程
        Tick2 ticket = new Tick2();

        new Thread(()->{for(int i = 0; i < 50; i++) ticket.saleTick();},"a").start();
        new Thread(()->{for(int i = 0; i < 50; i++) ticket.saleTick();},"b").start();
        new Thread(()->{for(int i = 0; i < 50; i++) ticket.saleTick();},"c").start();
        
    }
}


//资源类 OOP
class Tick2{

    private int tickNum = 50;

    Lock lock = new ReentrantLock();




    //synchronized 本质就是排队
    public synchronized void saleTick(){
        lock.lock();
        try {
            if(tickNum > 0){
                System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickNum--)+"剩余"+tickNum);
            }
        }finally {
            lock.unlock();
        }

    }

}

Synchronized 和Lock 的区别

  • Synchronized 是java 的一个关键字,Lock 是一个类
  • Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取了锁
  • Synchronized 会自动释放锁,Lock 必须手动释放
  • Synchronized 线程1(获得锁)线程2(等待)。Lock 不一定会等待下去。
  • Synchronized 是可重入锁,不可中断的,非公平的。Lock 可重入锁,可以判断锁,可以设置是否公平锁
  • Synchronized 适合少量代码。Lock 适合大量同步代码。

l老版: 生产者 消费者问题

package demo1;

public class PC {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Thread(()->{
            try {
                buffer.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{
            try {
                buffer.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                buffer.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
        new Thread(()->{
            try {
                buffer.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();
    }
}


//OOP 资源类 缓冲区
class Buffer{
    
    private int num = 0;
    
    public synchronized void increment() throws InterruptedException {
        
        if(num != 0){
            //等待
            this.wait();
        }
        num++;
        //通知
        this.notifyAll();
    }
    
    
    public synchronized void decrement() throws InterruptedException {
        
        if(num == 0){
            //等待
            this.wait();
        }
        num--;
        this.notifyAll();
        
        
        
    }
    
    
}

出现问题,两个同时去加

image-20220310232112168

把上面if 改成while

JUC版:生产者消费者

package demo1;

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

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

        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++)
                    buffer.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "B").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++)
                    buffer.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "C").start();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++)
                    buffer.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "D").start();
    }
}



class Bufer2 {
    private int num = 0;
    Lock lock = new ReentrantLock();
    Condition conditionP = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void increment() throws InterruptedException {

        lock.lock();
        try {
            //业务代码
            while (num != 0) {
                //等待
                conditionP.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + num);
            //通知全部线程
            conditionC.signal();

        } finally {
            lock.unlock();
        }

    }


    public void decrement() throws InterruptedException {

        lock.lock();

        try {
            while (num == 0) {

                conditionC.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + num);
            //通知全部线程
            conditionP.signal();

        } finally {
            lock.unlock();
        }


    }
}

condition 可以精准唤醒

2 关于锁的8个问题

package demo2;

import java.util.concurrent.TimeUnit;


/**
 * 1. 为什么先打印call 再打印 send
 *
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(phone::call).start();
        new Thread(phone::sendMes).start();
    }

}


class Phone{

    //1. synchronized 锁的对象是方法的调用者,两个方法用的是同一把锁phone ,谁先拿到谁执行
    public synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send");
    }

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

}
package demo2;

import java.util.concurrent.TimeUnit;

public class Test2 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();

        new Thread(()->{phone.sendMes();},"a").start();

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


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

class Phone2{

    //1. synchronized 锁的对象是方法的调用者,两个方法用的是同一把锁phone ,谁先拿到谁执行
    public synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send");
    }

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

    //普通方法,没有锁,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}
package demo2;

import java.util.concurrent.TimeUnit;
public class Test3 {

    public static void main(String[] args) {
        Phone3  phone = new Phone3();
        Phone3 phone2 = new Phone3();

        new Thread(Phone3::sendMes,"a").start();

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


        new Thread(Phone3::call,"b").start();
    }
}


class Phone3{

    //static 静态方法,类一加载就有了,锁的是Class
    public static synchronized void sendMes(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("send");
    }

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

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

3 集合线程安全

3.1 list

public class UnsafeList {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

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

会出现ava.util.ConcurrentModificationException 并发修改异常

public class UnsafeList {

    public static void main(String[] args) {

       //List<String> list = new ArrayList<>();
        //解决办法
        //List<String> list = new Vector<>();
        // List<String> list = Collections.synchronizedList(new ArrayList<>());
        
        //CopyOnWrite  写入时复制  计算机程序设计领域的一种优化策略
        List<String> list = new  CopyOnWriteArrayList();
        for (int i = 0; i < 10; i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(1,10));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

    }
}

CopyOnWriteArrayList 比 Vector的效率要高一些,因为CopyOnWriteArrayList 使用的是lock,而Vector使用的是synchronized

3.2 set

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

        //Set<String> a = new HashSet<>();
        //解决方案
       // Set<String> a = Collections.synchronizedSet(new HashSet<>())
        
        Set<String> a = new CopyOnWriteArraySet<>();

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


    }
}

3.3 hashmap

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

        //Map<String,String> map = new HashMap<>();
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++){
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(1,3));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

4 Callable

image-20220311201422785

public class CallableTest {

    public static void main(String[] args) {

        MyCallable callable = new MyCallable();

        Callable callable1;
        FutureTask futureTask = new FutureTask<>(callable);//适配器
        
        new Thread(futureTask,"a").start();

        try {
            Integer o = (Integer) futureTask.get();  //get 方法可能会被阻塞,他会等到结果返回
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}




class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return 123;
    }
}

5 常用辅助类

5.1 CountDownLatch

image-20220311233159801

public class CountDownLatchTets {

    public static void main(String[] args) {

        CountDownLatch count = new CountDownLatch(6);

        count.countDown();  //执行减一

        for (int i = 0; i < 6; i++){
            new Thread(()->{
                count.countDown();
                System.out.println(count.getCount());
            },String.valueOf(i)).start();
        }

        try {
            count.await(); //等待计数器归零,然后再向下执行
            System.out.println(count.getCount());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

5.2 CyclicBarrier

image-20220311234232492

package demo5;

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

public class CyclicBarrierTest {


    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(10,()->{
            System.out.println("ok");
        });


        for(int i = 0; i < 10; i++){

            int finalI = i;
            new Thread(()->{
                System.out.println(finalI);
                try {
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }

            }).start();
        }


    }
}

5.3 Semaphore

image-20220311235037392

在限流的时候可以使用。

semaphore.acquire(); //获得许可证,如果没有了,则等待

public class SemaphoreTest {

    public static void main(String[] args) {
        
        Semaphore semaphore = new Semaphore(5);

        for (int i = 0; i < 10; i++){

            new Thread(()->{
                try {
                    semaphore.acquire(); //获得许可证
                    System.out.println(Thread.currentThread().getName()+"----"+semaphore.availablePermits());
                    TimeUnit.SECONDS.sleep(2);
                    semaphore.release(); //释放许可证
                    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

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

    }
}

6 读写锁 ReadWriteLock

image-20220311235834775

image-20220312000025552

/**
 * 独占锁(写锁) 独自占有
 * 共享锁(读锁) 多个线程同时进行
 */
public class ReadWriteLockTest {

    public static void main(String[] args) {
        Buffer buffer = new Buffer();

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


}


class Buffer{

    private  Map<String,String> map = new HashMap<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public void put(String str,String value){
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName()+"写入");
        map.put(str,value);
        System.out.println(Thread.currentThread().getName()+"写入OK");
        lock.writeLock().unlock();
    }

    public void get(String key){
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName()+"读取");
        String s = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取OK"+s);
        lock.readLock().unlock();
    }



}

7 阻塞队列

7.1 ArrayBlockingQueue

image-20220312105449014

image-20220312105509217

image-20220312112007537

image-20220312111403769

image-20220312111657904

BlockingQueue 和 List Set 是同级的

使用情况:多线程并发处理,线程池。

抛出异常 有返回值 阻塞等待 超时等待
添加 add offer put offer
移除 remove poll take poll
判断队首 element peek
//抛出异常
public static void main(String[] args) {

    Queue<Integer> queue = new ArrayBlockingQueue(2);

    queue.add(1);
    queue.add(2);
    System.out.println(queue.remove());
    System.out.println(queue.remove());
    System.out.println(queue.remove());

    queue.add(3);

}


如果是抛出异常,走的是AbstractQueue中的方法
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}


 public E remove() {
     E x = poll();
     if (x != null)
         return x;
     else
         throw new NoSuchElementException();
}
 /**
     * 不抛出异常,返回值
     */
public static void test2(){

    Queue<Integer> queue = new ArrayBlockingQueue(2);
    System.out.println(queue.offer(1));
    System.out.println(queue.offer(2));
    System.out.println(queue.offer(3));

    System.out.println(queue.poll());
    System.out.println(queue.poll());
    System.out.println(queue.poll());

}




// 走的是ArrayBlockingQueue 中的方法
public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }
 public static void test3() throws InterruptedException {
        ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(2);

        queue.put(1);
        queue.put(2);
        //queue.put(3);  //会一直等在这里


        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take()); // 会一直等在这里

    }
}



public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();  //------------
        enqueue(e);
    } finally {
        lock.unlock();
    }
}


public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();//--------------
            return dequeue();
        } finally {
            lock.unlock();
        }
}
public static void test4() throws InterruptedException {

    ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(2);

    System.out.println(queue.offer(1));
    System.out.println(queue.offer(2));
    System.out.println(queue.offer(3, 2, TimeUnit.SECONDS));

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


}




 public boolean offer(E e, long timeout, TimeUnit unit)
     throws InterruptedException {

     checkNotNull(e);
     long nanos = unit.toNanos(timeout);
     final ReentrantLock lock = this.lock;
     lock.lockInterruptibly();
     try {
         while (count == items.length) {
             if (nanos <= 0)
                 return false;
               // 调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。
    //nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;
    //若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的打断状态;若指定时间内未收到通知,则返回0或负数。 
             nanos = notFull.awaitNanos(nanos);
         }
         enqueue(e);
         return true;
     } finally {
         lock.unlock();
     }
 }


7.2 SynchronousQueue

image-20220312123906738

public static void main(String[] args) {

    BlockingQueue<String> queue = new  SynchronousQueue();


    new Thread(()->{

        try {
            System.out.println("put 1");
            queue.put("1");
            System.out.println("put 2");
            queue.put("2");
            System.out.println("put 3");
            queue.put("3");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    },"t1").start();

    new Thread(()->{

        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println(queue.take());
            TimeUnit.SECONDS.sleep(1);
            System.out.println(queue.take());
            TimeUnit.SECONDS.sleep(1);
            System.out.println(queue.take());

        } catch (Exception e) {
            e.printStackTrace();
        }

    },"t1").start();


}

8 线程池

池化技术

程序运行:占用系统的资源--> 池化技术

线程池,连接池,内存池,对象池

好处

  • 降低资源消耗
  • 提高相应的速度
  • 方便管理
  • 线程复用,可以控制最大并发数,管理线程

线程池三大方法

public static void main(String[] args) {

        //单个线程
        //ExecutorService service = Executors.newSingleThreadExecutor();

        //ExecutorService pool = Executors.newFixedThreadPool(5);//一个固定大小的线程池

        ExecutorService pool = Executors.newCachedThreadPool(); //可伸缩的

        try {
            for (int i = 0; i < 10; i++){
                pool.execute(()->{
                    System.out.println(Thread.currentThread().getName());
                });
            }
        } finally {
            pool.shutdown();
        }

    }
public static ExecutorService newSingleThreadExecutor() {
    return new 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>());
    }

可以发现上面创建线程的三个方法调用的都是ThreadPoolExecutor 
public ThreadPoolExecutor(int corePoolSize,   //核心线程池大小
                          int maximumPoolSize, //最大线程池大小
                          long keepAliveTime,  //存活时间
                          TimeUnit unit,   // 时间单位
                          BlockingQueue<Runnable> workQueue,  //阻塞队列
                          ThreadFactory threadFactory,  //线程工程,创建线程
                          RejectedExecutionHandler handler  //拒绝策略
                         ) { 
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

手动创建线程池

public class ThreadPoolExecutorTest {
    /**
         * 最大线程数
         * cpu 密集型: 一般为电脑核数  Runtime.getRuntime().availableProcessors()
         * io 密集型: 一般 > 程序中十分耗 io 的进程
         *
         */

    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,
                6,
                2,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略, 满了后抛出异常
                );


        try {
            for(int i = 0; i < 10; i++){
                final int tmp = i;
                executor.execute(()->{

                    System.out.println(Thread.currentThread().getName()+"---"+String.valueOf(tmp));
                });
            }
        } finally {
                executor.shutdown();
        }

    }
}

拒绝策略

AbortPolicy  //抛出 RejectedExecutionException 的被拒绝任务的处理程序

CallerRunsPolicy  //被拒绝任务的处理程序,它直接在执行方法的调用线程中运行被拒绝的任务,除非执行程序已关闭,在这种情况下,任务将被丢弃。



DiscardOldestPolicy  //拒绝任务的处理程序,丢弃最旧的未处理请求,然后重试执行,除非执行程序关闭,在这种情况下任务被丢弃.  尝试和最早的去竞争,

DiscardPolicy  //被拒绝任务的处理程序,它默默地丢弃被拒绝的任务。

9 四大函数式接口

函数式接口

只有一个方法的接口

@FunctionalInterface  //函数式接口
public interface Runnable {

    public abstract void run();
}
简化编程模式,在新版本的框架底层大量应用
foreach()


 public static void main(String[] args) {
        Function function = (str)->{ return str+"123"; };

        System.out.println(function.apply("aaaa"));
 }
断定型接口
public static void main(String[] args) {
    Predicate<String> predicate = s -> {
        return s.isEmpty();
    };


    System.out.println(predicate.test(""));
    System.out.println(predicate.test("aa"));

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


    Consumer<String> consumer = s -> {
        System.out.println(s);
    };
    consumer.accept("aaa");

}
Supplier<String> supplier = ()->{
  return "1024";
};

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

10 Stream 流式计算

public static void main(String[] args) {
    User u1 = new User("a",1);
    User u2 = new User("b",2);
    User u3 = new User("c",3);
    User u4 = new User("d",4);

    List<User> list = Arrays.asList(u1,u2,u3,u4);

    //链式编程
    list.stream()
            .filter(user -> {return user.getAge()>1;})
            .map(user -> {return user.getName().toUpperCase();})
            .sorted((a,b)->{return b.compareTo(a);})
            .forEach(System.out::println);

}

11 ForkJoin

在大数据量下,并行执行任务,提高效率。

ForkJoin的特点:工作窃取。

大任务拆解成多个子任务,子任务还可以继续拆解成更小的子任务,最后将这些最小的子任务用多个线程并行执行,然后合并执行结果,

ForkJoin模型利用了分治算法的思想,将大任务不断拆解,多线程执行,最后合并结果。它的本质是一个线程池。

工作流程

​ 每一个工作线程维护一个本地的双端队列用来存放任务。线程在运行的过程中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,从队尾取出任务来执行。当某个工作线程的本地队列为空时,它会尝试窃取一个任务,也叫工作窃取(可能是来自于刚刚提交到 pool 的任务,也可能是来自于其他工作线程的工作队列),窃取其他线程的工作队列的任务时,从队首取出,加到自己的队列中,然后执行下面两步:

  • 如果任务足够小就直接执行。
  • 否则将任务拆分成更小的子任务。

从上面的过程可以看出,Fork join并不是预先拆分所有任务,而是在执行时动态的决定拆分。

ForkJoin有三个比较重要的方法(操作)

  1. fork:开启一个新线程(或是重用线程池内的空闲线程),将任务交给该线程处理。
  2. join:等待子任务的处理线程处理完毕,获得返回值。
  3. compute:拆解并执行任务

image-20220312193324699

image-20220312204944830

public class ForkJoinTest extends RecursiveTask<Long> {


    private long start;
    private long end;

    private long tmp = 100_0000L;


    public ForkJoinTest(long start,long end){
        this.start = start;
        this.end = end;
    }



    //计算的方法
    @Override
    protected Long compute() {

        if ((end - start) < tmp){
            long sum = 0L;
            for (long i = start; i <= end; i++){
                sum += i;
            }
            return sum;
        }else {
            long mid = (start + end) / 2;
            ForkJoinTest test1 = new ForkJoinTest(start,mid);
            test1.fork(); //拆分任务,把任务压入线程

            ForkJoinTest test2 = new ForkJoinTest(mid,end);
            test2.fork();

            return test1.join() + test2.join();
        }

    }


}
public class Test {

    public static void main(String[] args) {
//        try {
//            test3(); //80072
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
       // test1(); 2797

        test2(); //919
    }

    public static void test1(){
        long start = System.currentTimeMillis();

        long sum = 0L;
        for (long i = 0L; i < 100_0000_0000L; i++){
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
        System.out.println(sum);

    }

    public static void test2(){
        long start = System.currentTimeMillis();

        long l = LongStream.rangeClosed(0L, 100_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println(end-start);
        System.out.println(l);
    }


    public static void test3() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinTest(0L,100_0000_0000L);

        ForkJoinTask<Long> submit = pool.submit(task);//提交任务
        Long aLong = submit.get();


        long end = System.currentTimeMillis();
        System.out.println(end-start);
        System.out.println(aLong);
    }
}

12 异步回调

​ Future接口在Java5中被引入,设计初衷是对将来某个时刻会产生的结果进行建模。它建模了一种异步运算,返回一个执行结果的引用,当运算结束后,这个引用被返回给调用方。在Future中触发那些潜在耗时的操作完成。

Future接口的局限性

当我们得到包含结果的Future时,我们可以使用get方法等待线程完成并获取返回值,注意我加粗的地方,Future的get() 方法会阻塞主线程。 Future文档原文如下

{@code Future}代表异步*计算的结果。提供了一些方法来检查计算是否完成,等待其完成并检索计算结果。

什么是CompletableFuture

​ 在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,结合了Future的优点,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

​ CompletableFuture被设计在Java中进行异步编程。异步编程意味着在主线程之外创建一个独立的线程,与主线程分隔开,并在上面运行一个非阻塞的任务,然后通知主线程进展,成功或者失败。

13 JMM

volatile

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

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

JMM 是什么

JMM : java 内存模型,是一种概念,约定。

关于JMM的一些同步约定

  • 线程解锁前,必须把共享变量刷回主存。
  • 线程加锁前:必须读取主存中的最新值到工作内存中。
  • 加锁和解锁必须是同一把锁。

preview

一些问题:(多线程环境下尤其)

  • 缓存一致性问题:在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存(MainMemory)。基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是也引入了新的问题:缓存一致性(CacheCoherence)。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致的情况,如果真的发生这种情况,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol,等等:

img

  • 指令重排序问题:为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。因此,如果存在一个计算任务依赖另一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证。与处理器的乱序执行优化类似,Java虚拟机的即时编译器中也有类似的指令重排序(Instruction Reorder)优化

Java内存模型定义了以下八种操作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:

  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

  • 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

14 Volatile

保证可见性

public class JMMTest {

    private static int num = 0;
    public static void main(String[] args) {

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

            }

        }).start();

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

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

    }
}

问题: 子线程会一直卡住,它不知道主内存中的num已经发生了变换。

image-20220312230510883

给num 加上关键词volatile ,保证可见性

private volatile static int num = 0;

不保证原子性

原子性: 不可分割,线程执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败。

public class VolatileTest {


    private volatile static int num = 0;

    public static void add(){
        num++; // num++ 不是原子性操作
    }


    public static void main(String[] args) {

        for (int i = 0; i < 20; i++){

            new Thread(()->{
                for (int j = 0; j < 1000; j++){
                    add();
                }
            }).start();

        }

        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(num);


    }


}

image-20220312233800619

保证原子性

public class VolatileTest {

    private  static AtomicInteger num = new AtomicInteger();

    public static void add(){
        num.getAndIncrement(); //num 加1
    }
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++){

            new Thread(()->{
                for (int j = 0; j < 1000; j++){
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2){
            Thread.yield();
        }
        System.out.println(num);
    }
}

这些类的的底层都在直接和操作系统挂钩,在内存中修改。

禁止指令重排

指令重排:计算机并不是按照自己写的那样取执行

源代码----编译器的优化重排----指令并行重排----内存系统重排----执行。

处理器在进行指令重排时,会考虑到数据的依赖性

内存屏障

为什么会有内存屏障

  • 每个CPU都会有自己的缓存(有的甚至L1,L2,L3),缓存的目的就是为了提高性能,避免每次都要向内存取。但是这样的弊端也很明显:不能实时的和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同。
  • 用volatile关键字修饰变量可以解决上述问题,那么volatile是如何做到这一点的呢?那就是内存屏障,内存屏障是硬件层的概念,不同的硬件平台实现内存屏障的手段并不是一样,java通过屏蔽这些差异,统一由jvm来生成内存屏障的指令。

内存屏障是什么

  • 硬件层的内存屏障分为两种:Load BarrierStore Barrier即读屏障和写屏障。

  • 内存屏障有两个作用:

    • 阻止屏障两侧的指令重排序;

    • 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。

对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;

对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。

volatile语义中的内存屏障

  • volatile的内存屏障策略非常严格保守,非常悲观且毫无安全感的心态:

    • 在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障;
    • 在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障;
  • 由于内存屏障的作用,避免了volatile变量和其它指令重排序、线程之间实现了通信,使得volatile表现出了锁的特性。

15 单列模式

public class LazyTest {

    private static boolean aa = false;

    private LazyTest(){
        synchronized (LazyTest.class){
            if (aa == false){
                aa = true;
            }else {
                    throw new RuntimeException("不要试图破坏单列");
            }

        }
    }


    private volatile static LazyTest lazyTest;


    //双重检测锁 懒汉式单列 DCL
    public static LazyTest GetInstance(){

        if(lazyTest == null){
            synchronized (LazyTest.class){
                if (lazyTest == null){
                    lazyTest = new LazyTest();
                    /**
                     * 1. 分配内存空间
                     * 2. 执行构造方法,初始化对象
                     * 3. 把这个对象指向这个空间
                     *
                     * 极端情况: 此时已经分配内存空间,而 lazyTest 已经指向该空间
                     * 但是此时又出现了一个线程,发现 lazyTest 指向内存,已经不为空了,于是在第一个 if 处就会结束返回
                     */
                }
            }
        }
        return lazyTest;
    }


    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //LazyTest lazyTest = LazyTest.GetInstance(); //拿到这个对象

        Field aa = LazyTest.class.getDeclaredField("aa");
        aa.setAccessible(true);

        Constructor<LazyTest> constructor = LazyTest.class.getDeclaredConstructor(null);

        constructor.setAccessible(true);

        LazyTest test = constructor.newInstance();

        aa.set(test,false);

        LazyTest test2 = constructor.newInstance();


        System.out.println(test2.hashCode());
        System.out.println(test.hashCode());
    }

}

16 深入理解 CAS

CAS

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

缺点:

  • 自旋锁,会耗时
  • 一次只能保证一个共享变量的原子性
  • ABA问题
public class CASTest {

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(10);

        //如果期望的值达到了,就更新,否则就不更新,CAS 是cup 的并发原语
        atomicInteger.compareAndSet(10,100);

        System.out.println(atomicInteger.get());
        atomicInteger.compareAndSet(10,120);
        System.out.println(atomicInteger.get());

        atomicInteger.getAndIncrement();
    }
}

unsafe

image-20220313112758441

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}


public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        //获得内存地址中的值
        var5 = this.getIntVolatile(var1, var2);
        //如果当前地址(var)的偏移值(var),还是期望的值(上步取的var5),那么就更新为var5+var4
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        //自旋锁,直到成功为止
    return var5;
}

CAS 的 ABA 问题

image-20220313114109124

public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(10);

    //如果期望的值达到了,就更新,否则就不更新,CAS 是cup 的并发原语
    atomicInteger.compareAndSet(10,100);

    atomicInteger.compareAndSet(100,10);

    System.out.println(atomicInteger.get());
    atomicInteger.compareAndSet(10,120);
    System.out.println(atomicInteger.get());

    atomicInteger.getAndIncrement();
}

17 原子引用

Integer 使用了对象缓存机制,默认范围是(-128-127),推荐使用静态工厂方法 valueOf() 获取对象实例,而不是直接 new 因为 valueOf() 使用缓存,而 new 一定会创建新的对象并分配内存空间

所有相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。对于 Integer i = ? 在 -128-127 之间的赋值,Integer 对象是在 IntegerCache .cache 中产生,会服用已有的对象,这个区间内的Intefer 值可以直接使用 == 判断,但是这个区间之外的所有数据,都会在堆上产生,并不会服用已有的对象,这就是一个大坑,所有推荐使用 equals 方法进行判断

public class CASTest2 {

    public static void main(String[] args) {

        // AtomicStampedReference 注意: 如果泛型是一个包装类,注意对象应用问题
        AtomicStampedReference<Integer> at = new AtomicStampedReference<>(10,1);

        new Thread(()->{
            //获得版本号
            int st = at.getStamp();
            System.out.println("A=="+st);

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

            System.out.println(at.compareAndSet(10, 12, at.getStamp(), at.getStamp() + 1));

            System.out.println("A=="+at.getStamp());

            System.out.println(at.compareAndSet(12, 10, at.getStamp(), at.getStamp() + 1));


        },"A").start();




        new Thread(()->{

            //获得版本号
            int st = at.getStamp();
            System.out.println("B=="+st);

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

            System.out.println(at.compareAndSet(10, 12, st, st + 1));

            System.out.println("B==="+at.getStamp());

        },"B").start();

    }
}

18 各种锁

公平锁,非公平锁

  • 公平锁: 必须先来后到,不可以插队
  • 非公平锁: 可以插队

可重入锁(递归锁)

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

image-20220313121309006

自旋锁

public class MyLock {

   AtomicReference<Thread> at = new AtomicReference<>();


   public void lock(){

       Thread thread = Thread.currentThread();

       while (!at.compareAndSet(null, thread)){

       }
       System.out.println(thread.getName()+"lock");

   }

   public void unLock(){
       Thread thread = Thread.currentThread();

       at.compareAndSet(thread, null);
       System.out.println(thread.getName()+"unlock");
   }


}
public class Tets {

    public static void main(String[] args) {
        MyLock lock = new MyLock();

        new Thread(()->{
            lock.lock();
            try {
                System.out.println("A====");

                TimeUnit.SECONDS.sleep(1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unLock();
            }

        },"A").start();

        new Thread(()->{
            lock.lock();
            try {
                System.out.println("B====");

                TimeUnit.SECONDS.sleep(1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unLock();
            }

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

死锁

public class DeadLock {
    public static void main(String[] args) {
        String A = "a";
        String B = "b";

        new Thread(new Dead(A,B),"1").start();
        new Thread(new Dead(B,A),"2").start();

    }
}


class Dead implements Runnable{

    private String A;
    private String B;

    public Dead(String A,String B){
        this.A = A;
        this.B = B;
    }


    @Override
    public void run() {

        synchronized (A){

            System.out.println(Thread.currentThread().getName()+"以获得"+A);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B){
                System.out.println(Thread.currentThread().getName()+"以获得"+B);
            }


        }
    }
}

环路等待,不可剥夺,互斥条件,部分分配。

image-20220313123647722

jstack 2900

image-20220313123806248

死锁解决:

  • 死锁预防:
    • 打破互斥和不可剥夺:但是无法解决互斥资源
    • 搭配部分分配: 线程步一定知道自己全部需要的锁,还会降低效率
  • 死锁避免:分配资源的时候,预测出是否可能发生死锁。
  • 死锁检测和恢复。

19 完结

本文作者:发呆鱼

本文链接:https://www.cnblogs.com/dyiblog/articles/16000249.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   发呆鱼  阅读(24)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起