多线程并发

多线程并发必须掌握的相关概念:
多线程:Thread、ThreasPool
线程安全:volatile ,synchonized,lock,CAS,ThreadLocal
线程通信:synchonized+wait/notify;lock+condition;生产者与消费者
另外需要了解还有:JVM种锁的优化等

1、开启多线程的三种方式

2 Thread的run()和start()方法的区别

3、多线程不安全的原因

  • 线程不安全的诱因有3个:
    1.有共享变量
    2.处在多线程环境下
    3.对共享变量有修改操作(只有读操作不会造成线程不安全)
  • 结合JMM来说,变量存储在主内存,线程对变量的操作需要将变量从主内容复制到自己的工作内存,操作后再写回主内存。这样就会存在一个问题:线程A修改共享变量x的值,还未写回主内存,这是线程B也对x进行操作,但此时A线程中x对线程B时不可见的。

4、死锁

  • 原因:两个线程持有锁,却又在等待对方释放锁。
  • 解决方法: 在写代码时,要确保线程在获取多个锁时采用一致的顺序。
a和b两个方法都需要获得A锁和B锁。一个线程执行a方法且已经获得了A锁,在等待B锁;另一个线程执行了b方法且已经获得了B锁,在等待A锁。这种状态,就是发生了静态的锁顺序死锁。

//可能发生静态锁顺序死锁的代码
class StaticLockOrderDeadLock {
    private final Object lockA = new Object();
    private final Object lockB = new Object();

    public void a() {
        synchronized (lockA) {
            synchronized (lockB) {
                System.out.println("function a");
            }
        }
    }

    public void b() {
        synchronized (lockB) {
            synchronized (lockA) {
                System.out.println("function b");
            }
        }
    }
}

https://github.com/LRH1993/android_interview/blob/master/java/concurrence/deadlock.md

5、volatile

  • 既然有操作系统底层保证了缓存一致性原则为什么还会导致内存不可见?
    各个缓存会监视别的缓存中的数据,但是由于readBufer/loadBuffer的存在,数据在cpu/寄存器上修改后不会立即刷新到缓存/内存中,缓存中数据没有变化,所以导致内存不可见。
  • 指令重排序
    编译器和cpu都会进行指令重排序,在不影响单线程运行结果的情况下,对于没有依赖的数据的的读写操作会进行重排序。
  • volatile的作用
    使用volatile修饰变量相当于在插入了一个内存屏障,内存屏障能够保证2点:
    1、禁止cpu和编译器进行指令重排,内存屏障前的指令不会在内存屏障后面执行。
    2、强制把缓存中的数据刷新到主内存中,这个操作会导致其他缓存中的数据无效。
  • volatile为什么不能保证线程安全(原子性)
    以i++自增为例,volatile修饰变量i,可以保证内存可见性,但是i++并不是原子操作,该操作先是读值,再加1,然后在把输入刷新到内存中。
  • volatile的使用场景
    1、CAS,利用其可见性。
    2、DCL,利用其禁止指令重排。
    http://www.cnblogs.com/dolphin0520/p/3920373.html
    https://blog.csdn.net/javazejian/article/details/72772461
    https://www.jianshu.com/p/506c1e38a922
    https://tech.meituan.com/java-memory-reordering.html

6、synchonized的作用及底层原理

  • synchronized 关键字有三种应用方式:
    1 修饰实例方法,作用与当前的实例加锁,进入同步代码钱要获得当前实例的锁。
    2 修饰静态方法,作用于当前类的class对象加锁,进入同步代码前要获得当前类对象的锁
    3 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
  • synchronized 底层原理
    对象在内存中布局除了实例数据,还有对象头,对象头有一个指针,指向monitor对象。每个对象都存在一个monitor与之关联。线程进入同步方法或者同步代码块需要获取monitor的监视权。
    使用synchronized 修饰代码块时会在代码块前面插入MonitorEnter指令,在代码块后面插入Monitor插入MonitorExit指令。MonitorEnter指令会去尝试获取monitor的持有权,monitor对象有个字段count,计数器,当count等于0时表示当前没有任何线程持有monitor,那么该线程就能成功获取锁,执行同步代码块。
    同步方法的底层原理和同步代码块一致。
    https://blog.csdn.net/javazejian/article/details/72828483#synchronized%E7%9A%84%E5%8F%AF%E9%87%8D%E5%85%A5%E6%80%A7

