JUC, Java并发编程

JUC概述

线程状态

线程状态枚举类

  1. NEW 新建

  2. RUNNABLE 准备就绪

  3. BLOCK 阻塞

  4. WAITING 不见不散

  5. TIMED_WAITING 过时不候

  6. TERMINATED 中结

并行与并发

wait和sleep的区别

  1. sleep 是Thread的静态方法,wait是Object的方法,任何对象实例都可以调用。

  2. sleep不会释放锁,它也不需要占用锁。若当前线程占有锁,那么wait会释放锁。

  3. 他们都可以被interrupted 方法中断

  4. 两方法在哪睡,在哪醒

串行模式

一次只能取得一个任务,并执行这个任务

并行模式

多项工作一起执行,之后再汇总。例如:泡面,一边烧水一边放调料

并发模式

同一时刻多个线程访问同一个资源,多线程对一个点。例如:春运抢票,电商秒杀

管程

  1. Monitor 监视器,所说的锁

  2. 是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码

  3. jvm同步基于进入和退出,使用管程对象实现的

用户线程

自定义线程:new Thread

守护线程:运行在后台,没有用户线程,都是守护线程,JVM就会结束。比如垃圾回收

public class Main {
    public static void main(String[] args) {
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() +
                    "::" + Thread.currentThread().isDaemon());
            while (true) {
​
            }
        }, "aa");
        // aa.isDaemon
        aa.start();
        System.out.println(Thread.currentThread().getName()+"over");
    }
}

Lock接口

Synchronized 关键字回顾

是一种同步锁,他修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是"{}"括起来的代码

  2. 修饰一个方法,被修饰的方法称为同步方法,起作用范围是整个方法,作用的对象是调用这个方法的对象

  3. 修改一个静态方法,其作用的范围就是整个静态方法,作用的对象是这个类的所有对象

  4. 修改一个类,其作用方位是 synchronized 后面括号括起来的部分,作用的对象是这个类所有对象

package sync;
​
class Ticket{
    private int number = 30;
​
    public synchronized void sale(){
        // 判断是否还有票
        if (number > 0){
            System.out.println(Thread.currentThread().getName() +
                    " : 卖出: " +(number--) + " 剩下: " + number);
        }
    }
}
​
public class saleTicker {
    // 创建资源类
    // 定义属性和操作方法
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        // 创建三个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < 40; i++){
                    ticket.sale();
                }
            }
        }, "AA").start();
        // 创建三个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < 40; i++){
                    ticket.sale();
                }
            }
        }, "BB").start();
        // 创建三个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0; i < 40; i++){
                    ticket.sale();
                }
            }
        }, "CC").start();
    }
}
​

可重入锁Lock

Lock与 Synchronized 区别:

  • Lock 不是java 语言内置的,synchronized 是java语言关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问。

  • Lock需要手动释放锁

  • 在 synchronized发生异常时会在自动释放线程占有的锁,因此不会发生死锁现象;Lock需要主动释放锁才会避免死锁现象,需要在finally块中释放锁。

  • Lock可以让等待锁的线程响应中断,而synchronized不行会一直等待

  • 通过Lock 可以知道有没有成功获取锁

  • Lock可以提高多个线程进行读操作的频率

线程间的通信

