java多线程技术系列-二 线程互斥技术

线程安全

背景:

银行卡转账:比如我卡里有1000块,我现在要转转账200块给家里人,这时候程序执行拿到1000这个余额,执行1000-200这个计算(**A线程**),
正准备将余额=800赋值的时候,这时候另外有个人给我的账号里面转300(**B线程**),这时候CPU跑到另外一个线程B去做balance=1000+300
这个动作,做完之后余额balance=1300,这时候CPU跑回到A线程继续执行1000-200的动作,最终balance=800 造成数据不一致

原因

多个线程同时操作同一份数据就会有线程安全的问题

案例

为了更直观的描述线程安全问题,通过下面的案例说明,比如,如下有一个方法打印name中的每个字符,然后打印出name,这时候,启动2个线程A, B , A线程打印name=”zhang_hytc” B线程打印name=”lijia” ,我们看A线程和B线程同时启动,这时候会产生什么?

public class ThaditionalThreadSynchronized {

    public static void main(String[] args) {
        //要想创建 内部类的实例对象,必须要有外部类的实例对象,如何解决?
        new ThaditionalThreadSynchronized().init();
    }

    public void init(){
        final Outputer output = new Outputer();
        new Thread(new Runnable() {
            public void run() {
                while (true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("current-thread1:" + Thread.currentThread().getName());
                    output.output("zhangxiaoxiao");
                }
            }
        }).start();



        new Thread(new Runnable() {
            public void run() {
                while (true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("current-thread2:" + Thread.currentThread().getName());
                    output.output("lijia");
                }

            }
        }).start();

    }


    class Outputer{
        public void output(String name){
            System.out.println("name:" + name);
            for(int i =0; i<name.length();i++){
                System.out.println("character:" + name.charAt(i));
            }
            System.out.println();
        }
    }

}

执行结果:
这里写图片描述

解决

出现上述问题的本质原因在于多个线程同时操作了同一份数据,而在本案例中这个同一份数据其实就是output这个对象,因此可以如下这么写

public class ThaditionalThreadSynchronized {

    public static void main(String[] args) {
        //要想创建 内部类的实例对象,必须要有外部类的实例对象,如何解决? 将逻辑封装到方法中,然后new 出外部类去调用这个方法
        new ThaditionalThreadSynchronized().init();
    }

    public void init(){
        //内部类对象在使用的时候要求创建时类型为final
        final Outputer output = new Outputer();
        new Thread(new Runnable() {
            public void run() {
                while (true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    output.output("zhangxiaoxiao");
                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                while (true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    output.output("lijia-mylove");
                }

            }
        }).start();

    }

    class Outputer{
        String xxx = "";
        public void output(String name){ //因为name多个线程操作的不是同一个对象,所以这样是起不到效果的
            System.out.println("name:" + name);
            synchronized (xxx) {   //因为多个线程是公用output这个对象的,所以xxx是公用的,这样就能起到同步的效果
                for(int i =0; i<name.length();i++){
                    System.out.println("character:" + name.charAt(i));
                }
                System.out.println();
            }


        }
    }

}

执行结果:

这里写图片描述

以上写法从功能上没有问题, 但是仔细想一下,其实两个线程操作的都是output这个对象,因此在该对象中定义xxx=”“其实没必要,因此将采取如下写法:

class Outputer{
        public void output(String name){ //因为name多个线程操作的不是同一个对象,所以synchronized(name)这样是起不到效果的
            System.out.println("name:" + name);
            synchronized (this) {   
                for(int i =0; i<name.length();i++){
                    System.out.println("character:" + name.charAt(i));
                }
                System.out.println();
            }


        }
    }

synchronized

synchronized 保护的不是一小段代码,而是整个方法的内容,这时候,output方法如下修改:

        public synchronized void output2(String name){
            System.out.println("name:" + name);
            for(int i =0; i<name.length();i++){
                System.out.println("character:" + name.charAt(i));
            }
            System.out.println();

        }

在一段代码中尽量只用一个synchronized, 如果方法里面又使用了synchronized,很可能会出现死锁

如果按照线程A调用的是output方法,线程B调用的是output2()方法,那么这两个线程能否互斥? 答案是肯定的,
因为两个线程检查的是同一个锁对象, output2() 这个synchronized方法用的就是this对象,output用的也是this对象,说明他们用的监视器对象是一样的,所以不会有问题

静态synchronized方法

上面output和output2使用的是同一个锁对象,没有问题,那么如果将output2方法设置成static方法呢,比如叫做output3()? 那么这时候output和output3能否同步?
这时候需要修改Outputer这个类,修改成如下静态类

    static class Outputer{
        public void output(String name){ //因为name多个线程操作的不是同一个对象,所以这样是起不到效果的
            System.out.println("name:" + name);
            synchronized (this) {   //因为多个线程是公用output这个对象的,所以xxx是公用的,这样就能起到同步的效果
                for(int i =0; i<name.length();i++){
                    System.out.println("character:" + name.charAt(i));
                }
                System.out.println();
            }
        }

        public synchronized void output2(String name){
            System.out.println("name:" + name);
            for(int i =0; i<name.length();i++){
                System.out.println("character:" + name.charAt(i));
            }
            System.out.println();

        }

        public static synchronized void output3(String name){
            System.out.println("name:" + name);
            for(int i =0; i<name.length();i++){
                System.out.println("character:" + name.charAt(i));
            }
            System.out.println();

        }
    }

执行结果如下:
这里写图片描述

如何让output()和output3()能够互斥?
我们知道静态方法不需要创建类的实例对象,但是字节码对象已经在内存中了,那么如果需要同步的话,那么output方法是不是只要也改成字节码对象就可以了,答案是的
修改如下:
这里写图片描述

执行结果:
这里写图片描述

总结

1.  synchronized(obj) 中obj作用就是保证同一时刻只有一个人能拿到这把锁进行操作,obj可以是任意的对象,但必须保证多个线程操作的是同一个锁对象,否则是自己玩自己的,就会出现线程安全问题

2. synchronized 修饰方法的锁是this对象, synchronized修饰静态方法的锁是字节码对象

3. 创建 内部类的实例对象,必须要有外部类的实例对象, 如何创建外部类的实例对象然后去创建内部类的实例对象? 是将创建 内部类对象封装到方法中, 如上述案例中,init方法,如果将init方法中内容放到main()方法中直接创建是报错的

4. 局部内部类和匿名内部类只能访问局部final变量,所以Outputer对象定义的时候要加上final关键字 

写在最后的话:
多线程并发编程java中是非常重要的一块,一个开发人员能走多远,就看这些核心基础知识的掌握,以上的案例为多年前看张孝祥老师讲解的java多线程技术课程中的案例,在此说明,感谢张孝祥老师
未完,待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值