多线程

2022-02-18  本文已影响0人  ttyttytty

线程安全性

线程原则

原子性

原子类AtomicXX

加内置锁

内存可见性

volatile-不重排

不变性

不安全问题

1.竞态条件(Race Condition)----原子性

2.数据竞争(Data Race)

3.活跃性问题:操作无法正常执行下去。e.g.死锁/饥饿/活锁

4.发布和逸出

5.线程封闭

线程栈/局部变量+不逸出

ThreadLocal(全局变量)

同步机制

原子类AtomicXX

volatile

线程安全的数据结构

跨线程的回调函数是如何实现的?

CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待,类似有福同享,有难同当

因为在泛型代码内部,无法获取任何有关泛型参数类型的任何信息!,Java的泛型就是使用擦除来实现的
那如何获取参数类型?——类型标签Class< T>

typetoken获取类型转换?

全研成的单例内存返回(全局,Rxjava怎么会返回到第二次的?)
Rxjava如何实现线程切换,防止线程泄露?

胜澜的命令异步回调多线程内存泄露(全局变量缓存局部变量,且没有清空操作造成内存泄露https://blog.csdn.net/weixin_39629679/article/details/114221004

多线程各场景及解决方案


/**
 * scenario:写写未互斥:两个线程同时写同一个变量,
 * problem: 写非原子操作,数据异常
 * solution:写写互斥
 */
public class MultiWriteV1 {
    private int num = 0;

    private void read() {
        System.out.println(num);
    }

    private void write(int change) {
        num += change;
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
        }).start();

        // 睡眠一秒保证线程执行完成 todo
        // 1s一定OK?
        Thread.sleep(1000);
        // 读取结果
        read();
    }
}


/**
 * scenario:写写互斥:两个线程同时写同一个变量,
 * problem: 写写互斥
 * solution:写线程,对象锁,
 */
public class MultiWriteV2 {
    private int num = 0;
    private final Object lock = new Object();

    private void read() {
        System.out.println(num);
    }

    private void write(int change) {
        synchronized (lock) {
            num += change;
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
        }).start();


        // 睡眠一秒保证线程执行完成 todo
        // 1s一定OK?
        Thread.sleep(1000);
        // 读取结果
        read();
    }
}

/**
 * scenario:边读边写
 * solution:写写互斥,读写不互斥,读读不互斥
 * problem: 读到的不是最新主内存中的数据
 */
public class MultiReadWriteV1 {
    private int num = 0;
    private final Object lock = new Object();

    private void read() {
        System.out.println("read " + num);
    }

    private void write(int change) {
        synchronized (lock) {
            num += change;
            System.out.println("write " + num);
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();


    }
}


/**
 * scenario:边读边写
 * solution:写写互斥,读写互斥,读读互斥:读写互斥都加同一把锁
 * problem: 读到的是最新主内存中的数据,满足边读边写,但效率低,所有操作都互斥
 */
public class MultiReadWriteV2 {
    private int num = 0;
    private final Object lock = new Object();

    private void read() {
        synchronized (lock) {
            //所以我要给 read 中的代码也加上判断,它也要拿到钥匙后才能读取,
            // 这样就能保证读取时不会有写操作,写的时候也没有读取操作了
            //更常见的需求是写入全部完成后,再去读取值。
            System.out.println("read " + num);
        }
    }

    private void write(int change) {
        synchronized (lock) {
            num += change;
            System.out.println("write " + num);
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();


    }
}

/**
 * scenario:边读边写
 * solution:写写互斥,读写不互斥,读读不互斥,volatile
 * problem: 无效 读到的不是最新的
 */
public class MultiReadWriteV3 {
    private volatile int num = 0;
    private final Object lock = new Object();

    private void read() {
        System.out.println("read " + num);
    }

    private void write(int change) {
        synchronized (lock) {
            num += change;
            System.out.println("write " + num);
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();
    }
    
}

/**
 * scenario:写完再读
 * solution:写写互斥,读写互斥,读读互斥,加一个写线程是否结束的标志位,读线程循环检查标准位
 * problem: 一旦读在写完之前取拿到了锁,死循环在读线程while,一直打印waiting,写线程无法拿到锁,无法修改度的条件标志位;所有都互斥,效率低
 */
public class MultiReadWriteV4 {
    private int num = 0;
    private final Object lock = new Object();
    private boolean isWriteFinished1 = false;
    private boolean isWriteFinished2 = false;