package lockSale;
​
class share{
    private int num = 0;
    // +1
    public synchronized void incr() throws InterruptedException {
        if (num == 0){
            num++;
            System.out.println(Thread.currentThread().getName()+"::"+ num);
        }else {
            this.wait();
        }
        this.notify();
    }
​
    public synchronized void del() throws InterruptedException {
        if (num == 1){
            num--;
            System.out.println(Thread.currentThread().getName()+"::"+ num);
        }else {
            this.wait();
        }
        this.notify();
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        share share = new share();
        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();
​
        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                try {
                    share.del();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();
    }
}

虚假唤醒

if判断只判断一次,wait在哪睡在哪醒,唤醒时会+1,然后加的线程抢到锁,再加1就会导致出现2的情况

package lockSale;
​
class share{
    private int num = 0;
    // +1
    public synchronized void incr() throws InterruptedException {
        while (num == 0){
            num++;
            System.out.println(Thread.currentThread().getName()+"::"+ num);
        }else {
            this.wait();
        }
        this.notify();
    }
​
    public synchronized void del() throws InterruptedException {
        while (num == 1){
            num--;
            System.out.println(Thread.currentThread().getName()+"::"+ num);
        }else {
            this.wait();
        }
        this.notify();
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        share share = new share();
        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"AA").start();
​
        new Thread(() ->{
            for (int i = 0; i < 10; i++){
                try {
                    share.del();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"BB").start();
    }
}

条件判断必须加while。

集合线程安全

ArryList()不安全,add方法没有加 synchronized 关键字

解决方案:

  • Vector(古老):add方法 加了synchronized

  • 实现Collection的synchronizedList方法

    Collection.synchronizedList(new ArrayList<>());
  • 使用juc的CopyOnWriteArrayList:

    new CopyOnWriteArrayList<>();

    • 写时复制技术

    • 每次写时复制一个原集合,再写入,合并,最后读取复制集合

HashSet()不安全解决方案:

  • CopyOnWriteArraySet:

    new CopyOnWriteArraySet<>();

HashMap()不安全解决方案:

  • new CopyOnWriteArrayMap<>();

锁分析

注意:如果synchronized加载static上就锁的是字节码 .class 文件。

公平锁和非公平锁

private Lock lock = new ReentrantLock(false);

即在ReentrantLock()内可填入 true(fair) & false(true)

默认采用非公平锁,效率高,非公平锁获取资源会询问,降低了效率。

可重入锁

synchronized 隐式

Lock 显示

可重入锁,可以实现自由进出多层锁的功能。

package sync;
// 可重入锁
public class reAcc {
    public static void main(String[] args) {
        Object o = new Object();
        new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"outer");
                synchronized (o){
                    System.out.println(Thread.currentThread().getName()+"middle");
                    synchronized (o){
                        System.out.println(Thread.currentThread().getName()+"inner");
                    }
                }
            }
        },"reAcc").start();
​
    }
}

可重入锁也叫递归锁

在使用lock方法实现可重入锁时,如果内层锁不释放,也可以正常使用,但是新线程无法获取锁。

死锁

两个或者两个以上进程在执行过程中,因为抢夺资源造成一种互相等待的现象,如果没有外力干涉,无法再执行下去。

产生死锁三种原因:

  1. 系统资源不足

  2. 进程运行推进顺序不当

  3. 资源分配不当

package sync;
​
public class dieLock {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
​
        new Thread(()->{
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+ "持有a获取b");
                synchronized (b){
                    System.out.println(Thread.currentThread().getName()+ "获取b");
                }
            }
        },"A").start();
​
        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+ "持有b获取a");
                synchronized (a){
                    System.out.println(Thread.currentThread().getName()+ "获取a");
                }
            }
        },"B").start();
    }
}

死锁验证

  1. jps:类似linux ps -ef 查看进程

  2. jstack:jvm自带堆栈跟踪工具

创建线程四种方式

  1. 继承Thread类

  2. 实现Runnable接口

  3. Callable接口

  4. 线程池

Runnable和Callable接口比较

  1. Callable有返回值

  2. Callable抛出异常

  3. Callable用call()

Callable不能直接替换Runnable,需要使用FutureTask中间类来创建

FutureTask未来任务,累加线程,最后总和

package sync;
​
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
​
public class Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask<>(() ->{
            System.out.println(Thread.currentThread().getName() + "Callable");
            return 1024;
        });
​
        new Thread(futureTask, "aa").start();
​
        while (!futureTask.isDone()){
            System.out.println("wait...");
        }
​
        System.out.println(futureTask.get());
        System.out.println(Thread.currentThread().getName()+"::isDne");
    }
​
}

JUC辅助类

CountDownLatch

减少计数

package juc;
​
import java.util.concurrent.CountDownLatch;
​
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
​
        //设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);
​
        for (int i = 1; i<= 6; i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
                countDownLatch.countDown();// 计数器减一
            }, String.valueOf(i)).start();
        }
​
        // 阻塞
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"锁门");
    }
}

CyclicBarrier:

循环阻塞

package juc;
​
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
​
public class CyclicBarrierDemo {
    private static final int NUM = 7;
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM, ()->{
            System.out.println("makabak");
        });
        for (int i = 0; i<7;i++){
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"done");
                //
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } catch (BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            },String.valueOf(i)).start();
        }
    }
}

Semaphore

信号灯

package juc;
​
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
​
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建
        Semaphore semaphoreDemo = new Semaphore(3);
