JUC初级(全部)

JUC

Module java.base
代码位置

关键字:volatile

部分汇总

线程创建位置

都是先穿件好?不是有new就直接创建线程,创建不管其中的方法是否阻塞,都先穿件好(一定不是等一个线程完成了再穿件下一个)
基本原则:主线成创建线程,没有阻塞主线程,则主线程不管子线程的执行

创建线程的方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
    • FutrueTask
    • 线程池
  4. 线程池方式
Constructor and Description
Thread()分配一个新的 Thread对象。
Thread(Runnable target)分配一个新的 Thread对象。
Thread(Runnable target, String name)分配一个新的 Thread对象。
Thread(String name)分配一个新的 Thread对象。
Thread(ThreadGroup group, Runnable target)分配一个新的 Thread对象。
Thread(ThreadGroup group, Runnable target, String name)分配一个新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,属于 group引用的线程组。
Thread(ThreadGroup group, Runnable target, String name, long stackSize)分配一个新的 Thread对象,以便它具有 target作为其运行对象,将指定的 name正如其名,以及属于该线程组由称作 group ,并具有指定的 堆栈大小
Thread(ThreadGroup group, String name)分配一个新的 Thread对象。

FutureTask

  1. 调用futureTask.get(),如果线程没有计算完成,get没有值,等待线程执行完成,有值则直接返回
  2. 不调用get,主线程不会被阻塞,主线程直接结束,子线程也结束

FutureTask作用/原理

  • 主线程继续
  • 开启子线程执行
  • 主线程获取子线程结果

[!WARNING]

  • futureTask的get方法是阻塞方法,无论使不使用isDone,主程序都会等线程进行完成并返回结果,通过get获取.

  • 线程的执行和get无关,线程执行完成使得get获取到结果,但是一旦get有值之后,再次调用直接获取值(线程已经执行结束了)

  • 如果不调用get,则主线程不会被阻塞

辅助类

  • 阻塞主线程,等待子线程——CountDownLatch
  • 阻塞各个子线程,指定各个子线程阻塞位置和阻塞子线程数————CyclicBarrier
  • 指定"同时"能有几个线程允许访问资源,其余线程阻塞(相当于同时发几个锁)————Semaphore

在‌[Java](https://www.baidu.com/s?tn=84053098_3_dg&wd=Java&usm=1&ie=utf-8&rsv_pq=d27c70f300152f55&oq=future get 阻塞原理&rsv_t=242bzBP8kvtrlMXu4O2LMbF5GaZ6EfLw%2BS9qZaqYmaYb%2BUrTzRKN0m6QipIjyMw1Hh7aTA&sa=re_dqa_generate)中,Future表示异步计算的结果,get()方法用于等待Callable任务结束并获取执行结果。如果任务还未完成,get()方法会阻塞当前线程,直到任务完成并返回结果。

多线程编程步骤

  1. 穿件资源类
  2. 资源类操作方法
    1. 判断
    2. 干活
    3. 通知
  3. 创建多个线程,调用资源类方法
  4. 防止虚假唤醒

Package java.util.concurrent

线程不安全

  • arrayList
  • HashSet
  • HashMap
    解决方案
  1. juc包下的写时复制技术
  • CopyOnWriteArrayList
  • CopyOnWriteArraySet
  • ConcurrentHashMap
  1. 集合工具包穿件带锁的集合
  • Collections.synchronizedList
  • Collections.synchronizedSet
  • Collections.synchronizedMap

synchronized

  • 非公平锁
  • 同步锁对象是谁

基本概念

线程VS进程

  • **进程:**是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程 设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的 描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活 动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
  • **线程: 操作系统能够进行运算调度的最小单位。**一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每条线程并行执行不同的任务

线程状态

NEW——新建
RUNNABLE——准备就绪
BLOCKED——阻塞
WAITING——不见不散啊
TIMED_WAITING——过时不候
TERMINATED——终结

wait()Vsleep()

wait() sleep()
Object的方法 Thread的静态方法
会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中) 不会释放锁,它也不需要占用锁

它们都可以被interrupted方法中断。

并发与并行

  • **并发:**同时刻多线程访问同一资源
  • **并行:**多项工作同时执行,最后再汇总

管程

  • Monitor监视器——锁
  • 一种同步机制,保证同一时间,只有一个线程访问被保护数据/代码
  • JVM同步基于进入和退出,使用管程对象实现的

