线程的同步互斥

  • 当多个线程对同一个数据进行操作的时候,就会出现线程安全问题。
  • 比如银行转账问题:同一个账户一边进行出账操作(淘宝支付),另一边进行入账操作(别人给自己汇款),此时会因为线程同步带来安全性问题。
  • 以下举一个线程安全问题的实例:
    两个线程不停地向屏幕输出字符串,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内部类的实例对象

  • 执行后的代码如下显示


    线程的同步互斥_第1张图片
    这里写图片描述

理想状态下我们希望上一个字符串打完以后,在执行别的,从执行后的结果显示,它没有等一个字符串全部输出,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

用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

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

class Outputer {
        public synchronized void output(String string) {
            int len = string.length();
                for(int i=0;i

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

static class Outputer {
        public synchronized void output(String string) {
            int len = string.length();
                for(int i=0;i

注:关键字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高并发程序等书写的,因为本人小白正在努力学习中,对知识掌握的特别的肤浅,如果看到我的博文,有什么不对的地方,或者是对我文章有意见的,可以私信给我,我会一直不断改进的。

你可能感兴趣的:(线程的同步互斥)