目录
synchronized和ReentrantLock 的区别
什么是JUC呢??
JUC是java的一个包--java.util.current包里的类.
ReentractLock是一种可重入锁,来保证线程安全
ReentranctLock是JUC包下的一个类,通常会有三种操作,lock,unlcok,trylock
Callable是一个接口,也是用来描述任务的,callable与RUNNABLE不相同RUNNABLE不带有返回值,而Callable带有返回值,通过Callable接口的call方法来计算结果,将任务计算的结果保存到FutureTask对象中,然后Thread构造方法里加上FutureTask对象,最后通过调用FutureTask对象的get方法得到返回值
- public static void main(String[] args) {
- Callable
callable = new Callable () { - @Override
- public Integer call() throws Exception {
- int sum =0;
- for(int i =0;i<=1000;++i){
- sum+=i;
- }
- return sum;
- }
- };
- FutureTask
futureTask = new FutureTask<>(callable); - Thread t = new Thread(futureTask);
- t.start();
- }
什么是线程池 ???
线程池就是将已创建好的线程放到线程池里,使用线程从池子里取,用完了就放回池子中,重复利用已经创建好的线程.

拒绝策略 : 当任务满了,在尝试添加任务,线程池该怎么做

newFixedThreadPool : 创建固定数量线程池
- public static void main1(String[] args) {
- //创建固定线程池数量
- ExecutorService erService = Executors.newFixedThreadPool(10);
- for(int i =0;i<10;++i){
- erService.submit(new TimerTask() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName());
- }
- });
- }
- }
- //submit可以执行有返回值的和无返回值的
- public static void main2(String[] args) throws ExecutionException, InterruptedException {
- ExecutorService service = Executors.newFixedThreadPool(10);
- Future
futureTask = service.submit(new Callable(){ - @Override
- public Integer call(){
- int sum =0;
- for(int i =0;i<100;++i){
- sum+=i;
- }
- return sum;
- }
- });
- //返回任务结果
- System.out.println(futureTask.get());
-
- }
newCatchThreadPool : 创建动态增长数量线程池
- public static void main3(String[] args) {
- ExecutorService service = Executors.newCachedThreadPool();
- //自定义线程池--->动态增长的线程池--会根据任务的数量,创建线程数量,一定时间内可以重复使用某线程
- for(int i =0;i<10;++i){
- int finalSz = i;
- service.submit(()->{
- System.out.println("第" + finalSz + "次" + "线程名"+ Thread.currentThread().getName());
- });
- }
- }
newSingleThreadPool: 创建单一线程的线程池
- public static void main6(String[] args) {
- //执行单线程任务线程池
- ExecutorService executor = Executors.newSingleThreadExecutor();
- for(int i =0;i<10;++i){
- executor.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("线程名"+Thread.currentThread().getName());
- }
- });
- }
- }
newScheduleThreadPool : 创建定期执行命令的线程池
- public static void main(String[] args) {
- //执行定时任务的线程池
- ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);//5个线程
- System.out.println("添加任务时间" + LocalDate.now());
-
- executor.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("执行子任务"+ LocalDate.now());
- }
- },2,TimeUnit.SECONDS);
-
- }
ExecutorsService是一个工厂类,Executors是静态方法-->利用静态方法创建的实例-->工厂方法
可以调用Executors的submit方法用来提交多份任务.
答案是不确定的.不同的程序,不同的CPU,不同的主机配置,线程数目是不能确定的.
但是可以通过压测-->性能测试,根据性能测试,测试出程序使用时间,CPU占用,内存占用来选择合适的线程数目.程序分为IO密集型和CPU密集型,大部分我们既需要IO密集型也需要CPU密集型,两者不同的时间比例也会影响线程数目.
- //实现一个线程池
- public class MyThreadPool {
-
- //队列用来存放任务同时要保证线程安全
- private BlockingQueue
queue = new LinkedBlockingQueue<>(); - private void submit(Runnable runnable){
- try {
- queue.put(runnable);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- private Runnable runnable;
- public MyThreadPool(int nThreads){
- for(int i =0;i
- Thread t = new Thread(()->{
- while(!Thread.currentThread().isInterrupted()){
- try {
- Runnable runnable = queue.take();
- runnable.run();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- t.start();
- }
- }
-
- public static int cnt =0;
- public static void main(String[] args) {
- // ExecutorService e = Executors.newFixedThreadPool(10);
- MyThreadPool e = new MyThreadPool(10);
- for(int i =0;i<100;++i){
- e.submit(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- });
- cnt++;
- }
- System.out.println("总共执行的hello 数量" + cnt);
-
- }
- }
信号量(semaphore)
信号量是用来表示可用资源的个数.
- 申请资源相当于P操作,会使得可用资源个数+1-->acquire
- 释放资源相当于V操作,会使得可用资源个数-1--->release
- 当可用资源个数为0时,想要申请资源,就会阻塞等待其他线程释放资源
信号量类似于停车场的车位,,当有一个车进来时就占上了一个车位(相当于P操作,--申请资源),当有一个车开走了就相当于空出一个车位(P操作,--释放资源),而当停车场车位满了,如果还有人想停车只能等待其他人把车开走空出一个车位.
代码示例
- 创建 Semaphore 示例, 初始化为 4, 表示有 4 个可用资源.
- acquire 方法表示申请资源(P操作), release 方法表示释放资源(V操作)
- 创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执行效果.

CountDownLatch
CountDownLatch要等待所有子任务都完成,整个任务才算完成
- CountDownLatch 调用countDown方法代表每个子任务已经完成
- CountDownLatch 调用 await()方法 等待所有子任务全部完成.
相当于举行跑步比赛.当发令枪响的时候,所有比赛人员一起跑,当所有人到达终点的时候,这场跑步比赛才结束.
- 构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成.
- 每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减.
- 主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了.

多线程集合类
其中我们以前学过一些集合其中有的是线程安全的,有的不是线程安全的.
eg : Stack,Vector,HashTable currentHashMap-----是线程安全的
eg : HashMap, ArrayList,LinkedList.....都是线程不安全的.
多线程使用ArrayList
在java.util.current包里也有线程安全的ArrayList的类.
- Collection.synchronizedList,Collection集合里的类synchronizedList,在关键方法中加了synchronized
- 还可以在使用ArrayList时加上ReentrantLock或者加上synchronized
- copyOnWriteArrayList--写时复制的容器
copyOnwriteArrayList是一种写时复制的容器,当进行修改数据时,会将原有数据拷贝到一个新容器里,然后在新容器里执行修改操作,然后将原有容器指向新的容器.
CopyOnriteArrayList 适合并发的读操作,因为在修改数据时需要完成大量的拷贝工作,拷贝成本会很高.
多线程环境使用队列
- ArrayBlockingQueue--------基于数组实现的阻塞队列
- LinkedBlockingQueue--------基于链表实现的阻塞队列
- PriorityBlockingQueue--------基于堆实现的带优先级的阻塞队列
- TransferQueue -------- 最多只包含一个元素的阻塞队列
多线程使用哈希表
单线程环境下我们使用的是HashMap ,而多线程有HashTable (不使用),ConCurrentHashMap(常常使用).
- HashTable
HashTable与JDK1.7以前HashMap的底层数据结构类似 数组 + 链表,HashTable是对里面的几个重要方法加锁,相当于对HashTable本身加了一把大锁,在多线程下调用方法使用都会产生锁竞争,这就会导致向不同的哈希桶里添加元素都会导致发生锁竞争,导致效率低下.

- ConCurrentHashMap
ConCurrentHashMap在JDK1.7以前采用的是分段锁,每一把锁,只锁一部分数据,访问不同数据段的数据不会产生锁竞争,只有访问相同数据段数据才会产生锁竞争.在JDK1.8底层数据结构采用数组+链表+红黑树,在并发时使用synchronized和CAS机制.
- ConCurrentHashMap是把锁细化,在每一个哈希桶上都加上一把锁,又因为哈希桶的数量可能会很多,这就导致锁竞争的概率就会很小
- ConCurrentHashMap采用CAS机制来维护size
- ConCurrentHashMap优化了扩容机制,比如原来的HashMap执行put操作一口气扩容,而ConCurrentHashMap每次只扩容一小部分,ConCurrentHashMap而是在查询时,使用新表和老表一起查询,当要插入元素时,只往新表中插入,搬运完最后一个元素再把老表删除.
- ConCurrentHashMap只有写加锁,读不加锁(加了volatile关键字)

-
Hashtable和HashMap、ConcurrentHashMap 之间的区别?
- 首先,HashTable和ConCurrentHashMap是线程安全的,而HashMap不是线程安全的
- HashTable是对HashTable本身加了一把大锁,导致多个线程修改任意数据都会产生锁冲突,效率低下,而ConCurrentHashMap在JDK 1.8以后将锁的粒度细分,每一个哈希桶都加上一把锁,由于可能桶数量会很多,所以锁冲突会减少,ConcurrentHashMap对扩容机制做了更好地改进,ConCurrentHashMap不是一口气全部完成扩容,而是化整为零,查询时新表旧表一起查询,插入时只往新表中插入,搬运完最后一个元素再把每次只扩容一小部分,最终完成整个扩容过程,同时CurrentHashMap并发时使用synchronized进行加锁并且使用CAS机制维护size
- HashTable,HashMap允许key值为null,而ConCurrentHashMap不允许key值为null