多线程并发的优化

在日常开发中,多线程无数不在,尤其是android开发,看似业务代码没有调用多线程,实际上也在使用多线程,比如GC线程还有运行在子线程的网络请求。而在使用多线程的时候,不可避免的就需要做好并发安全,否则很容易出现死锁。为了优化多线程,首先就必须来了解一下关于多线程的一些基本概念。

1、线程和进程

  • 线程是进程中可独立执行的最小单位,也是 CPU 资源分配的基本单位。
  • 进程是程序向操作系统申请资源的基本条件,一个进程可以包含多个线程。
    对手机而言,我们开发的app就是运行在手机系统里面的进程之一

2、线程属性

线程有四个属性:编号、名字、类别以及优先级
(1)编号:线程的编号用于标识不同的线程,每条线程拥有不同的编号。
(2)名字:每个线程都有自己的名字,名字的默认值是 Thread-线程编号,比如 Thread-0
这个名字可以在出问题的时候让我们方便德定位到具体出问题德线程。
(3)类别:线程的类别可分为守护线程和用户线程
(在线程启动前,通过 setDaemon(true) 即可把线程设置为守护线程。)
区别:当 JVM 要退出时,会先考虑是否所有的用户线程都已经执行完毕,而守护线程,则不会考虑。

(4)优先级:线程的优先级用于表示应用程序希望优先(并不一定会优先)运行的线程,线程调度器在运行线程时,会根据这个值来决定优先运行哪个线程
(它的默认值伟5,取值范围为 1~10)

3、线程使用

有了以上的线程的基本概念后,接下来看看线程的基本使用。

  • 首先是简单的start()方法,启动线程。但是只能调用一次,再次调用会抛出非法线程状态异常。
  new Thread(){
            @Override
            public void run() {
                super.run();
            }
        }.start();
  • join方法。
    对于join方法,则用于插入到当前线程中执行,比如当前线程调用其他线程的join方法,则会插入其他线程,当前线程进入等待状态,直到插入的线程执行到结束后,当前线程继续执行。
  • yield方法
    此方法则是用于使当前线程放弃对处理器的占用,降低线程优先级,(注意,只是降低优先级,并不一定会使线程陷入暂停状态)
    要使线程进入暂停状态,则需要使用sleep方法
  • sleep(ms)方法
    sleep方法接收long参数,用于使当前线程休眠ms毫秒

实现线程安全

要实现线程安全主要时确保线程的原子性、可见性和有序性。

  • 原子性
    原子性是指对于涉及共享变量访问的操作,若该操作从其执行线程以外的任意线程看来是不可分割的,那么该操作就是原子操作,相应地称该操作具有原子性(Atomicity)
  • 可见性
    可见性是指一个线程对共享变量的更新,对于其他读取该变量的线程是否可见。
  • 有序性
    有序性是指一个处理器在为一个线程执行的内存访问操作,对于另一个处理器上运行的线程来看是乱序的。

也就是说,要实现线程安全就要确保线程的着四个特性。
常见的方法则是使用锁原子类型
锁可分为内部锁、显式锁、读写锁、轻量级锁(volatile)四种。

1、内部锁

锁是为了解决多线程共享同一资源时,对资源的占用控制,防止线程之间同时修改同一资源信息,导致不可预知的问题。
在java中,通过 synchronized 关键字来实现内部锁机制,相应的方法称为同步方法,而相应的代码则称为同步代码块。
当多个线程一起访问同一个资源时,使用了内部锁,可以避免同时修改同一个资源导致问题。举一个简单的例子,如:

    private static int resource=1;//同一个资源
   private static void add(){
        for (int i=1;i<10000;i++)
            resource+=1;
        System.out.println(resource);
    }

首先是创建一个方法去访问一个共同的资源,然后在开启两个线程去访问他:

        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                add();
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                add();
            }
        });
        thread1.start();
        thread2.start();

此时,最后thread2输出的结果并不是期望中的19999,而是14919或者是其他不确定的值,这是因为当两个线程分别读取resource为1,然后各自加1得到resource为2并先后写入,结果,虽然resource++执行了2次,但是实际resource的值只增加了1。
因此就必须给add方法加一个锁或者给共同资源resource加锁,如下:

