多线程--同步与锁

同步与锁

上一篇中,笔者介绍了Java多线程的基础知识,主要讲解了进程/线程的区别、Java多线程的创建、Java多线程的使用,以及Java多线程的生命周期。

对于多线程,笔者想强调一点的是,多线程的使用并不是为了提高程序的运行速度,而是为了提高程序的运行效率,让CPU的使用率更高,让资源得到更合理的安排。

如果你对上述的知识点,还不了解,那笔者建议还是先从多线程--基础入手,再来学习本篇文章。

今天,我们来继续学习Java多线程的内容---同步与锁。

线程安全

多线程的使用,让CPU得到了更充分的释放,让程序有了更高的执行效率,说直白点这叫释放天性。但是,多线程在使用的同时,也带来了一些烦恼和麻烦。俗话说的好,世界上任何事物都包含有利的一面和不利的一面,事物的两面性。

对于多线程来说,也遵循同样的道理,而不好的一面就是安全问题。

(1)什么是线程的安全问题?

(2)什么情况下会导致线程的安全问题?

解决问题之前,我们需要知道上面两个问题的答案。

问题一:

一个线程从开始执行到执行结束的过程,如果在执行过程中有一个对象的变量被其他线程锁修改,那么对于当前线程来说,就发生了线程安全问题。如果每次运行结果和单线程运行的结果不一致,那么就会产生线程安全问题。 

问题二:

运行多线程环境;

多线程中存在共享变量;

多线程中操作共享变量,主要操作行为--写操作;

我们都知道,CPU在任何一个时间点上都只会操作一个线程,我们感受到多个程序同时执行的情况,只不过是CPU模拟出来的结果,由于CPU处理的速度很快,在一秒中内CPU可以执行很多指令,处理很多线程,所以我们误认为是CPU在同时执行。

当一个线程在被执行时,不巧的是CPU在切换时,把执行权切换到了另一条线程上,处理了另外一个线程的请求,而另外的线程对共享数据做了改变。当CPU再次切换回原有的线程上时,共享的数据已被改变,数据安全问题由此诞生。

我们举个简单例子,来验证:

//卖票的例子:
public class ThreadTest9 implements Runnable {

    //可售总数量 100张:
    private Integer count = 100;

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while(true) {
            //如果大于0,就进行卖票
            if(count>0) {
                System.out.println(Thread.currentThread().getName()+"正在售票,剩余票数:"+ count--);
                continue;
            }
            System.out.println(Thread.currentThread().getName()+"票源不足,此时可售票数为:"+ count);
            break;
        }
    }

    public static void main(String[] agrs){
        ThreadTest9 ticket = new ThreadTest9();
        //开启4个窗口来卖票
        new Thread(ticket,"窗口1").start();
        new Thread(ticket,"窗口2").start();
        new Thread(ticket,"窗口3").start();
        new Thread(ticket,"窗口4").start();
    }
}

测试结果:

窗口3正在售票,剩余票数:100
窗口4正在售票,剩余票数:100
窗口2正在售票,剩余票数:100
窗口1正在售票,剩余票数:99
窗口2正在售票,剩余票数:98
。。。。。。。。。。。。。。。省略   
窗口2正在售票,剩余票数:8
窗口3正在售票,剩余票数:7
窗口1正在售票,剩余票数:6
窗口4正在售票,剩余票数:7
窗口2正在售票,剩余票数:5
窗口4正在售票,剩余票数:4
窗口4正在售票,剩余票数:1
窗口4票源不足,此时可售票数为:0
窗口2正在售票,剩余票数:44
窗口3正在售票,剩余票数:98
窗口2票源不足,此时可售票数为:-1
窗口1正在售票,剩余票数:0
窗口1票源不足,此时可售票数为:-1
窗口3票源不足,此时可售票数为:-1

在run()方法中,加入Thread.sleep(10)的主要目的是为了让四个线程都能得到执行。

通过测试结果,可以看到不同的窗口卖了相同的票,而在可售票源为0时,依旧有线程在做--操作。

如何解决线程的安全问题?

消除共享数据:在多线程中,不同线程间可共享的数据包括“成员变量、静态变量”,将这两种类型的变量转换为局部变量,那么线程安全问题自然就解决了。

建立副本:使用ThreadLocal为每一个线程建立一个变量的副本,将共享变量存入到ThreadLocal当中,这样以来实现每个线程间独立操作,算是上面解决方案的一种升华。