用户线程和守护线程

属性(Daemon,可以进行设置)
(true)用户线程:自定义线程;主线程结束,用户线程还运行,jvm存活
守护线程:如垃圾回收,运行再后台;主线程结束,JVM结束

Lock接口

Package java.util.concurrent.locks

synchronized

多线程步骤
  1. 创建资源类(属性+方法)
  2. 创建多个线程,调用资源类操作方法
创建线程的方式
  1. 继承Thread类
  2. 实现Runnable接口
    匿名内部类
new Thread(new Runnable() {
   
            @Override
            public void run() {
   
                //调用执行方法
                tickets.sale();
            }
        }, "saler2").start();


Thread saler1 = new Thread(new Runnable() {
   
    @Override
    public void run() {
   
        //调用执行方法
        tickets.sale();
    }
}, "saler1");
        saler1.start();
        
        
        new Thread(
                ()->{
   
        for (int i = 0; i < 40; i++) {
   
        tickets.sale();
                    }
                            }, "saler1").start();
  1. 使用Callable接口
  2. 使用线程池
  • @FunctionalInterface——函数式接口——可以使用lamda表达式
  • 调用start的时候,线程不一定被创建好了(由操作系统决定)

[!IMPORTANT]

synchronized——自动上锁解锁

Lock接口

java.util.concurrent.locks.ReentrantLock
ReentrantLock——可重入锁

synchronized VS Lock
  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock() 去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5. Lock可以提高多个线程进行读操作的效率。
    性能竞争不激烈的时候,两者性能差不多,而系统资源竞争激烈的时候(大量线程),Lock性能更优

线程间通信

demo3ThreadCommunication

[!WARNING]

调用start的时候,线程不一定被创建好了(由操作系统决定)

资源类操作方法

  • 判断
  • 干活
  • 通知:通知其他线程执行

synchronized实现

  • 类的方法
  • 条件不满足等待
    wait()
    作用?等待多久?————等待下次抢到资源,在原地唤醒
  • 通知
    notify()
    notifyAll()
public synchronized void incr() throws InterruptedException {
   
        //条件判断
        if (number != 0){
   
            System.out.println(Thread.currentThread().getName()+":  条件不满足 - 等待...");
            this.wait();

        }
        number++;
        System.out.println(Thread.currentThread().getName()+":  加1操作 - 结果:"+number);
        //操作完成——通知其他线程操作
        this.notifyAll();
    }
    public synchronized void decr() throws InterruptedException {
   
        //条件判断
        if (number != 1){
   
            System.out.println(Thread.currentThread().getName()+":  条件不满足 - 等待...");
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+":  减1操作 - 结果:"+number);
        //操作完成——通知其他线程操作
        this.notifyAll();
    }
线程通信问题

条件写在if中可能会出现虚假唤醒——建议写在循环中(while循环判断条件)(其实也可以使用if-else)
wait特点——在哪里wait()就会在哪里唤醒不会从头执行

while(condition){
   
wait()}

Lock实现

条件调用等待、通知

