Java多线程 ----(2)多线程同步

1 同步函数
2 同步代码块
3 锁

另外,这篇文章尝试了一种新的行文风格,轻喷。

1、多线程存在的一个问题:当多个线程存在时,可能会出现同步问题。

例如,在某一个线程休眠的时候,可能其他线程会进入if语句继续执行,而当这个线程苏醒继续执行剩余的代码,程序的状态已经发生巨大变化,继续执行就会导致无法预计的后果。
例如,常见的卖票程序,总共100张票,在某个线程休眠之后,后进来的接着执行,会出现-1,-2号票。

//创建Ticket类,实现Runnable方法
public class Ticket implements Runnable{

    private int ticketCount = 100;
    public void run(){
        while(true){
            if(ticketCount>0){//该语句未生效,结果中出现了0,-1,-2号票
                 try{
                   Thread.sleep(10);//先进来的线程休眠,后进来的接着执行,可能出现异常
                }catch(InterruptException){
                    e.printStack();
                }
                System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
            }
        }
    }
}

public static void main(String args[]){
        Ticket t = new Ticket();
        //启动四个线程,共享Ticket
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
}
image.png

2、解决这个问题

常见的方法是使用Java提供的同步代码块,语法如下:

synchronized(对象){
}

修改后代码如下:

//创建Ticket类,实现Runnable方法
public class Ticket implements Runnable{

    private int ticketCount = 100;
    Object obj = new Object();//必须有对象,代表锁,必须放在执行代码的外层,保证其唯一性
    public void run(){
        while(true){
          synchronized(obj){
            if(ticketCount>0){
                 try{
                   Thread.sleep(10);//可能出现异常
                }catch(InterruptException){
                    e.printStack();
                }
                System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
            }
          }
        }
    }
}

通过上述的锁即可解决多线程的安全问题。

对象就如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取到了CPU的执行权,也无法进入上锁的代码块。

同步的前提:
1、必须要有两个或者两个以上的线程;
2、必须是多个线程同时使用一个锁;
同步的好处:解决了多线程安全问题。
弊端:多个线程需要判断,较为消耗资源。

3、同步函数

除了同步代码块之外,也可以用同步函数来来实现代码同步的功能。
例如:

//创建Ticket类,实现Runnable方法
public class Ticket implements Runnable{
    private int ticketCount = 100;
    public synchronized void run(){
        while(true){
            if(ticketCount>0){
                 try{
                   Thread.sleep(10);//可能出现异常
                }catch(InterruptException){
                    e.printStack();
                }
                System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
            }
          }
    }
}

但是,上述代码中同步函数的使用会导致一个问题,如图所示:

image.png

原因是:在0线程未执行完毕之前,其他的线程无法获得执行权,进入到该方法中来。

所以,在使用同步代码时:
必须要搞清楚“哪些些代码需要被同步,哪些则不能被同步!”

那么在改方法中需要同步的代码只涉及,ticket的操作,故可以将该部分代码抽取出来,使用一个同步函数表示,如下:

private int ticketCount = 100;
    public void run(){
        while(true){
            show();
        }
    }

   //抽取出需要同步的代码
    public synchronized void show(){
        if(ticketCount>0){
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"**********"+ticketCount--);
        }
    }

如此,即可实现多线程安全的功能


image.png

4 、新的问题:在上述代码中,似乎并没有新创建一个锁对象

Object obj = new Object();//被删除了

那么,在这个时候,同步函数使用的锁是哪个呢?

结论:同步函数使用的是this锁。

验证:
写一个小程序来测试一下,如下:
功能:声明两种加锁的方式来实现同步,一种是函数块,一种是代码块;

假设1:如果说两种方式使用的是同一个锁。如果是这样,下面的程序应该可以很好的完成同步功能,不会出现异常。

代码如下:

public class Ticket implements Runnable{

    private int ticketCount = 100;
    Object obj = new Object();
    boolean flag = true;
    public void run(){
        if(flag){
            while(true){
                show();
            }
        }else{
            while(true){
                synchronized (obj) {
                    if(ticketCount>0){
                        try{
                            Thread.sleep(10);
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
                    }
                }
            }
        }
    }

    public synchronized void show(){
        if(ticketCount>0){
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
        }
    }
}

主函数如下:

public static void main(String args[]){
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try {
            Thread.sleep(10);//必须让主线程休眠一会儿,否则flag总是false
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.flag = false;
        t2.start();
    }

运行结果如下


image.png

通过上图中0号票的存在,
假设1被推翻,即两种同步代码用的并不是同一个锁。

假设2:show()同步函数使用的是this锁,代码块使用的是new的Object锁(已知)。

验证代码如下:将obj换成this,若未出现安全问题,则说明show()使用的是this锁。

public class Ticket implements Runnable{

    private int ticketCount = 100;
    Object obj = new Object();
    boolean flag = true;
    public void run(){
        if(flag){
            while(true){
                show();
            }
        }else{
            while(true){
                synchronized (this) {
                    if(ticketCount>0){
                        try{
                            Thread.sleep(10);
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
                    }
                }
            }
        }
    }

    public synchronized void show(){
        if(ticketCount>0){
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
        }
    }
}

运行结果如下:


image.png

结果中并未出现0号异常票,所以,假设得证!

结论:同步函数使用的是this锁。

5 再说一个要注意的地方:

如果同步函数被静态所修饰,使用的锁是该方法所在类的字节码文件对象,可通过类名称.class获得。
代码例子:

public class Ticket implements Runnable{
private static int ticketCount = 100;
    Object obj = new Object();
    boolean flag = true;
    public void run(){
        if(flag){
            while(true){
                show();
            }
        }else{
            while(true){
                synchronized (Ticket.class) {
                    if(ticketCount>0){
                        try{
                            Thread.sleep(10);
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
                    }
                }
            }
        }
    }

    public static synchronized void show(){
        if(ticketCount>0){
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
        }
    }
}

结果如下:


image.png

结果正常,无0号票,但是如果不使用Ticket.class,使用this就会出现0号票。

END


P.S.第一次采用这种行文方式来写,感觉不错,更加面向浏览者,是个好的转变!O(∩_∩)O哈哈~

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