Java并发工具包-JUC

java并发包与线程池

java.util.concurrent

  • 并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥的问题
  • JDK 1.5推出的java.util.concurrent(并发工具包)可以解决这些问题。

new Thread的弊端

  • new Thread()新建对象,性能差
  • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源导致死机或OOM

线程池ThreadPool

  • 重用存在的线程,减少对象对象、消亡的开销
  • 线程总数可控,提高资源的利用率
  • 避免过多资源竞争,避免阻塞
  • 提供额外功能,定时执行、定期执行、监控等

线程池的种类

在java.util.concurrent中,提供了工具类Executors(调度器)对象来创建线程池,可创建的线程池有四种:

  1. CachedThreadPool - 可缓存线程池
  2. FixedThreadPool - 定长线程池
  3. SingleThreadExecutor - 单线程池
  4. ScheduledThreadPool - 调度线程池

可缓存线程池示例:

package threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolSample1 {
    public static void main(String[] args) {
        //调度器对象
        //ExecutorService用于管理线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个可缓存线程池
        //可缓存线醒池的特点是,无限大,如果线程池中没有可用的线理则创建。有空闲线程则利用起来
        for (int i = 1; i <= 1000; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
        //threadPool.shutdownNow();立即终止线程池
        threadPool.shutdown();
    }
}

定长线程池的示例:

package threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolSample1 {
    public static void main(String[] args) {
        //调度器对象
        //ExecutorService用于管理线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);//创建一个定长线程池
        for (int i = 1; i <= 1000; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
        //threadPool.shutdownNow();立即终止线程池
        threadPool.shutdown();
    }
}

单线程线程池示例:

单线程是指程序中只有一个线程在执行任务,执行完一个任务后才会执行下一个任务。单线程适用于一些简单的任务,例如顺序执行一些代码片段。

单线程池是单线程的一种实现方式,它将任务放入任务队列中,单个线程按照队列中的顺序依次执行任务。与单线程相比,单线程池可以更好地控制任务的执行顺序和频率,避免了线程启动和销毁的开销。但是,由于只有一个线程在执行任务,如果任务量过大或者某个任务阻塞,会导致整个线程池的执行性能降低。

package threadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolSample1 {
    public static void main(String[] args) {
        //调度器对象
        //ExecutorService用于管理线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建一个单线程池
        for (int i = 1; i <= 1000; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                }
            });
        }
        //threadPool.shutdownNow();立即终止线程池
        threadPool.shutdown();
    }
}

 CountDownLatch - 倒计时锁

  • CountDownLatch倒计时锁特别适合“总-分任务”,例如多线程计算后的数据汇总
  • CountDownLatch类位于java.util.concurrent (J.U.C) 包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了

执行原理

CountDownLatch是Java中一种同步工具,它可以使线程等待其他线程完成操作后再执行。CountDownLatch的原理是通过一个计数器来实现的,计数器初始值为n,当每个线程完成任务后,计数器的值减1。当计数器的值为0时,代表所有线程都已经完成任务,等待的线程就可以继续执行了。

在具体实现时,可以调用CountDownLatch的await()方法来阻塞当前线程,直到计数器的值为0才会继续执行。而其他线程完成任务后,调用CountDownLatch的countDown()方法来使计数器的值减1。当所有线程都完成任务后,计数器的值会变为0,此时等待的线程就可以继续执行了。

例如,假设有三个线程需要执行任务,可以用如下方式实现:

CountDownLatch latch = new CountDownLatch(3);

Thread worker1 = new Thread(new Worker(latch));
Thread worker2 = new Thread(new Worker(latch));
Thread worker3 = new Thread(new Worker(latch));

worker1.start();
worker2.start();
worker3.start();

latch.await();

System.out.println("All workers have completed their tasks.");

在这个例子中,CountDownLatch的初始值为3,代表有三个线程需要完成任务。每个线程完成任务后,都会调用latch.countDown()方法来使计数器的值减1。当计数器的值变为0时,latch.await()方法会返回,等待的主线程就可以继续执行。

示例

package threadPool;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownSample {
    private static int count = 0;

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(100);
        CountDownLatch countDownLatch = new CountDownLatch(10000);//总数和操作数一致
        for (int i = 1; i <= 10000; i++) {
            final int index = i;
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    synchronized (CountDownSample.class) {
                        try {
                            count = count + index;
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            countDownLatch.countDown();//计数器减1
                        }
                    }
                }
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(count);
        threadPool.shutdown();
    }
}

Semephore信号量