condition.await()——等待
condition.signalAll()——通知`

基本步骤
  1. 定义重入锁
  2. 通过锁创建条件
  3. 上锁
  4. 条件判断——等待
  5. 干活
  6. 通知
  7. 解锁
private Integer number = 0;

//实例锁
private final ReentrantLock lock = new ReentrantLock();

//实例化条件
private final Condition condition =  lock.newCondition();

public void incr(){
   
        lock.lock();
        try{
   
            while (number!=0){
   
                condition.await();
            }
            System.out.println("@"+Thread.currentThread().getName()+"  加1操作, result = "+(++number));
        } catch (InterruptedException e) {
   
            throw new RuntimeException(e);
        } finally{
   
            condition.signalAll();
        }
    }

线程间的定制化通信

线程按照指定的执行顺序进行调用(上面谁抢到并满足条件就执行)

  • 使用标识位作为执行条件
  • 为每个线程创建一个条件
  • 完成通知(唤醒)拥有指定的condition(钥匙)的线程

案例:
AA打印5次->BB打印10次->CC打印15次——5个轮回

  • 使用标识位作为执行条件

  • 每个方法定义一个标识位,加入标识位变量,执行完成改变标识位,指定下一方法的标识位(标志位作为条件)(不同线程调用不同的方法)

为每个线程创建一个condition(相当于钥匙)

condition2.signal();通知condition2的调用者

public class PrintResource {
   
    //定义标志位
    private int flag = 1;

    //创建锁
    ReentrantLock lock = new ReentrantLock();

    //创建不同的条件
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();



    public void print5(int epcho){
   
        lock.lock();
        try{
   
            while (flag!= 1){
   
                condition1.await();
            }

            for (int i = 0; i < 5; i++) {
   
                System.out.println(Thread.currentThread().getName()+"::i -- 第 "+epcho + "轮");

            }

            //完成内容,更改标志位
            flag=2;

            //通知c2
            condition2.signal();
        } catch (InterruptedException e) {
   
            throw new RuntimeException(e);
        } finally{
   
            lock.unlock();
        }

    }

    public void print10(int epcho){
   
        lock.lock();
        try{
   
            while (flag!= 2){
   
                condition2.await();
            }

            for (int i = 0; i <10; i++) {
   
                System.out.println(Thread.currentThread().getName()+"::i -- 第 "+epcho + "轮");
            }

            flag=3;

            condition3.signal();

        } catch (InterruptedException e) {
   
            throw new RuntimeException(e);
        } finally{
   
            lock.unlock();
        }

    }

    public void print15(int epcho){
   
        lock.lock();
        try{
   
            while (flag!= 3){
   
                condition3.await();
            }

            for (int i = 0; i < 15; i++) {
   
                System.out.println(Thread.currentThread().getName()+"::i -- 第 "+epcho + "轮");
            }

            //通知
            flag=1;

            //通知c2
            condition1.signal();
        } catch (InterruptedException e) {
   
            throw new RuntimeException(e);
        } finally{
   
            lock.unlock();
        }

    }

}

集合线程安全

ArrayList

add没有锁

public boolean add(E e) {
   
        modCount++;
        add(e, elementData, size);
        return true;
    }

Exception in thread "第16个线程" java.util.ConcurrentModificationException
解决方案:
  • Vector
public synchronized boolean add(E e) {
   
modCount++;
add(e, elementData, elementCount);
return true;
}
  • Collections创建带锁list
  Collection<String> list = Collections.synchronizedList(new ArrayList<>());
  • JUC包中的CopyOnWriteArrayList(写时复制技术)推荐
    即兼顾了并发读,也兼顾了并发写
  CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

在这里插入图片描述

HashSet

add同样没有锁 synchronized

    public boolean add(E e) {
   
        return map.put(e, PRESENT)==null;
    }
解决方案
  • JUC包中的CopyOnWriteArraySet(写时复制技术)推荐
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();

HashMap

解决方案

ConcurrentHashMap<>()

public static void main(String[] args) {
   
    //HashMap线程不安全
//        HashMap<String, String> hashMap = new HashMap<>();

    //解决方案
    ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();


    //多线程操作
    for (int i = 0; i < 40; i++) {
   
        new Thread(()->{
   
            hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
            System.out.println(hashMap);
        },"第"+i+"线程").start();
    }
}

多线程锁

synchronized

实现同步的基础:Java中每个对象都可以作为锁
表现形式:

  • 对于普通方法,锁时当前实例对象
  • 对于静态方法,锁是当前类的Class对象
  • 对于同步方法块,锁是synchronized括号里配置的对象
    在分析多线程阻塞与非阻塞的时候,就看其同步的锁是谁,谁拥有锁

公平锁和非公平锁

ReentrantLock(boolean fair)——默认false
对比

非公平锁
  • 线程饿死
  • 效率高
    谁抢到锁谁有有权进行加锁操作
公平锁
  • 线程访问资源均衡
  • 效率相对较低
    将抢锁的线程放入队列,依次出队列加锁
源码
    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
   
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
   
        sync = fair ? new FairSync() : new NonfairSync();
    }

可重入锁/递归锁

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

synchronized

public static void main(String[] args) {
   
    Object o = new Object();
    new Thread(()->{
   
        synchronized (o){
   
            System.out.println(Thread.currentThread().getName()+"第一层");
            synchronized (o){
   
                System.out.println(Thread.currentThread().getName()+"第二层");
                synchronized (o){
   
                    System.out.println(Thread.currentThread().getName()+"第三层");
                }
            }
        }
    },"synchronized_thread").start();
}

ReentrantLock

static private void op(){
   

        try{
   
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName()+&
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值