栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC,学习并持续更新中......

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JUC,学习并持续更新中......

进程、线程、管程
  • 进程

    • 系统中一个运行的程序就是一个进程,每个进程都有自己的内存空间和系统资源

    • 进程是操作系统分配资源的基本单位

  • 线程

    • 也叫做轻量级进程,一个进程中有多个线程,这些线程共享一个进程中的资源

    • 线程是CPU调度的基本单位

  • 管程

    • Monitor(监视器),锁,是一种同分机制,保证同一时间只有一个线程可以访问同步的代码

    • Monitor对象和Java对象同一时间创建并销毁。底层由C++实现

用户线程和守护线程
  • 用户线程:系统的工作线程,会完成程序需要完成的业务

  • 守护线程:为用户线程服务的后台线程,若用户线程全部结束,就只剩下守护线程,守护线程没有服务的对象,守护线程随着JVM结束运行

  • isDaemon():判断是否为守护线程

  • setDaemon(true/false):true表示设置为守护线程,false表示设置为用户线程;但是需要在strat()之前设置

线程的五种状态

  1. 新建

    • 当使用new创建一个线程但没调用start()时,此时的状态是新建状态

  2. 就绪

    • 调用start()后,线程就进入就绪状态,但此时不会立即执行run(),需要等待获取CPU资源(线程运行的顺序可能和创建的顺序不一致)

  3. 运行

    • 当线程获取到CPU时间片后,就会进入运行状态,开始执行run()

  4. 阻塞

    • 当遇到以下情况,线程就会进入阻塞状态

      • 调用sleep(),使线程睡眠

      • 调用wait(),使线程等待

      • 当线程去获取同步锁,锁正在被其他线程持有

      • 调用阻塞式IO方法

      • 调用suspend(),挂起线程

  5. 死亡

    • 当run()正常执行结束或抛出异常都会使线程进入死亡状态

run()和start()
  • 创建线程重写run(),启动线程用start()

  • 当start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行,但并不意味着线程就会立即运行;当cpu分配给它时间时,才开始执行run()方法(如果有的话)

  • start()调用run(),run()被重写,run()中包含的是线程的主体

synchronized
  • synchronized是自动获得锁和自动解锁

  • synchronized实现同步,Java中每个对象都可以作为锁

    1. 对于普通方法,锁是当前实例

    2. 对于静态方法,锁是当前类的Class对象

    3. 对于同步代码块,锁是synchronized括号配置的对象

  • synchronized本身是非公平锁

  • 使用this.wait()进行等待

this.wait();
  • synchronized可重入锁

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() + " 内层");
            }
        }
    }
},"线程A").start();
  • 常用方法:

    • this.wait():把当前线程挂起

    • this.notify():唤醒阻塞的线程

    • this.notifyAll:唤醒其他所有的阻塞的线程

Lock
  • lock需要手动加锁和手动解锁,一般使用Lock的实现类ReentrantLock创建锁(可重入锁)

  • ReentrantLock()里面参数可填true或false,默认false

    • true表示公平锁,使得每个线程都会运行,但是效率低

    • false表示非公平锁,效率高,但是会出现线程饿死的情况

private final Lock lock = new ReentrantLock();
  • 使用lock的newCondition()创建Condition对象,再进行condition.await()进行等待

private final Condition condition = lock.newCondition();
condition.await();
  • lock可重入锁

ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
    try {
        lock.lock();//加锁
        System.out.println(Thread.currentThread().getName() + " 外层");
        try {
            lock.lock();//加锁
            System.out.println(Thread.currentThread().getName() + " 内层");
        }finally {
            lock.unlock();//解锁
        }
    }finally {
        lock.unlock();//解锁
    }
},"线程1").start();
  • 常用方法:

    • condition.await():把当前线程挂起

    • condition.signal():唤醒阻塞的线程

虚假唤醒
  • 使用this.wait()或者condition.await()进行等待,当被唤醒时,由于等待是在哪里睡,就在哪里醒,因此在等待时的条件不能是if,应是while,此时就会一直判断

while (number != 0) {
    this.wait();
}
while (number != 0) {
    condition.await();
}
volatile和synchronized
  1. volatile是线程同步的轻量级实现,volatile性能比synchronized好;volatile只能修饰变量,synchronized能修饰方法,代码块

  2. 多线程访问volatile不会阻塞,而synchronized会阻塞

  3. volatile能保证数据的可见性,但是不能保证原子性;synchronized既可以保证原子性,也可以保证可见性

  4. volatile解决的是变量在多个线程之间的可见性,synchronized解决的是多线程访问公共资源的同步性

ArrayList线程不安全问题
  • 使用Vector

List list = new Vector<>();
  • 使用Collections工具类的synchronizedList()

List list = Collections.synchronizedList(new ArrayList<>());
  • 使用CopyWriteArrayList(写时复制技术)

    • 写时复制技术

      • 当有多个线程请求同一资源时,他们会共同获取相同的引用指向相同的资源,若某个线程试图修改内容时,系统会复制一份副本给该线程,该线程修改完成后,将副本和原来的资源进行合并

List list = new CopyOnWriteArrayList<>();
HashSet线程不安全问题
  • 使用CopyOnWriteArraySet(写时复制技术)

Set set = new CopyOnWriteArraySet<>();
HashMap线程不安全问题
  • 使用ConcurrentHashMap

