线程的同步互斥

  • 当多个线程对同一个数据进行操作的时候,就会出现线程安全问题。
  • 比如银行转账问题:同一个账户一边进行出账操作(淘宝支付),另一边进行入账操作(别人给自己汇款),此时会因为线程同步带来安全性问题。
  • 以下举一个线程安全问题的实例:
    两个线程不停地向屏幕输出字符串,A线程输出feifeilover,B线程输出xiaoxin,
    所要达到的目的是:屏幕显示完整的字符串。

代码如下:

package com.java;
public class Threadtrodition00 {
    public static void main(String[] args) {
        new Threadtrodition00().init(); 
    }   
    private void init() {
        final Outputer output = new Outputer();
        new Thread(new Runnable() {  //线程运行的代码在Runnable对象里面
            
            @Override
            public void run() {   //run中while循环是为了不停地运行
                while(true) {
                    try {
                        Thread.sleep(10);   
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    output.output("feifeilover");
                }
            }
        }).start();
        new Thread(new Runnable() {  //线程运行的代码在Runnable对象里面
            
            @Override
            public void run() {   //run中while循环是为了不停地运行
                while(true) {
                    try {
                        Thread.sleep(10);   
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    output.output("xiaoxin");
                }
            }
        }).start();//main中启动两个线程
    }
        
    class Outputer { // 定义一个内部类,此类为一个输出器
        public void output(String string) { // 这个方法是为了把字符串的内容打印到屏幕上
            int len = string.length();
            for (int i = 0; i < len; i++) {
                System.out.print(string.charAt(i));// 把字符一个一个的打印到屏幕
            }
            System.out.println(""); // 换行
        }
    }
}

注:内部类不能访问局部变量,为访问局部变量要加final;
静态方法里面不能new内部类的实例对象

  • 执行后的代码如下显示


    这里写图片描述

理想状态下我们希望上一个字符串打完以后,在执行别的,从执行后的结果显示,它没有等一个字符串全部输出,cpu却跑去执行另一个线程了;
这就是因为线程不同步,而使两个线程都在使用同一个对象。

  • 这里先给出一个声明:

同步(Synchronous) 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后继的行为。

  • 要从根本上解决上述问题 ,就必须保证两个线程A、B在对i输出操作时完全同步。即在线程A写入时,线程B不仅不能写,同时也不能读。因为在线程A写完之前,线程B读取的一定是一个过期数据

  • java中,提供了一个重要的关键字synchronized来实现这个功能。它的功能是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性(即上面代码的for语句每次应该只有一个线程可以执行)。

Synchrouized关键字常用的几种方法:

1.指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。

class Outputer {
        String str = "";    
        public void output(String string) {
            int len = string.length();
            synchronized (str) {   //加锁并传入同一个对象
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
        }
    }//内部类,是一个输出器 

用Synchronized实现同步互斥,在锁中一定要是同一个对象。

前面我们提到的A线程是output对象,B线程是output对象。这两个使用的是同一个对象,只需在内部类中加入String xxx = “”;获得Outputer的锁。
由以上代码可以看出锁就是Outputer里面的str。Outputer对象在外部看是output,而在内部看就是this。所以代码可以简化为:

class Outputer {
        public void output(String string) {
            int len = string.length();
            synchronized (this) {  
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
        }
    }//内部类,是一个输出器 

2 . 直接作用于实例对象:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
方法返回值前加synchronized(一般一段代码中只用一次synchronized,为了防止死锁)

class Outputer {
        public synchronized void output(String string) {
            int len = string.length();
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
    }//内部类,是一个输出器 

3.直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
静态同步方法使用的锁是该方法所在的class文件对象
代码如下:

static class Outputer {
        public synchronized void output(String string) {
            int len = string.length();
                for(int i=0;i<len;i++) {
                    System.out.print(string.charAt(i));
                }
                System.out.println("");
            }
    }//内部类,是一个输出器 
    
    public static synchronized void output3(String string) {
        int len = string.length();

        for (int i = 0; i < len; i++) {
            System.out.print(string.charAt(i));
        }
        System.out.println("");

    }

注:关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它确保了线程对变量访问的可见性和排他性。


经典面试题:

  • 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次。

首先,将子线程和主线程中要同步的方法进行封装,加上同步关键字实现同步。
代码如下:

package com.java;

public class TraditionalThread {
    public static void main(String[] args) {
        final Business business = new Business();   //创建一个business对象
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                for(int i=1;i<=50;i++) {    //来回循环50次
                    business.sub(i);
                }
            }
        }).start();
        for(int i=1;i<=50;i++) {
            business.main(i);
        }
    }
}   //先起两个线程,主线程和子线程

class Business {    //定义内部类
    public synchronized void sub(int i) {    //定义子线程  (加锁实现同步)
         for(int j=1;j<=10;j++) {
             System.out.println("sub "+j +","+"loop of " +i);
         } 
    }
    
    public synchronized void main(int i) {   //定义主线程(加锁实现同步)
        for(int j=1;j<=100;j++) {
            System.out.println("main " + j+","+"loop of " +i);
        }
    }
}   

以上代码实现了两个线程的互斥。

等待/通知机制

  • 一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B()调用了对象O的notify()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。以上两个线程就是通过对象O来完成交互的。

wait与notify实现线程间的通信代码(以上述面试题为例)

class Business { // 定义内部类
    private boolean BShould = true;

    public synchronized void sub(int i) { // 定义子线程 (加锁实现同步)
        if (!BShould) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println("sub " + j + "," + "loop of " + i);
        }
        BShould = false;
        this.notify();
    }

    public synchronized void main(int i) { // 定义主线程(加锁实现同步)
        if (BShould) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 100; j++) {
            System.out.println("main " + j + "," + "loop of " + i);
        }
        BShould = true;
        this.notify();
    }
}
  1. 在使用wait、notify方法时需要先对调用对象加锁
  2. notify方法调用后,等待线程依旧不会从wait()返回,需要调用notify()的线程释放锁之后, 等待线程才能有机会从wait()返回。
  3. wait()返回的前提是获得了调用对象的锁。

注:此系列博客参照张孝祥的java并发视频,以及java高并发程序等书写的,因为本人小白正在努力学习中,对知识掌握的特别的肤浅,如果看到我的博文,有什么不对的地方,或者是对我文章有意见的,可以私信给我,我会一直不断改进的。

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

推荐阅读更多精彩内容