​
        // 模拟
        for (int i = 1; i<=6;i++){
            new Thread(() -> {
                try {
                    semaphoreDemo.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到了");
​
                    // 设置随机时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    semaphoreDemo.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

读写锁ReadWriteLock

悲观锁:

只有一个线程可以获取资源,解锁后其他线程可以争抢资源。

乐观锁:

多个线程获取资源,但是会添加版本号,再修改时,比较二者版本号是否一致,一致进行修改。

读锁:共享锁

上锁:

lock.readLock.lock;

写锁:独占锁

上锁:

lock.writLock.lock;

读写锁都存在死锁问题,关键:做好自己的事,不要想着多个功能呢一起完成。

一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享。

锁降级

将写锁降级为读锁

获取写锁 --> 获取读锁 --> 释放写锁 --> 释放读锁

package juc;
​
import java.util.concurrent.locks.ReentrantReadWriteLock;
​
public class Demo1 {
    public static void main(String[] args) {
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
​
        // 锁降级
        writeLock.lock();
        System.out.println("giao~!");
​
        readLock.lock();
        System.out.println("___read");
​
        writeLock.unlock();
​
        readLock.lock();
    }
}
​

读锁无法升级为写锁

阻塞队列BlockingQueue

俩线程,一个拿一个放,在阻塞时挂起线程。

阻塞队列分类

  1. ArrayBlockingQueue: 由数组结构组成的一个有界的阻塞队列。

  2. LinkedBlockingQueue: 由链表结构组成的有界(默认大小为integer.MAX_VALUE)阻塞队列。

  3. DelayQueue:使用有衔接队列实现的延迟无界阻塞队列。

  4. PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

  5. SynchronousQueue:不存储元素阻塞队列,单个元素队列

  6. LinkedTransferQueue:由链表结构组成的无界阻塞队列。

  7. LinkedBlockingDeque:由链表组成的双向阻塞队列

方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e, time,unit)
移除 remove() poll() take() poll(time, unit)
检查 element() peek() 不可用 不可用

线程池

是一种线程使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

线程池优势:

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

主要特点:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁的小号

  • 提高响应速度

  • 提高线程的可管理性,统一分配,调用和监控

  • Java种线程池是通过EXecutor框架实现的,

线程池使用方式:

  1. Executors.newFixedThreadPool(int):一池N线程

  2. Executors.newSingleThreadPool():一个任务一个任务的执行,一池一线程

  3. Executors.newCachedThreadPool():线程池根据需求创建线程,可扩容,遇强则强

package pool;
​
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        // 一池5线程
        ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
​
        // 一池1线程
        ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
​
        // 一池自动扩容线程
        ExecutorService threadPool3 = Executors.newCachedThreadPool();
​
        try {
            for (int i = 0; i< 10;i++){
                threadPool3.execute(() ->{
                    System.out.println(Thread.currentThread().getName() + "办理业务");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            threadPool3.shutdown();
        }
    }
}

ThreadPoolExcutor

上述3种方式都是new 一个ThreadPoolExcutor来实现线程池创建

参数 释义
int corePoolSize 常驻线程数量(核心)
int maximumPoolSize 最大线程数量
long keepAliveTime 线程存活时间
TimeUnit 时间单位
BlockingQueue workQueue 阻塞队列
ThreadFactory threadFactory 线程工厂
RejectedExecutionHandler handler 拒绝策略

JDK内置拒绝策略:

  1. AbortPolicy(默认):直接抛出RejectedExecutionException 异常阻止系统正常运行。

  2. CallerRunsPolicy:不会抛弃任务,也不会抛出异常,而是将某些任务退回到调用者,从而降低新任务的流量。

  3. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前仍无加入队列中尝试再次提交当前业务。

  4. DiscardPolicy:默默丢弃无法处理的任务,不做任何处理,也不抛异常。

实际使用线程池,不适用之前三种方式,因为其最大值为Integer.MAX_VALUE可能会堆积大量请求。导致OOM。

package pool;
​
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
​
public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2
                , 5
                , 2L
                , TimeUnit.SECONDS
                , new ArrayBlockingQueue<>(3)
                , Executors.defaultThreadFactory()
                , new ThreadPoolExecutor.AbortPolicy()
        );
​
        try {
            for (int i = 0; i < 7; i++){
                threadPoolExecutor.execute(() ->{
                    System.out.println(Thread.currentThread().getName() + "办理");
                });
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}

分支合并框架

Fork

Join

异步回调

你可能感兴趣的:(JUC,java,开发语言)