JUC基础

一、JUC基本概念

1.wait和sleep区别:

wait是Object的方法,会释放锁。(在哪睡在哪醒)

Sleep是Thread的方法,不释放锁。

2.管程(Monitor): 是一种同步机制,保证同一时间,只有一个线程访问被保护数据或代码。管程是随着对象创建而创建的,每个Java对象都有一个管程

jvm同步就是通过每次进入和退出,使用管程对象实现的,每次进入判断管程对象计数器是否为0,为0进入,不为0不进。

3.用户线程:自定义线程, 平时自己new的线程

主线程(main)结束了,用户线程还在运行,jvm存活。

守护线程: 比如垃圾回收,这种运行在后台的线程

主线程结束了,jvm结束。

4.Synchronized:Java中的关键字,是一种同步锁。

  • 修饰代码块,作用范围是大括号{}括起来的代码,作用对象是调用代码块的对象
  • 修饰方法,被称为同步方法,作用范围是整个方法,作用对象是调用这个方法的对象。

二、多线程编程步骤

step1:
创建资源类,在资源类创建属性与操作方法

就是去封装一个可以直接使用功能的类,不需要了解类里面的内容

step2:
在资源类中使用方法
(1)判断
(2)干活
(3)通知

step3:
创建多个线程,调用资源类的才做方法

step4:
防止虚假唤醒问题(判断条件放到while中,要将wait放在循环中,保证在醒来后在进行一次检查)

三、Lock

1.Lock和synchronized区别

  • Lock是一个类,synchronized是Java关键字。
  • Lock需要手动释放锁。synchronized自动上锁放锁
  • 发生异常lock不会自动释放锁,synchronized会
  • Lock可以让锁等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待线程会一直等待
  • Lock性能高

2.案例

eg:3个售货员,卖30张票

**分析:**资源类是票,一个售货员一个线程

Lock

java 复制代码
package com.sjy.lock;

import java.util.concurrent.locks.ReentrantLock;

class LTicket{
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    public void sale(){
        lock.lock();
        try{
            if(number>0){
                System.out.println(Thread.currentThread().getName()+" 卖出: "+(number--)+"剩余:"+number);
            }
        }finally {
            lock.unlock();
        }
    }
}

public class LSaleTicket {

    public static void main(String[] args) {
        LTicket ticket = new LTicket();

        new Thread(()->{
            for(int i = 0;i<100;++i){
                ticket.sale();
            }
        },"aa").start();

        new Thread(()->{
            for(int i = 0;i<100;++i){
                ticket.sale();
            }
        },"bb").start();

        new Thread(()->{
            for(int i = 0;i<100;++i){
                ticket.sale();
            }
        },"cc").start();
    }
}

synchronized

java 复制代码
package com.sjy.sync;

class Ticket{
    //票数
    private int number = 100;
    //操作方法:买票
    //因为这个方法是多个线程调用,操作同一资源,所以需要加锁保证线程安全
    public synchronized void sale(){
        //判断:是否有票
        if(number > 0){
            System.out.println(Thread.currentThread().getName()+" : "+(number--)+"剩下:"+number);
        }
    }
}

public class SaleTicket {

    //创建多个线程调用资源的操作方法
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<100;++i){
                    ticket.sale();
                }
            }
        },"aa").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<100;++i){
                    ticket.sale();
                }
            }
        },"bb").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<100;++i){
                    ticket.sale();
                }
            }
        },"cc").start();
    }
}

四、线程间通信定制

指多个线程在运行时,为了共同完成一个任务,彼此之间通过某种媒介发送信号或等待信号。

就是通过资源类里面共享资源,每个线程进行条件判断,就能实现通信。

例如下面这个例子中,flag标志位就是共享资源,建立三个Condition等候区,flag==1时c1的线程执行并改变标注为,flag==2时c2线程执行。

java 复制代码
package com.sjy.lock;

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

