多线程

作者: ttyttytty | 来源:发表于2022-02-18 17:15 被阅读0次

线程安全性

  • 多线程时,使用同步机制,对于可变共享对象的访问/修改,需要保证数据的正常。- 注:访问也是需要同步的(参考内存可见性)。
  • 线程的局部变量在独立的线程栈上,是线程安全的。

线程原则

原子性

  • 多个关联的原子变量/代码块之间,也需要确保原子性修改。

原子类AtomicXX

加内置锁

内存可见性

  • 读为什么也需要同步的原因:保证线程间的修改即时立刻可见。(与写,同一个同步实现)
  • 指令的重排列,无法保证执行的时序。
  • 锁前后,确保工作内存&主存的及时刷新。

volatile-不重排

  • 仅确保修改后,其他线程,立刻可见;不能保证修改时原子的。锁可以可见性+原子性。
  • 当旦仅当满足以下所有条件时,才应该使用 volatile 变量:
    1.对变量的写人操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
    2.该变量不会与其他状态变量一起纳人不变性条件中。
    3.在访问变量时不需要加锁

不变性

  • 不可变对象是必然线程安全的。

不安全问题

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

  • 并发时,由于不恰当的执行时序,导致不正确的数据。e.g.a++;可变的共享变量+并发,非同步原子
    的情况下,根据多线程的调度,数据不可靠。

2.数据竞争(Data Race)

  • 未同步,导致读写线程的数据时不正确的

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

4.发布和逸出

  • 发布:使得对象在当前作用域之外被使用。
  • 逸出:当不该发布的对象被发布出去。
    • this引用逸出
  • 在对象未构造完成之前,就发布该对象,破坏线程的安全性。
  • 构造函数中,发布其他对象×。super.register(new listener()),内部实例持有对象的隐含引用
  • 构造函数中,构造一个线程√,此时,线程持有当前对象this,启动一个线程×。
  • 构造函数中,调用可被重写的方法(final private)

5.线程封闭

  • 对象封闭在线程中,不被其他共享。

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

ThreadLocal(全局变量)

同步机制

原子类AtomicXX

volatile

线程安全的数据结构

  • Vetor

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

  • 重点在于进程间通信,本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互。
  • 对象是存在多线程共享的堆中的,不是局部变量的线程栈中,所以可以获取。

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

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

      本文链接:https://www.haomeiwen.com/subject/kqpylrtx.html