    private void read() {
        synchronized (lock) {
            //所以我要给 read 中的代码也加上判断,它也要拿到钥匙后才能读取,
            // 这样就能保证读取时不会有写操作,写的时候也没有读取操作了
            //更常见的需求是写入全部完成后,再去读取值。

            //这不能实现10000次读取
//            if (isWriteFinished1 && isWriteFinished2) {
//                System.out.println("read " + num);
//            } else {
//                System.out.println("writing");
//            }

            while (!isWriteFinished1 || !isWriteFinished2) {
                System.out.println("writing");
            }
            System.out.println("read " + num);
        }
    }

    private void write(int change) {
        synchronized (lock) {
            num += change;
            System.out.println("write " + num);
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
            isWriteFinished1 = true;
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
            isWriteFinished2 = true;
        }).start();


        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();
    }
}
/**
 * scenario:写完再读
 * solution:写写互斥,读写互斥,读读互斥,不阻塞,等待/唤醒机制(synchronized (lock) {lock.wait、lock.notify}))
 * 写入操作不受限制;如果写入还没有完成,read 方法先进入等待状态。write 方法写入完成后,通知 read 开始读取
 * problem: 读读互斥,效率低
 */
public class MultiReadWriteV5 {
    private int num = 0;
    private final Object lock = new Object();
    private boolean isWriteFinished1 = false;
    private boolean isWriteFinished2 = false;

    private void read() {
        synchronized (lock) {
            while (!isWriteFinished1 || !isWriteFinished2) {
                // 等待,并且不要阻塞写入
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 只会在上述的lock被notify,往下走后,打印两次
                System.out.println("writing");
            }
            System.out.println("read " + num);
        }
    }

    private void write(int change) {
        synchronized (lock) {
            num += change;
            System.out.println("write " + num);
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
            isWriteFinished1 = true;
            //写线程写完后,唤醒读取线程继续读取。
            synchronized (lock) {
                lock.notify();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
            isWriteFinished2 = true;
            //写线程写完后,唤醒读取线程继续读取。
            synchronized (lock) {
                lock.notify();
            }
        }).start();


        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();
    }
}
/**
 * scenario:写完再读
 * solution:写写互斥,读写互斥,不阻塞,等待/唤醒机制(ReentrantLock+Condition 类替代synchronized (lock) {lock.wait、lock.notify}))
 * ReentrantLock可以设置尝试获取锁的等待时间+condition可以设置自唤醒时间
 * 写入操作不受限制;如果写入还没有完成,read 方法先进入等待状态。write 方法写入完成后,通知 read 开始读取
 * problem:读读互斥,效率低
 */
public class MultiReadWriteV6 {
    private int num = 0;

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    private boolean isWriteFinished1 = false;
    private boolean isWriteFinished2 = false;

    private void read() {
        lock.lock();

        while (!isWriteFinished1 || !isWriteFinished2) {
            // 等待,并且不要阻塞写入
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 只会在上述的lock被notify,往下走后,打印两次
            System.out.println("writing");

//            if (condition.await(1, TimeUnit.SECOND)) {
//                // 1 秒内被 signal 唤醒
//            } else {
//                // 1 秒内没有被唤醒,自己醒来
//            }


        }
        System.out.println("read " + num);

        lock.unlock();
    }

    private void write(int change) {
        try {
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                num += change;
                System.out.println("write " + num);

                lock.unlock();
            } else {
                System.out.println("1 秒内没有获取到锁,不再等待。不执行");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
            isWriteFinished1 = true;

            //写线程写完后,唤醒读取线程继续读取。
            // 写入完成,唤醒读取线程,await/signal 操作必须在 lock 时执行。
            lock.lock();
            condition.signal();
            lock.unlock();

        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
            isWriteFinished2 = true;
            //写线程写完后,唤醒读取线程继续读取。
            // 写入完成,唤醒读取线程,await/signal 操作必须在 lock 时执行。
            lock.lock();
            condition.signal();
            lock.unlock();
        }).start();


        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();
    }
}
/**
 * scenario:写完再读
 * solution:写写互斥,读写互斥,读读互斥(ReadWriteLock优化读读互斥,本身也支持不阻塞的唤醒通知机制)
 * problem:ReadWriteLock 会导致写线程必须等待读线程完成后才能写(悲观锁)
 */
public class MultiReadWriteV7 {
    private int num = 0;

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock writeLock = lock.writeLock();
    private final Lock readLock = lock.readLock();
    private final Condition condition = writeLock.newCondition();