class ShareResource {
    //定义标志位
    private int flag = 1;
    //创建Lock锁
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5(int loop) throws InterruptedException {
        lock.lock();
        try{
            //判断
            while(flag !=1){
                //等待
                c1.await();
            }
            for(int i = 0;i<=5;++i){
                System.out.println(Thread.currentThread().getName()+":"+i+" 轮数:"+loop);
            }
            flag = 2;
            //通知
            c2.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print10(int loop) throws InterruptedException {
        lock.lock();
        try{
            //判断
            while(flag !=2){
                //等待
                c2.await();
            }
            for(int i = 0;i<=10;++i){
                System.out.println(Thread.currentThread().getName()+":"+i+" 轮数:"+loop);
            }
            flag = 3;
            //通知
            c3.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print15(int loop) throws InterruptedException {
        lock.lock();
        try{
            //判断
            while(flag !=3){
                //等待
                c3.await();
            }
            for(int i = 0;i<=15;++i){
                System.out.println(Thread.currentThread().getName()+":"+i+" 轮数:"+loop);
            }
            flag = 1;
            //通知
            c1.signal();
        }finally {
            lock.unlock();
        }
    }

}

public class ThreadDemo3{
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for(int i = 1;i<=10;++i){
                try {
                    shareResource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"aa").start();
        new Thread(()->{
            for(int i = 1;i<=10;++i){
                try {
                    shareResource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"bb").start();
        new Thread(()->{
            for(int i = 1;i<=10;++i){
                try {
                    shareResource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"cc").start();
    }
}

五、集合的线程安全

(一)List集合线程安全

1.问题描述

ArrayListadd 方法核心逻辑大致如下:

在add方法底层并没有加锁

main 方法中,每个线程都在做两件事:

  1. 写操作list.add(...)

  2. 读操作System.out.println(list)(隐式触发了对整个 list 的遍历)

线程 A 正在执行 System.out.println(list) 遍历到一半时,线程 B 刚好执行了 list.add()

这样list中的modCount和迭代器中的expectedModCount就不一致了,就会抛出异常

2.解决方案

CopyOnWriteArrayList(写时复制技术)原理:

读的时候并发的读,写的时候独立写。每次写复制一份,在合并集合。这样写的时候就不会影响到读的内容

当你执行 add()set()remove() 等修改操作时,它不是直接在原数组上改,而是分为以下几步:

  1. 加锁 :先通过 ReentrantLock 加锁,确保同一时刻只有一个线程在修改。

  2. 克隆 :把当前内部存储数据的数组(array完全复制出一份副本。

  3. 修改:在副本数组上执行添加或删除操作。

  4. 替换 :将副本数组赋值回内部的 array 引用。

  5. 解锁:释放锁。

我们这里只是解决了线程安全的问题,保证不会抛异常,但是读的时候还是可能会乱序,那不是我们要解决的问题

**使用方法:**直接new

就行,CopyOnWriteArrayList继承List,改写了add操作。

java 复制代码
package com.sjy.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * list集合线程不安全
 */
public class ThreadDemo4 {

    public static void main(String[] args) {

        //CopyOnWriteArrayList解决
        List<String> list = new CopyOnWriteArrayList<>();

        for(int i = 0;i<10;++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));

                System.out.println(list);
            },String.valueOf(i)).start();
        }

    }
}

(二)HashSet线程安全

解决方案

CopyOnWriteArraySet

用法和List一样

(三)HashMap线程安全

解决方案

CopyOnWriteArraySet

用法和List一样

六、多线程锁

1.synchronized锁的作用范围:

  1. 对于普通同步方法,锁是当前示例对象
  2. 对于静态同步方法,锁是当前类的Class字节码对象
  3. 对于同步代码块,锁是括号里自己设置的对象

2.非公平锁:

谁抢到谁执行。线程饿死,效率高。

实现方法:默认是非公平锁

java 复制代码
​private final ReentrantLock lock = new ReentrantLock(false);

3.公平锁:

线程抢到锁后会判断队列中是否有人排队,有人的话会进入队列排队,依次执行,效率相对较低。

java 复制代码
private final ReentrantLock lock = new ReentrantLock(true);

.可重入锁:

就是拿了锁,可以进这个锁锁住的所有门。synchronized和Lock都是可重入锁。

5.死锁

两个或两个以上的线程,因为争夺资源而导致的一种互相等待的现象。

例如下面这个例子,线程1抢到了a,线程2抢到了b双方互相等待对方放锁这就是死锁

java 复制代码
public class DeadLock {

    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+"持有锁a,试图获取锁b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b){
                    System.out.println(Thread.currentThread().getName()+"获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+"持有锁b,试图获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a){
                    System.out.println(Thread.currentThread().getName()+"获取锁a");
                }
            }
        },"B").start();
    }
}

如何判断程序中有死锁存在:

我们利用jdk提供的命令:
jps -l查找当前正在运行的程序。找到我们要检查的进程ID。

jstack ID会告诉里有没有死锁

bash 复制代码
jps -l
jstack ID

七、Callable

Runnable和Callable的区别:Callable有返回值,Runnable没

java 复制代码
class MyThread1 implements Runnable{
    @Override
    public void run() {
        
    }
}
class MyThread2 implements Callable{
    @Override
    public Object call() throws Exception {
        return 200;
    }
}

由于Callable没办法直接被Thread使用,所以我们找到了FutrueTask封装了Callable和Runnable

FutrueTask为什么叫这个名字?
就相当于分配出去一个任务让线程做,不要求立马做完,想要得到结果时,随时调用,如果没做完就等待。

java 复制代码
package com.sjy.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;



public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask1 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName()+"come in callable");
            return 1024;
        });

        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName()+"come in callable");
            return 200;
        });

        //创建一个线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

        //调用FutureTask的get方法
        System.out.println(futureTask2.get());
        System.out.println(futureTask1.get());
    }
}

八、JUC辅助类

(一)减少计数CountDownLatch

官方定义:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待

给定一个初始值count,每次调用countDown()方法count都会-1,await()方法等待count不大于0.继续执行await后的语句。

  • 调用countDown方法的线程不会阻塞
  • 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

使用步骤:

  1. 创建CountDownLatch对象,设置初始值

  2. 每次操作计数-1

  3. 在何时地方设置await等待计数器归零

案例:

