简单的并发编程,一句话概括“更多线程安全的方式”

 

1、ReentranLock(瑞恩纯特Lock)

       1、特性

                  1、可中断

                         synchronize锁不能用其他方式中断,ReentranLock就可以

                         举例:reentrantLock.lockInterruptibly();

                               该锁可以被线程的interrupt()打断,如果是lock则不会被打断

                  2、可以设置超时时间

                         synchronize会一直等待其他线程释放锁,ReentranLock可以设置一定时间后就不再去等待锁

                  3、可以设置公平锁

                         也就是可以设置线程先到先得

                  4、支持多个等待唤醒方式

                         值synchronize中,如果想要让线程休息,可以调用锁的wait和notify方法,这种会有问题,例如有多个线程同时休息,notify只能唤醒一个 ,notifyAll又全都唤醒,此时只想唤醒其中一个,就会出现问题

                         在reentrantLock中,可以给每个线程都有自己的wait和notify方法,就能很好的解决这个问题

static ReentrantLock reentrantLock = new ReentrantLock();
    //休息室 可以控制 1线程的等待和唤醒
    static Condition condition1 = reentrantLock.newCondition();
    //休息室 可以控制 2线程的等待和唤醒
    static Condition condition2 = reentrantLock.newCondition();
    static volatile int i = 0;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            while (i < 100) {
                try {
                    reentrantLock.lock();
                    i++;
                    System.out.println(Thread.currentThread().getName() + " :" + i);
                    //1线程休息
                    condition1.await();
                    //2线程启动
                    condition2.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    reentrantLock.unlock();
                }
            }

        }, "thread1").start();

        new Thread(() -> {
            while (i < 100) {
                try {
                    reentrantLock.lock();
                    i++;
                    System.out.println(Thread.currentThread().getName() + " : " + i);
                    //1线程启动
                    condition1.signal();
                    //2线程休息
                    condition2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    reentrantLock.unlock();
                }
            }
        }, "thread2").start();

    }

2、乐观锁CAS

        1、什么是CAS

                 以不需要锁的方式解决线程安全的问题,  传入修改前和修改后的值,比较修改后是否和修改后的值相同,如果修改失败则循环再次赋值,直到执行成功为止。需要配合volatile 关键字保证准确

       2、CAS效率比较

                 cas比synchronize效率高,因为每次不需要阻塞,修改失败再次修改就好了,而synchronize则每次都会导致线程阻塞,所以效率会低。

                 但是如果线程并发大的情况下,cas会导致修改失败很多次,效率可能会低

       3、乐观锁实现

              1、原子整数

                      安全类有AtomicInteger、AtomicBoolean 、AtomicLong 等原子整数

                     1、拿AtomicInteger举例,我们使用AtomicInteger实现cas操作

    public static void main(String[] args) throws InterruptedException {

        AtomicInteger atomicInteger = new AtomicInteger(0);
        Thread thread1 = new Thread(() -> {addInt(atomicInteger);}, "thread1");
        Thread thread2 = new Thread(() -> {addInt(atomicInteger);}, "thread2");
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
        System.out.println("最终结果:" + atomicInteger.get());

    }

    private static void addInt(AtomicInteger atomicInteger) {
        while (atomicInteger.get() < 100) {
            //获得当前值
            int prev = atomicInteger.get();
            //修改后的值
            int next = prev + 1;
            //这里返回true 即为修改成功  否则就一直修改
            while (atomicInteger.compareAndSet(prev, next)) {
                System.out.println(Thread.currentThread().getName() + " 修改后" + atomicInteger.get());
                break;
            }
        }
    }

                        2、也可以直接使用内部已经写好的方法进行操作,例如atomicInteger.compareAndSet(prev,next);,结果是一样的

              2、原子引用

                        原子引用类型给了并发更多的可能性,例如不是int,long,是float类型,就需要用到原子引用类型

                        1、AtomicReference:

                             引用类型,可以放任何类型

                        2、AtomicStampedReference:

                              解决ABA问题,ABA问题就是只判断内容是否一致,而不判断内容是否被修改过。如果想要就算值一致,但是只要是修改过,就修改失败,就可以用这个引用类型,解决方案是增加一个版本号,每次修改都将版本号+1

                              getReference :获取值

                              getStamp:获取版本号

                        3、AtomicMarkableReference:

                              可以判断是否修改过,有一个构造参数就是true或者false

                        4、 AtomicReferenceFieldUpdater:保护对象中的字段的线程安全问题

public class Demo1 {

    public static void main(String[] args) throws InterruptedException {
        User user = new User();
        AtomicReferenceFieldUpdater<User, String> name = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
        name.compareAndSet(user,null,"b");
        System.out.println(user);
    }

}
class User{
    //必须是volatile修饰  不能用private修饰 否则找不到
    volatile String name;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

              3、原子累加器

                       1、LongAdder、DoubleAdder等

                             主要是做自增操作,相比AtomicLong速度快

                       2、原理

                             缓存行和伪共享

3、final不可变类 

                  我们常用的SimpleDateFormat转换日期的方法,在多线程的情况下会出现报错,导致不能正确转换日期

       SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    simpleDateFormat.parse("1997-12-12");
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }

                此时jdk提供了DateTimeFormatter安全的日期转换类,做同样的操作,就不会出现这个问题

        DateTimeFormatter simpleDateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                simpleDateFormat.parse("1997-12-12");
            }).start();
        }

                原理:DateTimeFormatter内部使用了final修饰了类和常量,以保证类可以是不可变的,也就保证了在多线程的情况下  没有线程可以修改DateTimeFormatter的状态

                设计:设计final对象方法的时候,如果遇到了修改内容的时候,应该是直接创建新对象来覆盖该对象本身,而不是直接修改该对象的本身的内容,例如String就是一个很好的例子

                问题:每次都会创建很多的对象,造成对象的浪费,解决方案,可以使用设计模式:享元模式

                享元模式:达到最小化内存的使用,相同值的对象进行共享。例如Long.valueOf方法就是享元模式的体现,-128 - 127之间是拿的缓存中的值,大于这个值才会去创建新的对象,还有StringTable,线程池也是享元模式

4、ReentrantReadWriteLock(读写锁)

          1、介绍

                   当出现可能会有脏读的情况的时候,也就是读取的数据不是最新的数据,此时就应该用到读写锁,该锁的特点是读写阻塞,写写阻塞,读读不阻塞

    static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock rLock = rwLock.readLock();
    static ReentrantReadWriteLock.WriteLock wLock = rwLock.writeLock();

    public static void main(String[] args) throws InterruptedException {
        new Thread(() ->{write();}).start();
        Thread.sleep(500);
        new Thread(() ->{read();}).start();
    }

    private static void read(){
        rLock.lock();
        try{
            System.out.println("读操作");
            Thread.sleep(1000);
            System.out.println("读操作完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
    }
    private static void write(){
        wLock.lock();
        try{
            System.out.println("写操作");
            Thread.sleep(5000);
            System.out.println("写操作完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            wLock.unlock();
        }
    }

          2、注意

                      1、读锁不支持条件变量

                      2、重入时不支持锁升级,只支持锁降级,读锁去获取写锁,会导致死锁,但是可以先写锁再读锁,这样就可以正常执行

    private static void read(){
        wLock.lock();
        rLock.lock();
        try{
            System.out.println("读操作");
            Thread.sleep(1000);
            System.out.println("读操作完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
            wLock.unlock();
        }
    }

5、StampedLock

         1、介绍

                    1、读锁和写锁都是需要配合一个戳使用

                    2、相对于读写锁,还多了一个乐观锁,读取后先做一次锁校验,如果这期间没有被修改,那么数据就可以正常使用,如果修改了,再去加锁,也就可以增加效率

        StampedLock stampedLock = new StampedLock();
        //读锁  获得到戳
        long readStamped = stampedLock.readLock();
        //解锁时必须使用这个戳
        stampedLock.unlockRead(readStamped);

        //写锁  获得到戳
        long writStamped = stampedLock.writeLock();
        //解锁时必须使用这个戳
        stampedLock.unlockWrite(writStamped);

        long tryStamped = stampedLock.tryOptimisticRead();
        //true没有被修改 false为被修改了   没有加锁 只是判断了
        boolean validate = stampedLock.validate(tryStamped);
        if(validate){
            //可以正常执行
        }else{
            //加锁
        }

6、CountDownLatch

        1、介绍

              等待执行线程数执行完之后,调用await方法才能继续执行下去,否则就会一直阻塞,等待减完,例如 lol中10个玩家必须全部到达百分之百才可以进入游戏

    static CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {

        // 2修改为3  会将倒计时减完  也就能正常运行下去
        for (int i = 0; i < 3; i++) {
            new Thread(() ->{service();},"thread" + i).start();
        }

        //等待线程数为0,如果线程数为0的情况,才可以继续,否则会阻塞
        countDownLatch.await();
    }

    public static void service(){
        System.out.println(Thread.currentThread().getName() + " 执行");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 执行结束");
        //线程数减1
        countDownLatch.countDown();
    }

7、ConcurrentHashMap

         jdk7中,hashMap会导致循环链表

        

     原理

            get方法:没有使用锁

            put方法:

                  第一次没有map,懒加载:初始化map方法:使用cas来确保并发安全

                  初始化链表头:cas方式

                  扩容方法:在put时,如果发现别的线程正在扩容,则会去帮忙扩容,在数据重新计算放入链表的时候使用了synchronize锁

8、LinkedBlockingDeque

      原理

            使用了双向链表的数据结构,内部使用了ReentrantLock来保证线程的安全性

9、CopyOnWriteArrayList   CopyOnWriteArraySet

      原理

             使用了写入式拷贝的思想,增删改操作会将底层数组拷贝一份新数组,拷贝操作加了锁,更新操作会在新数组上操作。

             好处:不影响别的线程并发读,别的线程读的还是旧数组

             坏处:拷贝操作比较耗费时间

                      

         

猜你喜欢

转载自blog.csdn.net/qq_38384460/article/details/113700674
今日推荐