6、Lock

  • ReentrantLock,可重入锁,可重入锁分为2种,公平锁和非公平锁,底层实现都是AQS。
    AQS有个成员变量state,用来控制同步状态。当state=0,表示当前没有线程持有该锁,否则说明有线程持有该变量,其他尝试获取锁的线程的线程进入队列中,节点进入队列后会处于一个自旋过程(死循环过程),如果节点的前驱节点是头节点,则尝试获取锁,如果不是则挂起线程,符合队列先进先出。
    公平锁与非公平锁的区别是公平锁先判断队列中是否有线程等待,有的话加入到队列尾部,没有才尝试获取锁,而非公平锁直接尝试获取锁。
    https://blog.csdn.net/javazejian/article/details/75043422
    https://blog.csdn.net/jiangjiajian2008/article/details/52226189
    https://www.jianshu.com/p/e4301229f59e

7、ThreadLocal

  • 线程局部变量实现线程安全的原理是:
    线程Thread类中有个变量是ThreadLoaclMap,ThreadLocalMap可以看作是一个哈希表,key是ThreadLocal,value则是调用set设置进来的值。每个线程维护自己的ThreadLocal,实现线程数据的隔离,实现线程安全。
  • ThreadLocalMap跟HashMap的区别:
    1、处理Hash冲突的方法:ThreadLocal是线性探测法,即找到数组的下一个null位置,作为插入点。采用线性探测法的原因是:key的hash值分布均匀冲突比较少,能快速找到插入的位置。
    2、ThreadLocalMap的key是ThreadLocal的弱引用对象,使用弱引用的原因是解绑Thread和ThreadLocal的生命周期,如果采用强引用的话则Thread存在的周期内,即使ThreadLocal已经没有使用到,ThreadLocal仍然无法垃圾回收。使用弱引用ThreadLocal会造成内存泄漏,因为ThreadLocal被回收,但是ThreadLocalMap到value存在引用,即使key为null,value也无法回收。为了避免这种情况,调用get,set等方法的过程,会去检查key是否为null,如果为null则回收该位置的Entry,另外一种方法避免内存泄漏的方法,使用完后手动调用remove方法。
    https://www.cnblogs.com/micrari/p/6790229.html
    https://www.cnblogs.com/zhangjk1993/archive/2017/03/29/6641745.html

8、CAS

9、锁的优化

首先偏向锁是为了避免某个线程反复获得/释放同一把锁时的性能消耗,如果仍然是同个线程去获得这个锁,尝试偏向锁时会直接进入同步块,不需要再次获得锁。
而轻量级锁和自旋锁都是为了避免直接调用操作系统层面的互斥操作,因为挂起线程是一个很耗资源的操作。
为了尽量避免使用重量级锁(操作系统层面的互斥),首先会尝试轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。
http://www.importnew.com/21353.html

10、线程池

  • 原理
    ①如果在线程池中的线程数量没有达到核心的线程数量,这时候就回启动一个核心线程来执行任务。
    ②如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行。
    ③由于任务队列已满,无法将任务插入到任务队列中。这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会立即启动一个非核心线程来执行任务。
    ④如果线程池中的数量达到了所规定的最大值,那么就会拒绝执行此任务,这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。
  • 4种线程池
    (1)newSingleThreadExecutor
    (2)newFixedThreadPool
    这两种线程池核心线程固定(newSingleThreadExecutor是一个核心线程,newFixedThreadPool是n个),线程池个数等于核心线程个数,任务队列无限。
    (3)newCachedThreadPool
    没有核心线程,阻塞队列为0,线程个数无限,,即是提交一个任务则会开启一个线程执行,空闲 60s后销毁。
    (4)newScheduledThreadPool
    定时执行任务
  • 创建线程池核心线程个数的选择
    (1) CPU密集型任务:线程池中线程个数应尽量少,如配置N+1个线程的线程池。
    (2) IO密集型任务:由于IO操作速度远低于CPU速度,那么在运行这类任务时,CPU绝大多数时间处于空闲状态,那么线程池可以配置尽量多些的线程,以提高CPU利用率,如2*N。
    https://github.com/LRH1993/android_interview/blob/master/java/concurrence/thread-pool.md