6个同学陆续离开教室后班长才可以锁门

java 复制代码
import java.util.concurrent.CountDownLatch;

public class countDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室
        for(int i = 0;i<6;++i){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教师");
                //计数-1
                countDownLatch.countDown();

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

        //当计数器未到0时,一直等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+"班长锁门");
    }
}

(二)循环栅栏CyclicBarrier

官方定义:一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

使用步骤

  1. 创建目标线程数量
  2. 设置栅栏任务奖励
  3. 派遣线程去await打到目标值获得奖励

案例

集齐七颗龙珠

java 复制代码
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () -> {
            System.out.println("集齐7颗龙珠召唤神龙");
        });

        //集齐七颗龙珠过程
        for(int i = 0;i<7;++i){
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+"星");
                    cyclicBarrier.await();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            },String.valueOf(i)).start();
        }

    }
}

(三)Semaphore

官方定义: 一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。

九、读写锁

缺点:

(1)造成锁饥饿,一直读,没有写操作

(2)读时候,不能写,只有都完成后,才可以写。写操作的时候,可以读

悲观锁:每个线程操作时,其他线程只能阻塞

乐观锁:每个线程同时操作,并设置版本号,更新一次数据升级一次版本号,当线程发现自己的版本号和最新版本号不同,就进行合并,并更新版本号。

表锁:锁住一整张数据库表

行锁:锁住一行,会有死锁

读锁:共享锁,可以多个线程一起读,会有死锁

写锁:独占锁,只能有一个线程进行写,会有死锁

案例:

java 复制代码
package com.sjy.readwrite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{

    private volatile Map<String,Object> map = new HashMap<>();

    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key,Object value){
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
            TimeUnit.MICROSECONDS.sleep(300);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+" 写完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }

    }
    public Object get(String key){
        rwLock.readLock().lock();
        Object result = null;
        try {

            System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 取完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwLock.readLock().unlock();
        }
        return result;
    }
}
public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //创建线程放数据
        for(int i = 1 ;i<=5;++i){
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }
        for(int i = 1 ;i<=5;++i){
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

十、阻塞队列BlockingQueue

当队列为空,从队列获取元素会被阻塞,直到其他线程插入新元素被唤醒

当队列满了,从队列中添加元素会被阻塞,直到其他线程移除一个或多个元素后唤醒

优点:不需要关心什么时候要阻塞线程,什么是皇后要唤醒。

(一)ArrayBlockingQueue(定长的阻塞队列)

由数组结构组成的有界阻塞队列

(二)LinkedBlockingQueue

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

十一、ThreadPool线程池

newFixedThreadPool(int)一池n线程

newSingeThreadExecutor()一池一线程

newCachedThreadPool()根据需求创建线程,可扩容

三种线程池使用方法都一样,只是分配策略不一样。都是通过Executors框架来创建线程池。使用的时候只需要使用线程池的execute方法并指定Runnable任务即可,最后调用shutdown方法,线程池拒绝处理新的任务,并处理剩余任务,释放资源。

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {

    public static void main(String[] args) {
        //ExecutorService threadPool1 = Executors.newSingleThreadExecutor();// 5个窗口
        //ExecutorService threadPool1 = Executors.newFixedThreadPool(5);// 5个窗口
        ExecutorService threadPool1 = Executors.newCachedThreadPool();// 5个窗口
        //10个顾客请求
        try {
            for (int i = 0; i < 10; ++i) {
                threadPool1.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        }finally {
            threadPool1.shutdown();
        }

    }
}

底层原理

我们翻看源码会发现每个newThreadPool方法底层都会new一个ThreadPoolExecutor

java 复制代码
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

底层执行流程:

当执行了excute()方法后线程才被创建,任务处理会优先使用常驻线程,当常驻线程满了,第3,4,5个任务会在阻塞队列等待,当阻塞队列满了,会创建剩下的线程,给阻塞队列处理,当线程数满了,就会开始执行拒绝策略

自定义线程池(常用)

就按照上述参数,创建一个你需要的线程池。随后execute方法分配任务,

java 复制代码
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 < 10; ++i) {
                threadPoolExecutor.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}
相关推荐
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day7】
开发语言·jvm·数据库·c++·蓝桥杯
小江的记录本2 小时前
【端口号】计算机领域常见端口号汇总(完整版)
java·前端·windows·spring boot·后端·sql·spring
色空大师2 小时前
网站搭建实操(二)后台管理(1)登录
java·linux·数据库·搭建网站·论坛
柒.梧.2 小时前
深入理解AQS:Java并发编程的核心基石
java·开发语言
2301_792674863 小时前
java学习day24
java
咸鱼2.010 小时前
【java入门到放弃】跨域
java·开发语言
indexsunny10 小时前
互联网大厂Java求职面试实战:微服务与Spring生态全攻略
java·数据库·spring boot·安全·微服务·面试·消息队列
沐苏瑶10 小时前
Java 搜索型数据结构全解:二叉搜索树、Map/Set 体系与哈希表
java·数据结构·算法
冬夜戏雪11 小时前
实习面经记录(十)
java·前端·javascript