吃苹果比赛的同步安全问题

在本人学习Java的过程中,遇到了很多形形色色的问题。当时琢磨了好久才琢磨出这样的总结,贴出来供大家参考参考。以下观点仅代表本人在学习过程中的观点,望大家能够共同讨论,查漏补缺。


通过吃苹果比赛的实例,经过实际操作之后,我们不难看出会产生线程的同步安全问题。而具体的,我们使用继承的方式以及使用实现接口的方式都会造成不同的线程同步安全问题:

1、对于通过继承的方法来创建多线程,会造成不能操作同一个共享数据的问题
2、对于通过实现接口的方式,则会出现线程安全问题,例如出现共享的数据出现负数的问题

那么此时我们应该怎么解决呢?


针对出现的线程同步安全问题,通过查阅资料,我们不难发现的是我们可以通过同步锁来对此类问题进行解决。而在Java中对于同步锁的操作,我们常用的大致又分为了三种操作:

1、synchronized关键字
2、synchronized方法
3、Lock锁机制

首先我们需要来分析一下为什么会出现线程同步安全的问题(针对实现Runnable接口出现的出现数据负数的问题):

先将原代码贴出:
package learning.test.demo1;

//定义一个类实现Runnable接口
class AppleImple implements Runnable {
    // 定义一个苹果总数
    private int num = 50;

   // 复写run方法
    @Override
    public void run() {
    
            for (int i = 0; i < 50; i++) {
                if (num > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                 }
                 
/*
    由于是实现关系,而接口中是没有getName方法的,因此我们需要使用到静态方法来获取
    线程名
**/        
               System.out.println(Thread.currentThread().getName() + 
               "吃了第" + num-- + "个苹果");
                    
                }
            }
       }
}

public class AppleThreadByImplements {
        public static void main(String[] args) {
            // 创建三个线程
            // 创建实现类
            AppleImple ai = new AppleImple();
                new Thread(ai, "小黄").start();
                new Thread(ai, "小李").start();
                new Thread(ai, "小红").start();
    }
}
1、当代码执行到还剩下一个苹果的时候,我们的for循环此时判断num > 0,为true,因此进入了if语句,第一个线程A拿到了这个苹果,然后他进入了休眠

2、此时线程B抢到了执行权,而这时候,num还是为1的,因此通过if语句判断,num>0,因此又进入了if语句,这时候又陷入了休眠

3、此时线程C抢到了执行权,而这时候,num还是为1的,因此通过if语句判断,num>0,因此又进入了if语句,这时候又陷入了休眠

4、而这个时候恰好线程A苏醒了,他打印出了第1个苹果,这时候num--。

5、然后线程B接着苏醒,这时候num为0,因此线程B打印出第0个苹果,这时候num--,num为-1
   
6、然后这时候线程C又苏醒了,打印出了第-1个苹果。

因为线程是一直在相互切换的,我们需要一种有效的方法防止其自由切换,就好像你在家里你不锁门,然后就会有其他人可能会推门进来


那么此时我们就需要引入同步锁

同步锁概念:

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.
 
同步监听对象/同步锁/同步监听器/互斥锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
  
Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为
同步监听对象.

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能
在外等着.

对于同步锁,我们还要注意的就是其实他还是需要一个锁对象的,而这个锁对象就是一开始在类中创建的对象。

如果我们在局部方法中创建这个对象,那么则意味着当创建线程的时候,每创建一次就会创建一个锁,这样很明显是违背了只有一个锁的原则,这样还是会造成线程不安全的问题,因此,我们需要在全局字段中去创建这个锁对象


synchronized关键字
class Apple1 implements Runnable {

    // 定义一个私有字段
    private int num = 50;

    // 覆盖run方法
    public void run(){

        for (int i = 0; i < 50; i++) {
            synchronized(this){
                if(num > 0){
                /*
                    此时,我们是看不出结果的,因为线程切换的太快,
                    现在我们就使用sleep方法模拟网络延迟
                **/
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                System.out.println(Thread.currentThread().getName() 
                + "吃了第" + num-- + "个苹果");
                 }
             }
           
         }
    }
}

public class AppleThreadBySynchronized {
    public static void main(String[] args) {

        // 创建一个苹果对象
        Apple1 a = new Apple1();

        // 创建三个线程
        new Thread(a, "小黄").start();
        new Thread(a, "小丽").start();
        new Thread(a, "小雪").start();

    }
}
 当引入了同步锁之后,就不会出现类似的线程安全问题。我们可以这样理解:
 
 当线程A进来的时候,他是携带了一把锁,然后他就会一直占着这个程序的运行权限,就等同于
 你把门锁了,没有人能够进来。哪怕是你在里面睡着了,别人也只能等你出来他才能进去,因此
 只有当线程A完成了他的操作,线程B或者线程C才有机会获得执行权。

这样就保证了线程的安全性


同理的,使用synchronized方法以及使用Lock锁机制也是通过这样上锁的方式来保证我们的线程同步安全的问题,不同的只是在于操作的方式不同

下面我就贴出关于synchronized方法以及Lock锁机制的操作方式

synchronized方法
//同步方法示例
//定义一个类实现Runnable接口
class Apple implements Runnable{

    //定义一个私有化字段
    private int num = 50;

    @Override
    public void run() {

        for (int i = 0; i < 50; i++) {
            //定义一个同步方法
            synchronizedDemo();
        }
    }

    synchronized private void synchronizedDemo() {

        if(num > 0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() +
             "吃了第" + num-- + "个苹果");

        }
    }
}

public class SynchronizedByMethodDemo {
    public static void main(String[] args) {
        //创建一个实现类对象
        Apple a = new Apple();

        //创建三个线程
        new Thread(a,"小黄").start();
        new Thread(a,"小海").start();
        new Thread(a,"小清").start();
    }
}

通过以上实例,我们不难看出同步方法只是将我们的代码块封装进一个方法中,然后使用synchronized修饰这个方法,本质跟我们的synchronized关键字是相同的。

不过必须注意的是!!synchronized不能用来修饰run方法!!!!只能用来修饰普通的方法,然后在run方法中调用!!


Lock锁机制
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;



//定义一个类实现Runnable方法
class LockDemo implements Runnable{

    //创建锁
    //添加同步锁
    Lock lock = new ReentrantLock();

    //定义私有化字段
    private int num = 50;


    @Override
    public void run() {

        for (int i = 0; i < 50; i++) {
            lockDemo();
        }
    }


    private void lockDemo() {


        lock.lock(); 
        try{

            if(num > 0){

                //添加延时
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + 
                "吃了第" + num-- + "个苹果");

            }
        }finally{

            lock.unlock();
        }

    }
}


public class SynchronizedByLockDemo {
    public static void main(String[] args) {
        //创建对象
        LockDemo l = new LockDemo();

        //创建线程
        new Thread(l,"黄").start();
        new Thread(l,"海").start();
        new Thread(l,"权").start();
    }
}


对于Lock锁机制而言,不同之处在于就是我们不再是使用synchronized关键字,而是使用lock和unlock方法进行加锁还有释放锁的操作。

而Lock锁机制和synchronized关键字又有什么区别呢?我将在另外一篇帖子中阐述。

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

推荐阅读更多精彩内容