Semephore信号量的作用

  • Semaphore信号量经常用于限制获取某种资源的线程数量。
  • 举个例子,比如说操场上有5个跑道,一个跑道一次只能有一个学生在上面跑步,一旦所有跑道在使用,那么后面的学生就需要等待,直到有一个学生不跑了

示例

package threadPool;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreSample {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        Semaphore semaphore = new Semaphore(5);//定义5个信号量,也就是说假务器只充许5个人在坚面究
        for(int i=0;i<20;i++){
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();//获取一个信号量
                        play();
                        semaphore.release();//执行后释放
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
        threadPool.shutdown();
    }

    public static void play(){
        try {
            System.out.println(new Date()+" "+Thread.currentThread().getName()+"获取资格");
            Thread.sleep(1000);
            System.out.println(new Date()+" "+Thread.currentThread().getName()+"退出");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
semaphore.tryAcquire(2, TimeUnit.SECONDS)//尝试获取信号量,尝试等待2秒
package threadPool;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreSample {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        Semaphore semaphore = new Semaphore(5);//定义5个信号量,也就是说假务器只充许5个人在坚面究
        for(int i=0;i<20;i++){
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        if(semaphore.tryAcquire(2, TimeUnit.SECONDS)){//尝试获取信号量,尝试等待2秒
                            play();
                            semaphore.release();//执行后释放
                        }else {
                            System.out.println(new Date()+" "+Thread.currentThread().getName()+"无空闲线程");
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
        threadPool.shutdown();
    }

    public static void play(){
        try {
            System.out.println(new Date()+" "+Thread.currentThread().getName()+"获取资格");
            Thread.sleep(1000);
            System.out.println(new Date()+" "+Thread.currentThread().getName()+"退出");
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

CyclicBarrier循环屏障

  • CyclicBarrier是一个同步工具类,它允许一组线程互相等待,直到到达某个公共屏障点。
  • 与CountDownLatch不同的是,barrier在释放等待线程后可以重用,所以称它为循环(Cyclic)的屏障(Barrier)

示例

package threadPool;

import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierSample {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    go();
                }
            });
        }
        executorService.shutdown();
    }

    private static void go() {
        System.out.println(new Date() + " " + Thread.currentThread().getName() + "准备就绪");
        try {
            cyclicBarrier.await();
            System.out.println(new Date() + " " + Thread.currentThread().getName() + "开始就绪");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
    }

}

重入锁

  • 重入锁是指任意线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞
  • ReentrantLock设计的目标是替代synchronized关键字

与synchronized区别

示例

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class test {
    private static int count = 0;
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        CountDownLatch countDownLatch = new CountDownLatch(10000);//总数和操作数一致
        for (int i = 0; i <= 10000; i++) {
            int finalI = i;
            executorService.execute(() -> {
                lock.lock();
                count = count + finalI;
                countDownLatch.countDown();//计数器减1
                lock.unlock();
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(count);
        executorService.shutdown();

    }
}

Condition条件唤醒

  • 在并行程序中,某些线程要预先规定顺序执行,例如先新增再修改、先买后卖、先进后出…对于这类场景,使用JUC的Condition对象再合适不过了。
    JUC中提供了Condition对象,用于让指定线程等待与唤醒,按预期顺序执行。
    它必须和ReentrantLock重入锁配合使用。
  • Condition用于替代wait()/notify()方法
  • notify只能随机唤醒等待的线程,而Condition可以唤醒指定的线程,这有利于更好的控制并发程序

Condition核心方法

  • await() - 阻塞当前线程,直到singal唤醒
  • signal() - 唤醒被await的线程,从中断处继续执行
  • signalAll() - 唤醒所有被await()阻塞的线程

示例

package threadPool;

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

public class ConditionSamole {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    condition1.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("花落知多少");
                lock.unlock();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    condition2.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("夜来风雨声");
                condition1.signal();
                lock.unlock();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    condition3.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("处处闻啼鸟");
                condition2.signal();
                lock.unlock();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("春眠不觉晓");
                condition3.signal();
                lock.unlock();
            }
        }).start();
    }
}

Callable与Future

  • Callable和Runnable一样代表着任务,区别在于Callable有返回值并且可以抛出异常。
  • Future是一个接口,用于表示异步计算的结果。提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果

示例

package threadPool;

import java.util.concurrent.*;

public class FutureSample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 2; i <= 10000; i++) {
            Computor c = new Computor();
            c.setNum(i);
            //future用于计算线程的监听,因为计算在其他线程执行,返回是异步的
            Future<Boolean> future = executorService.submit(c);
            try {
                Boolean result = future.get();//获取返回值,没有则等待
                if(result){
                    System.out.println(c.getNum());
                }
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

class Computor implements Callable<Boolean> {
    private Integer num;

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    @Override
    public Boolean call() {
        boolean isprime = true;
        for (int i = 2; i < num; i++) {
            if (num % i == 0) {
                isprime = false;
                break;
            }
        }
        return isprime;
    }
}

并发容器

线程安全的类

  • Vector是线程安全的,ArrayList、LinkedList是线程不安全的
  • Properties是线程安全的,HashSet、TreeSet是不安全的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的
  • HashTable是线程安全的,HashMap是线程不安全的

线程安全-并发容器

  • ArrayList -> CopyOnWriteArrayList —写复制列表
  • HashSet -> CopyOnWriteArraySet ——写复制集合
  • HashMap -> ConcurrentHashMap ——分段锁映射

示例

CopyOnWriteArrayList

package threadPool;

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

public class CopyOnWriteArrayListSample {
    public static void main(String[] args) {
        //List<Integer> list = new ArrayList<>();//如果用普通arraylist,会报读写并发错误
        List<Integer> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        for (Integer i : list) {
            list.remove(i);
        }
        System.out.println(list);
    }
}

CopyOnWriteArrayList是一种线程安全的List,它的读操作不需要进行同步,而写操作则需要进行同步。它的原理是,写操作时先将List复制一份,然后在新复制的List上进行修改,修改完成后再将原List替换为新List。这种写操作虽然需要进行复制操作,但由于读操作不需要同步,因此读操作的性能非常高。

以下是CopyOnWriteArrayList的实现原理:

  1. CopyOnWriteArrayList内部维护了一个数组,读操作直接返回该数组,不需要进行同步。

  2. 写操作时,先将数组复制一份,然后在新数组上进行修改。这样,读操作可以继续在旧数组上进行,不会受到写操作的影响。

  3. 修改完成后,再将原数组替换为新数组。这样,读操作就能看到最新的修改结果了。

  4. 在复制数组时,CopyOnWriteArrayList使用了可重入锁来保证线程安全。

由于CopyOnWriteArrayList需要进行数组复制,因此写操作的性能比较低。因此,它适用于读操作比写操作多得多的场景,比如缓存。

ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author user
 */
public class test {
    private static ConcurrentHashMap count = new ConcurrentHashMap();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            int finalI = i;
            executorService.execute(() -> {
                count.put(finalI, finalI);
            });
        }
        executorService.shutdown();
        System.out.println(count.size());
    }
}