Map map = new ConcurrentHashMap<>();
Callable创建线程
  • Callable和Runnable的区别:Callable有返回值,Runnable没有返回值

  • 创建线程时,new Thread()里需要传入Runnable,但不支持Callable,而FutureTask实现了Runnable接口,在new Future()就可以传入Callable

FutureTask futureTask = new FutureTask<>(() -> {
    System.out.println(Thread.currentThread().getName());
    return 1111;
});
new Thread(futureTask,"线程A").start();
System.out.println("线程A返回值:" + futureTask.get());//可获取Callable返回值
  • FutureTask(未来任务),异步并行执行

    • 主线程让子线程去完成一个任务,子线程可能比较耗时,启动子线程开启执行任务后,主线程就去做其他事,忙完其他事后再去获取子线程执行的结果

CountDownLatch
  • 减少计数:每个线程执行一次就减少一个数量级,减少到0的时候再继续执行之后的代码

CountDownLatch countDownLatch = new CountDownLatch(5);//设置初始值
​
    for (int i = 1; i <= 5; i++) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 号线程执行了");
​
            countDownLatch.countDown();//计数减1
​
        }, String.valueOf(i)).start();
    }
​
countDownLatch.await();//值没有变为0的时候就一直等待,值为0的时候才执行下面的代码
​
System.out.println(Thread.currentThread().getName() + " 所有线程执行完毕");
CyclicBarrier
  • 循环栅栏,和减少计数相反,当线程执行的个数达到设置的个数时,再执行之后的代码

CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
    System.out.println("所有线程执行完毕");
});
​
for (int i = 1; i <= 7; i++) {
    new Thread(() -> {
        try {
            System.out.println(Thread.currentThread().getName() + "号线程执行了");
​
            cyclicBarrier.await();//等待,当七个线程执行完成后,才会执行CyclicBarrier里的代码
​
        } catch (Exception e) {
            e.printStackTrace();
        }
    },String.valueOf(i)).start();
}
Semaphore
  • 信号灯:多个线程进入有限个数的资源,例如:6辆车停到3个停车位

Semaphore semaphore = new Semaphore(3);//许可数量
​
for (int i = 1; i <= 6; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();//抢到车位
​
            System.out.println(Thread.currentThread().getName() + " 抢到车位");
​
            TimeUnit.SECONDS.sleep(new Random().nextInt(5));//设置随机停车时间(5s内)
​
            System.out.println(Thread.currentThread().getName() + " ---离开停车位");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
​
            semaphore.release();//释放停车位
​
        }
    },String.valueOf(i)).start();
}
读写锁
  • 写锁(独占锁),读锁(共享锁)

  • 一个资源可以被多个读线程访问,或可以被一个写线程访问

  • 不能同时存在读写线程,读和读共享、读和写互斥、写和写互斥

死锁
  • 两个线程执行的时候并持有对方的锁,两个线程都处于等待对方释放锁的情况,造成死锁

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

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");
        }
    }
},"线程1").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");
        }
    }
},"线程2").start();
锁的区别
  1. 无锁:多线程前方多资源,数据不一致

  2. 独占锁synchronized和ReentrantLock

    • 写和写、读和写、读和读都是独占的,同一时刻只能有一个线程访问

    • 读和读不能共享,效率低

  3. 读写锁ReentrantReadWriteLock

    • 写和写、写和读是独占的,读和读可以共享,提高性能

    • 缺点:①会造成锁饥饿;②写的时候可以读,但读的时候不能写

    • 读写锁降级:把写锁降级为读锁

      • 过程:①获取写锁;②获取读锁;③释放写锁;④释放读锁

阻塞队列
  • 在队列的头部有一个线程,负责把数据添加到队列中;若队列满,再添加数据时队列就处于阻塞状态

  • 在队列的尾部有一个线程,负责把数据从队列中取出来;若队列空,再取出数据时队列就处于阻塞状态

线程池
  • 三种线程池:

    1. 一池多线程

    2. 一池一线程

    3. 一池可扩容

  • 三种线程池平时都不使用,而是自定义线程池,线程池的三种创建方法底层都是new ThreadPoolExecutor()

  • 自定义线程池:创建ThreadPoolExecutor()的七个参数

    1. int corePoolSize:常驻线程数量

    2. int maximumPoolSize:最大线程数量

    3. long keepAliveTime:线程存活时间

    4. TimeUnit unit:线程存活时间的单位

    5. BlockingQueue workQueue:阻塞队列

    6. ThreadFactory threadFactory:线程工厂,用于创建线程

    7. RejectedExecutionHandler handler:拒绝策略

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2,
    5,
    2L,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(3),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
);

try {
    for (int i = 1; i <= 10; i++) {
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + "线程执行了");
        });
    }
}catch (Exception e){
    e.printStackTrace();
}finally {
    executor.shutdown();
}
分支合并框架
  • 利用多线程实现类似分治算法

异步回调
//异步调用,没有返回值
CompletableFuture future1 = CompletableFuture.runAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "future1");
});
future1.get();

System.out.println();

//异步调用,有返回值
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "future2");
    return 1111;
});
future2.whenComplete((t,u) -> {//当完成任务时执行
    System.out.println("t:" + t + ",u:" + u);//t为方法返回值,u为异常
}).get();

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1040314.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号