java.util.concurrent包中的常见类

java.util.concurrent包中的常见类

1.ReentrantLock锁
a.两个线程并发执行,针对同一个变量各自自增50000次,这个过程是线程不安全的,数据最终的结果也不是100000,如何保证线程安全,加锁,synchronized和ReentrantLock锁

import java.util.concurrent.locks.ReentrantLock;

public class Test01 {
    static class TestIncrease{
        int i;
        ReentrantLock reentrantLock=new ReentrantLock();
        public void increase(){
            reentrantLock.lock();
            i++;
            reentrantLock.unlock();
        }
    }
    public static void main(String[] args) {
        TestIncrease testIncrease=new TestIncrease();
        Thread t1=new Thread(){
            @Override
            public void run(){
                for(int i=0;i<50000;i++){
                    testIncrease.increase();
                }
            }
        };
        t1.start();
        Thread t2=new Thread(){
            @Override
            public void run(){
                for(int i=0;i<50000;i++){
                    testIncrease.increase();
                }
            }
        };
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testIncrease.i);
    }
}

b.为什么引入ReentrantLock锁?

ReentrantLock锁是对synchronized锁的补充
1.这个锁加锁解锁是分开写的,lock()和unlock()方法;
2.提供了一个新的tryLock()方法;
3.这个锁是公平锁;
4.synchronized锁的等待唤醒采用的是wait()、notify()方法,唤醒线程是随机唤醒线程,
而ReentrantLock锁的等待唤醒采用的是Condition类提供的唤醒方法,可以指定唤醒具体的线程。
tryLock()方法和lock()方法的区别:
tryLock()方法:尝试加锁,加锁失败,不会阻塞等待
lock()方法:尝试加锁,加锁失败,会阻塞等待

c.synchronized和ReentrantLock方法适用场景是哪些?

当锁竞争比较小的时候,加锁可以采用synchronized,效率更高,可以自动释放,更便捷。
当锁竞争比较大的时候,加锁可以采用ReentrantLock,可以采用其中的tryLock(),更灵活的控制加锁
如果要使用公平锁,可以采用ReentrantLock锁。

d.已经存在synchronized,为什么juc当中会提出ReentrantLock?

1.ReentrantLock加锁解锁是分开使用的,使用比较灵活,lock(),unlock();
2.线程尝试获取synchronized锁,如果获取不到,就会进入阻塞状态,线程尝试获取ReentrantLock锁,采用的方法是unlock(),如果获取不到,不会进入阻塞状态。
3.公平锁,非公平锁:ReentrantLock可以指定是公平锁还是非公平锁,但是,synchronized只能是非公平锁。
4.等待唤醒机制:synchronized锁等待唤醒机制是利用wait()和notify()来实现的,ReentrantLock是利用Condition类中的等待唤醒机制来实现的,可以指定唤醒具体的线程。而notify()是随机唤醒线程。

2.原子类

原子类是CAS实现的

import java.util.concurrent.atomic.AtomicInteger;

public class Test02 {
    static class MyTest{
         AtomicInteger atomicInteger=new AtomicInteger(0);
        public void increase(){
            atomicInteger.getAndIncrement();
        }
    }
    public static void main(String[] args) {
        MyTest myTest=new MyTest();
        Thread t=new Thread(){
            @Override
            public void run(){
                for (int i=0;i<50000;i++){
                    myTest.increase();
                }
            }
        };
        t.start();
        Thread t2=new Thread(){
            @Override
            public void run(){
                for (int i=0;i<50000;i++){
                    myTest.increase();
                }
            }
        };
        t2.start();
        try {
            t.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(myTest.atomicInteger.get());
    }
}

b.原子类一般会使用在监视服务器方面

1.会监视服务器单位时间内收到的请求;
2.监视某个代码块执行了多长时间;
3.监视哪个方法被调用的次数比较多;
4.服务器内部某个类被创建出多少实例。

3.信号量-semaphore

信号量表示可以资源数
举例说明信号量的使用:
借助Java标准库里面的提供的信号量:Semaphore

import java.util.concurrent.Semaphore;

public class Test01 {
    public static void main(String[] args) {
        //定义可以用的资源数为4
        Semaphore semaphore =new Semaphore(4);
        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();
        }
    }
}