使用同步:在线程执行逻辑中,给读写共享变量的操作同时加锁处理,使得同一时刻只有一个线程可以操作访问共享数据。

下面,就主要来讲解下加锁处理线程安全!!!

解决线程的安全问题,主要是通过加锁来处理,加锁的目的就是为了保证一个线程在操作共享变量时,如果没执行完,那其他线程不能执行。简单点说就是一个线程在执行,其他线程给我等着,我不执行完,你们靠边呆着去。

在Java中,主要有三种方式来进行加锁,以来保证线程的安全:

同步方法:在类中的方法上加synchronized修饰符;

public synchronized void test(){
    //代码逻辑  
}  

同步方法中的锁是:this,即调用者本身;

静态同步方法: 在类中的静态方法上加synchronized

public static synchronized void test(){
    //代码逻辑  
}

静态同步方法中的锁是:类名.class,即Class对象;

同步代码块:在需要同步的代码上写一个synchronized(Object obj){}代码块;

synchronized(Object o){
    //代码逻辑
} 

同步代码块中的锁是:任意对象;

接下来,我们通过加锁的方式,再进行卖票:

//卖票的例子:
public class ThreadTest9 implements Runnable {

    //可售总数量 100张:
    private Integer count = 100;

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while(true) {
            //如果大于0,就进行卖票
            if(count>0) {
                synchronized (this) {
                    if(count>0){
                        System.out.println(Thread.currentThread().getName() + "正在售票,剩余票数:" + count--);
                        continue;
                    }
                }
            }
            System.out.println(Thread.currentThread().getName()+"票源不足,此时可售票数为:"+ count);
            break;
        }
    }

    public static void main(String[] agrs){
        ThreadTest9 ticket = new ThreadTest9();
        new Thread(ticket,"窗口1").start();
        new Thread(ticket,"窗口2").start();
        new Thread(ticket,"窗口3").start();
        new Thread(ticket,"窗口4").start();
    }
}

测试结果:

窗口2正在售票,剩余票数:100
窗口2正在售票,剩余票数:99
窗口1正在售票,剩余票数:98
窗口1正在售票,剩余票数:97
窗口3正在售票,剩余票数:96
窗口4正在售票,剩余票数:95
。。。。。。。。。。。。。。省略
窗口2正在售票,剩余票数:4
窗口2正在售票,剩余票数:3
窗口1正在售票,剩余票数:2
窗口4正在售票,剩余票数:1
窗口4票源不足,此时可售票数为:0
窗口1票源不足,此时可售票数为:0
窗口3票源不足,此时可售票数为:0
窗口2票源不足,此时可售票数为:0

synchronized的使用,解决了线程的安全问题,但是随着而来的是程序性能的下降。正所谓,事物的两面性。

单例设计模式

本文的最高,我们来说下面试中常见的一个问题:写一个性能安全的单例。

不安全的单例:

public class ThreadTest10 {
    private static ThreadTest10 single;
    private ThreadTest10(){}
    //懒汉:
    public static ThreadTest10 getInstance(){
        if(single==null){
            single = new ThreadTest10();
        }
        return single;
    }
}

安全的单例:

//无锁版:
public class ThreadTest11 {
    private static ThreadTest11 single = new ThreadTest11();
    private ThreadTest11(){}
    public static ThreadTest11 getInstance(){
        return single;
    }
}

//加锁版:
public class ThreadTest13 {
    //创建一把锁
    private static Object obj = new Object();

    private static ThreadTest13 threadTest13;

    private ThreadTest13(){}

    public static ThreadTest13 getInstance(){
        if(threadTest13==null){
            synchronized (obj){
                //双判断:
                if(threadTest13==null){
                    threadTest13 = new ThreadTest13();
                }
            }
        }
        return threadTest13;
    }
}

升级版:

后面说。。。。。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 142,680评论 1 300
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,177评论 1 256
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 94,249评论 0 212
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 40,981评论 0 175
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 48,746评论 1 255
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,763评论 1 176
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,362评论 2 268
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,151评论 0 165
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 28,964评论 6 229
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,501评论 0 213
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,285评论 2 215
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,614评论 1 229
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,232评论 0 31
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,117评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,498评论 3 204
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,615评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,012评论 0 167
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,512评论 2 230
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,594评论 2 231

推荐阅读更多精彩内容