private static synchronized   void add(){
        for (int i=1;i<10000;i++)
            resource+=1;
        System.out.println(resource);
    }

这样一来,就可以得到预想中的19999。

2、显式锁

内部锁是我们常用的一种锁方式。
对于显示锁,它是 Lock 接口的实例,Lock 接口只是对显式锁进行了抽象,实际上它的实现类是ReentrantLock。
与内部锁不同的是,使用显式锁需要自己释放和获取锁,即调用lock() 与 unlock() 方法。
例如,同样是上面的例子,使用显示锁解决多线程同时访问资源的问题时,则是使用如下所示的方式:

 private static   void add(){
        lock.lock();
        for (int i=1;i<10000;i++)
            resource+=1;
        System.out.println(resource);
        lock.unlock();
    }

倒也简单,就只是加上加上lock和unlock方法即可。与内部锁相比,显得比较灵活便利。

3、读写锁

读写锁 ReadWriteLock 接口的实现类是 ReentrantReadWriteLock,与其他锁不同的是,读写锁具有读锁共享写锁排他的特性
读锁共享指允许多个线程同时读取共享变量,即通过持有对应的读锁来访问共享变量,而读锁可以被多个线程持有。
写锁排他指在同一时刻,只允许一个线程更新共享变量,即通过持有对应的写锁访问共享变量,且写锁在任一时刻都只能被一个线程持有。
为了验证这两个特性,为方便比较,同样还是上面的例子,使用读写锁的解决方法如下:

 private static ReadWriteLock readAdWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readAdWriteLock .readLock();
    private static Lock writeLock = readAdWriteLock .writeLock();
    private static int resource=1;
private static  void add(){
        writeLock.lock();//加锁
        for (int i=1;i<10000;i++)
            resource+=1;
        System.out.println(resource);
        writeLock.unlock();
    }

在本例中,对资源自增1的操作是写操作,因此再加上writeLock.lock()和 writeLock.unlock()操作后即可实现对访问资源的保护,而对于读操作呢,由于读锁可以被共享,就不存在排他的特性:

 private static  void read(){
        readLock.lock();
        for (int i=1;i<100;i+=1)          
 System.out.println(Thread.currentThread().getName()+resource);
         readLock.unlock();
    }

截取部分输出结果如下:


image.png

可以看到,两个线程都可以持有读锁。

4、轻量级锁(volatile)

轻量级锁是指在满足一定的条件内,使用CAS(自旋)来尝试获取对象锁的一种机制,其中volatile关键字主要用于修饰共享变量,与其他锁相比,轻量级锁具有开销低的优点,主要用于修饰容易发生变化的变量。

死锁

讲到了多线程并发优化,就不得不一下死锁。
死锁的产生条件,举一个简单的例子,假如有线程A和线程B,线程A正在请求C资源,而线程B正在请求D资源,而这时,A线程占据着D资源同时B线程也占据着C资源,且A线程不肯释放D资源、B线程不肯释放C资源,于是两个线程就进入了循环等待的状态,产生了死锁。
大学里学过操作系统的都知道,产生死锁有着四个条件,即资源互斥、资源不可抢夺、占用并等待资源以及循环等待资源
其中,
资源互斥是指某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
资源不可抢夺是指资源只能被持有资源的线程主动释放,无法被其他线程释放。
占用并等待资源是指涉及的线程占用着资源(如例子中的A占用D)并等待其他资源(如C资源)
循环等待资源指涉及的线程必须等待其他线程释放资源,而其他资源又同时等待它释放资源。

解决死锁

知道了死锁的产生条件,要解决死锁也很简单,就是破坏死锁的四个条件即可。
即加锁顺序、加锁时限还有死锁检测。
加锁顺序即增加锁访问的顺序,以避免产生冲突;加锁时限则是设定锁循环等待的时限,如果超过时限就停止等待,而死锁判断则是及时判断死锁的出现。

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

推荐阅读更多精彩内容