    private boolean isWriteFinished1 = false;
    private boolean isWriteFinished2 = false;

    private void read() {
        readLock.lock();

        while (!isWriteFinished1 || !isWriteFinished2) {
            // 等待,并且不要阻塞写入
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 只会在上述的lock被notify,往下走后,打印两次
            System.out.println("writing");

//            if (condition.await(1, TimeUnit.SECOND)) {
//                // 1 秒内被 signal 唤醒
//            } else {
//                // 1 秒内没有被唤醒,自己醒来
//            }


        }
        System.out.println("read " + num);

        readLock.unlock();
    }

    private void write(int change) {
        try {
            if (writeLock.tryLock(1, TimeUnit.SECONDS)) {
                num += change;
                System.out.println("write " + num);

                writeLock.unlock();
            } else {
                System.out.println("1 秒内没有获取到锁,不再等待。不执行");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
            isWriteFinished1 = true;

            //写线程写完后,唤醒读取线程继续读取。
            // 写入完成,唤醒读取线程,await/signal 操作必须在 lock 时执行。
            writeLock.lock();
            condition.signal();
            writeLock.unlock();

        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
            isWriteFinished2 = true;
            //写线程写完后,唤醒读取线程继续读取。
            // 写入完成,唤醒读取线程,await/signal 操作必须在 lock 时执行。
            writeLock.lock();
            condition.signal();
            writeLock.unlock();
        }).start();


        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();
    }
}
/**
 * scenario:写完再读
 * solution:写写互斥,读写不互斥( StampedLock乐观锁,读的过程中也允许写。通过版本对比判断读的过程中是否有写入发生+回退悲观锁的方案,确保能读到主内存中最新的值)
 * problem:
 */
public class MultiReadWriteV8 {
    private int num = 0;
    private final StampedLock lock = new StampedLock();

    private void read() {
        long stamp = lock.tryOptimisticRead();
        int readNumber = num;
        if (!lock.validate(stamp)) {
            stamp = lock.readLock();
            System.out.println("乐观读取到的 number " + readNumber + " 有误,换用悲观锁重新读取:number = " + num);
            lock.unlockRead(stamp);
        }
    }

    private void write(int change) {
        long stamp = lock.writeLock();
        num += change;
        System.out.println("write " + num);
        lock.unlockWrite(stamp);
    }

    public void readAndWrite() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(1);
            }
            System.out.println("+ 10000 finish");
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                write(-1);
            }
            System.out.println("- 10000 finish");
        }).start();


        new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                read();
            }
            System.out.println("read finish");
        }).start();
    }
}

ReentrantLock
公平锁&非公平锁:抢锁直接排队还是先去抢失败再排队,ReentrantLock默认非公平
偏向锁、轻量锁等
乐观锁(无锁算法CAS,先干起来,写的时候检查是否被其他线程修改,修改的话就;AtomicInteger Java原子类中的递增操作就通过Compare And Swap(比较与交换自旋实现)&悲观锁(锁多写场景,,synchronized关键字和Lock的实现类):锁还是不锁同步资源
自旋锁&适应性自旋:抢锁失败了,阻塞这个线程还是让他自旋:多个处理器,CPU切换+恢复线程线程的消耗>自旋*N次的消耗。因为切换线程阻塞挂起唤醒等需要切CPU,保存/恢复现场,消耗大。如果同步资源获取后,线程处理逻辑简单,其实是比阻塞线程开销少。如果有多个处理器,可以让后来的线程自及忙一会,等会再来检查同步资源,到时候直接获取,避免线程切换。
可重入锁:同一线程能否多次获取同一把锁,ReentrantLock和synchronized都是重入锁
共享锁(大家都可读)&排他/独享锁(我自己可以读可以写):多个线程能否共享一把锁?那还锁啥锁?
https://tech.meituan.com/2018/11/15/java-lock.html
https://segmentfault.com/a/1190000023735772

上一篇下一篇

猜你喜欢

热点阅读