ConcurrentHashMap 是 Java 中的一个高效的并发 Hash 表,它支持同时多个线程访问它的查找与更新操作,并且不会阻塞其他操作。ConcurrentHashMap 的实现原理主要包括以下两个方面:

  • 分段锁机制

ConcurrentHashMap 内部维护了多个 Segment,每个 Segment 都是一个 Hash 表,它们是相对独立的,也就是说每个 Segment 都可以被不同的线程访问,不同的线程可以同时访问不同的 Segment,这样可以显著提高并发性能。在 ConcurrentHashMap 中,锁的粒度被降低到了 Segment 级别,而不是对整个 Hash 表加锁。

  • CAS 操作

ConcurrentHashMap 在执行插入和删除操作时,使用了 CAS(Compare-And-Swap)操作来保证数据的一致性。在插入或者删除一个元素时,如果该元素所在的位置已经被其他线程更新了,那么 CAS 会失败,防止多个线程同时修改同一个元素引起的数据不一致问题。

综上所述,ConcurrentHashMap 采用了分段锁机制和 CAS 操作来保证并发安全性,同时支持高效的并发访问。

Atomic与CAS算法(乐观锁)

原子性

指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。

Atomic包

  • Atomic包是java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类。
  • Atomic常用类
  1. AtomicInteger
  2. AtomicIntegerArray
  3. AtomicBoolean
  4. AtomicLong
  5. AtomicLongArray
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class test {
    private static AtomicInteger count = new AtomicInteger();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            executorService.execute(() -> {
                count.getAndIncrement();//count++
            });
        }
        executorService.shutdown();
        System.out.println(count);
    }
}

CAS算法

  • 锁是用来做并发最简单的方式,当然其代价也是最高的。
  • 独占锁是一种悲观锁。synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
  • 乐观锁,就是每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。其中CAS (Compare AndSwap,比较与交换)是一种有名的无锁算法。

Atomic的应用场景

  • 虽然基于CAS的线程安全机制很好很高效,
  • 但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小型,例如计数器等需求,否则也不会有锁的存在了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值