JUC(Java.util.concurrent)的常见类

1.ReentrantLock

可重入互斥锁。和synchronized定位类似,都是使用实现互斥效果,保证线程安全。

ReentrantLock的用法:

  • lock():加锁,如果获取不到锁就会死等。
  • trylock(超时时间):加锁,如果获取不到锁,等待一定的时间之后就放弃加锁。
  • unlock:解锁。
import java.util.concurrent.locks.ReentrantLock;

/**
 * Describe:ReentrantLock中lock的使用
 * User:lenovo
 * Date:2023-03-30
 * Time:15:58
 */
public class TestDemo1 {
    public static ReentrantLock lock = new ReentrantLock();
    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                for (int i = 0; i < 1000; i++) {
                    count++;
                }
            }finally {
                lock.unlock();
            }
        });
        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                for (int i = 0; i < 1000; i++) {
                    count++;
                }
            }finally {
                lock.unlock();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

ReentrantLock和synchronized的区别

  • synchronized是一个关键字,是JVM内部实现的(大概率是基于C++实现的)。ReentrantLock是标准库中的一个类,在JVM外实现的(基于Java实现的)
  • synchronized使用时不用手动释放锁。ReentrantLock使用时需要手动释放锁。使用起来更加灵活,但是也容易遗漏unlock.(如果程序抛出异常,或中途跳出,容易导致忘记释放锁)
  • synchronized在申请锁失败的时候,会出现死等的情况。ReentrantLock可以通过trylock的方式等待一段时间就放弃
  • synchronized是非公平锁ReentrantLock默认是非公平锁。可以通过构造方法传入一个true开启公平锁模式。
  •  更强大的唤醒机制。synchronized是通过Object的wait/notify实现等待-唤醒。每次唤醒的是一个随机等待的线程。ReentrantLock搭配Condition类实现等待-唤醒,可以更精准控制唤醒某个指定的线程。

如何选择那个锁呢?

  • 锁竞争不激烈的时候,使用synchronized,效率更高,自动释放更方便。
  • 锁竞争激烈的时候,使用ReentrantLock,搭配trylock更加灵活控制加锁的行为,而不是死等。
  • 如果需要使用公平锁,使用ReentrantLock.

2.原子类

原子类内部使用的是CAS实现,所以性能要比加锁实现i++高了好多。原子类有:

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicIntegerLong
  • AtomicReference
  • AtomicStampedReference

以AtomicInteger举例,常见的方法有:

addAndGet(int delta);       i += delta;

decrementAndGet();        --i;

getAndDecrement();        i--;

incrementAndGet();         ++i;

getAndIncrement();          i++;

public class TestDemo3 {
    public static void main(String[] args) {
        AtomicInteger a = new AtomicInteger(0);
        System.out.println(a.incrementAndGet());//++a
        System.out.println(a.getAndIncrement());//a++
        System.out.println(a.decrementAndGet());//--a
        System.out.println(a.getAndDecrement());//a--
    }
}

 3.线程池

虽然创建销毁线程比创建销毁进程更轻量,但是在频繁的创建和销毁线程的时候还是会比较低效的

线程池就是为了解决这个问题。如果某个线程不在使用了,并不是真正的把线程释放了,而是放到一个池子里。如果需要用到线程就直接从池子中取,不必通过系统来创建。

3.1ExecutorService 和 Executors

代码示例

  • ExecutorService表示一个线程池示例。
  • Executors是一个工厂类,能够创建及几种不同风格的线程池
  • ExecutorService的submit方法能够向线程池中提交若干个任务。
public class TestDemo4 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(10);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        });
    }
}

Executors创建线程池的几种方式

  • newFixedThreadPool:创建固定线程数量的线程池;
  • newCachedThreadPool:创建线程数量动态增长的线程池;
  • newSingleThreadExecutor:创建只包含单个线程的线程池;
  • newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令。是进阶版的Timer