11、生产者消费者模型

  • wait/notify/notifyAll
static class ProduceThread implements Runnable {        
        Resource resource;
        public ProduceThread(Resource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            while(true) {
                try {
                    Thread.sleep((long) (1000 * Math.random()));
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                resource.produce(new Object());
            }
        }

    }
    
    static class ComsumeThread implements Runnable {        
        Resource resource;
        public ComsumeThread(Resource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            while(true) {
                try {
                    Thread.sleep((long) (1000 * Math.random()));
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                resource.consume();
            }
        }

    }

    static class Resource {
        private int num = 0;
        private int size;
        private Object[] list;
        private ReentrantLock lock = new ReentrantLock();
        private Condition produceCon = lock.newCondition();
        private Condition comsumeCon = lock.newCondition();

        public Resource(int size) {
            this.size = size;
            this.list = new Object[size];
        }

        public void produce(Object o) {
            try {
                lock.lock();
                if (num < size) {
                    list[num++] = o;
                    System.out.println("produce " + num);
                    comsumeCon.signalAll();
                } else {
                    produceCon.await();
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public Object consume() {
            Object o = null;
            try {
                lock.lock();
                if (num > 0) {                  
                    o = list[--num];
                    list[num] = null;
                    System.out.println("consume " + num);
                    produceCon.signalAll();
                } else {
                    comsumeCon.await();
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            return o;
        }

    }


//测试
public static void main(String[] args) {
        
        Resource resource = new Resource(10);
        Thread produceThread1 = new Thread(new ProduceThread(resource));
        Thread produceThread2 = new Thread(new ProduceThread(resource));

        Thread comsumeThread1 = new Thread(new ComsumeThread(resource));
        Thread comsumeThread2 = new Thread(new ComsumeThread(resource));
        
        produceThread1.start();
        produceThread2.start();
        
        comsumeThread1.start();
        comsumeThread2.start();
    }

11、线程执行相关

  • AABB问题
    多线程,5个线程内部打印hello和word,hello在前,要求提供一种方法使得5个线程先全部打印出hello后再打印5个word。
static class MyRunnable implements Runnable{

        @Override
        public void run() {
            synchronized (o) {
                System.out.println("hello");
                nextHashCode.getAndAdd(1);
                System.out.println(nextHashCode.get());
                if(nextHashCode.get() < 5) {
                    try {
                        o.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }else {
                    o.notifyAll();
                }
                System.out.println("world");
            }
            
        }
        
    }
  • ABCABC
    建立三个线程A、B、C,A线程打印10次字母A,B线程打印10次字母B,C线程打印10次字母C,但是要求三个线程同时运行,并且实现交替打印,即按照ABCABCABC的顺序打印。
public class ABC_Condition {
    private static Lock lock = new ReentrantLock();
    private static Condition A = lock.newCondition();
    private static Condition B = lock.newCondition();
    private static Condition C = lock.newCondition();

    private static int count = 0;

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    while (count % 3 != 0)//注意这里是不等于0,也就是说在count % 3为0之前,当前线程一直阻塞状态
                        A.await(); // A释放lock锁
                    System.out.print("A");
                    count++;
                    B.signal(); // A执行完唤醒B线程
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    while (count % 3 != 1)
                        B.await();// B释放lock锁,当前面A线程执行后会通过B.signal()唤醒该线程
                    System.out.print("B");
                    count++;
                    C.signal();// B执行完唤醒C线程
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    static class ThreadC extends Thread {
        @Override
        public void run() {
            try {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    while (count % 3 != 2)
                        C.await();// C释放lock锁
                    System.out.print("C");
                    count++;
                    A.signal();// C执行完唤醒A线程
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ThreadA().start();
        new ThreadB().start();
        new ThreadC().start();
    }
}

https://blog.csdn.net/xiaokang123456kao/article/details/77331878

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容