举例说明信号量的使用场景/对上面代码的解释:
有四个可用资源,p操作是申请资源,V操作是释放资源
总共用20个线程,前四个线程申请资源并且获取到资源,如果前四个线程没有释放资源,就会处于阻塞状态。当前面几个线程释放资源之后,后面的线程可以获取资源。
Semaphore可以实现"共享锁"这样的概念。

4.CountDownLatch

同步等待N个任务执行完毕
countDown():每执行完一个任务,数量减一
await():发生阻塞,直到所有任务执行完毕
代码举例:有八个人一起比赛50m跑步,当所有人都到达终点之后,比赛完毕

import java.util.concurrent.CountDownLatch;


public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        //计数:8
        CountDownLatch countDownLatch=new CountDownLatch(8);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("起跑");
                //用线程等待时间----》模拟每一位运动员的跑步时间
                try {
                    Thread.sleep((long)(Math.random()*8000));
                    System.out.println("到达终点");
                    countDownLatch.countDown();//相当于是减减操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for(int i=0;i<8;i++){
            Thread t=new Thread(runnable);
            t.start();
        }
        //阻塞等待,直到countDownLatch值减到0为止
        countDownLatch.await();
        System.out.println("全部到达终点");
    }
}

结果展示:
在这里插入图片描述

5.ConcurrentHashMap

线程安全的
HashTable也是线程安全的,但是,现在基本不使用

6.HashTable和ConcurrentHashMap的区别是什么?

1.锁冲突概率不一样。HashTable是针对整个Hash表进行加锁,而ConcurrentHashMap是针对每一个hash桶进行加锁(jdk1.8),锁粒度小,所以,ConcurrentHashMap的锁冲突概率小。
2.针对读操作不一样,ConcurrentHashMap中数据是被volatile关键字修饰的,直接操作内存,不需要加锁,而HashTable读数据需要加锁。
3.修改操作不一样,ConcurrentHashMap修改size属性(元素个数),采用的是CAS机制,HashTable是直接加锁。
4.扩容机制不一样,HashTable是直接扩容一个更大的内存,将旧的数据拷贝到新的内存中(一次性全部拷贝),ConcurrentHashMap内存分为两个部分,新数据部分和旧数据部分,(一部分一部分数据拷贝),在这个过程中,当增加元素的时候,往新内存中添加元素,当查找元素的时候,从新内存和旧内存中读取元素。

7.HashMap、HashTable、ConcurrentHashMap的区别是什么?

1.HashMap不是线程安全的,允许key为null;
2.HashTable是线程安全的,针对整个hashTable表进行加锁,key不为null;
3.ConcurrentHashMap是线程安全的,针对每个链表的头节点进行加锁,减少了锁冲突概率。读操作不需要加锁,使用volatile关键字,修改size操作,使用CAS机制,key不为null。

关于ConcurrentHashMap的补充问题

1.ConcurrentHashMap读操作需要加锁吗?
不需要,读操作使用volatile关键字,直接操作内存读取。
2.ConcurrentHashMap锁分段技术是什么?
在jdk1.8版本中,ConcurrentHashMap采用synchronized锁针对每个链表的头节点进行加锁。而在jdk1.8版本之前,把所有的哈希桶分成若干段,针对每段进行加锁。目的都是为了减少锁竞争、锁冲突。
3.ConcurrentHashMap在jdk1.8版本中,做了哪些优化?
a.锁的粒度变小了,从原来的锁分段到现在针对每个哈希桶进行加锁,进一步减少了锁冲突的概率。
b.底层数据结构发生了一点变化,在原来的版本当中,底层数据结构是数组+链表,而现在的数据结构是数组+链表/红黑树(什么时候转化为红黑树?数组长度大于64,链表长度大于8)

8.线程同步的方式

可以借助synchronized、Semaphore、ReentrantLock来实现线程同步

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值