Executors本质上是ThreadPoolExecutor类的封装。

3.2ThreadPoolExecutor

ThreadPoolExecutor提供了更多的可选参数,可以进一步细化线程池行为的设定。

构造方法】 

理解ThreadPoolExecutor构造方法的参数

把创建一个线程可以想象为开个公司,每个员工相当于一个线程。

  • corePoolSize:正式员工的数量(线程一旦被创建就不会销毁)
  • maximumPoolSize:正式员工 + 临时工(线程一段时间不使用,就会销毁)
  • keepAliveTime:临时工允许的空闲时间;
  • unit:keepAliveTime的时间单位,是秒,分钟,还是其他的值;
  • workQueue:传递任务的阻塞队列
  • threadFacktory:创建线程的工厂,参与具体的创建线程工作;
  • RejectedExecutionHandle:拒绝策略,如果任务量超过公司的接下来怎么处理

拒绝策略

  1. AbortPolicy():超过负荷,直接抛出异常;
  2. CallerRunsPolicy:调用者负责处理;
  3. DiscardOldestPolicy():丢弃队列中最老的任务;
  4. DiscardPolicy():丢弃最新的任务。
public class TestDemo5 {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(4, 8, 1000, TimeUnit.MICROSECONDS,
                                                        new SynchronousQueue<Runnable>(),
                                                        Executors.defaultThreadFactory(),
                                                        new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 9; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
        }
    }
}

4.信号量

信号量:用来表示可用资源的个数。本质上是一个计数器。

理解信号量】可以把信号量理解为停车场的指示牌:当前由100个车位,表示有100个车位是空闲的。当有一辆车进入的时候,相当于申请一个资源,数量就会-1;当有一辆车出来的时候,相当于释放资源,数量就会+1。同样的栗子,有火车票剩余数量,酒店空闲房间等。

Semaphore的PV操作中加减计数器操作都是原子的,可以在多线程的环境下使用。

代码示例

  • 创建Semaphore示例,初始化为1,表示有1个可用的资源;
  • acquire方法表示申请资源(P操作),release方法表示释放资源(V操作)
  • 创建20个线程,每个线程都尝试申请资源,sleep1秒之后释放资源。
public class TestDemo6 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("申请资源");
                    semaphore.acquire();
                    System.out.println("获取到资源了");
                    Thread.sleep(1000);
                    semaphore.release();
                    System.out.println("释放资源了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 20; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }


}

 5.CountDownLatch

同时等待N个任务执行结束

【举个栗子】好像跑步比赛,10个选手依次就位,哨声响起同时出发;所有选手到达终点,比赛此结束。

  • 构造CountDownLatch,初始化10,表示有10个任务需要完成。
  • 每个任务执行完毕,都调用latch.countDown().在CountDownLatch内部的计数器同时自减;
  • 主线程中使用latch.await();阻塞等待所有任务执行完成。相当于计数器为0了。
public class TestDemo7 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(10);
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            new Thread(r).start();
        }
        latch.await();
        System.out.println("比赛结束");
    }
}

6.相关面试题

1)线程同步的方式有哪些?

synchronized,ReentrantLock,Semaphore等都可以用于线程同步。

2)为什么有了synchronized,还需要juc下的lock?

以juc的ReentrantLock为例,

  • synchronized使用时不需要手动释放锁。ReentrantLock需要手动释放锁,使用起来更灵活。
  • synchronized在申请锁失败的时候,会死等。ReentrantLock可以通过trylock的方式等待一段时间就放弃了。
  • synchronized是非公平锁。ReentrantLock默认是非公平锁,可以通过构造方法传入一个true开启公平锁模式。
  • synchronized是通过Object的wait/notify实现等待唤醒。每次唤醒的是一个随机等待的线程。ReentrantLock搭配Condition类实现等待-唤醒,更加精确控制某个指定的线程

3)AtomicInteger实现的原理是什么?

基于CAS机制来实现

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值