多线程并发编程(三):多线程同步互斥Synchronized

前言

其实就是针对线程安全问题进行的,最经典的问题就是银行转账的问题,A向B转账的同时,A也在存钱,比如A有1000元,向B转200元,A自己存300元,按道理最后是1100元,如果没有同步机制,那么就可能发生问题,如果在A转账的时候,余额还没有开始减,然后A存钱的时候,拿到的余额是没有减去的,然后A的余额开始减去,但是存钱的线程拿到的余额是没有减去的,那么最后计算赋值可能变成了1000+300=1300了,同样,有可能在存钱的时候还没有计算赋值,另外一个转账线程进来,拿到的值是还没有加起来的,也就是1000元,那么转账线程执行完,1000-200=800,按道理是1100的,所以可能出现钱多了,或者钱少了的问题。

线程安全问题

package test01;

public class TraditionalThreadSychronized {

    class Outputer{

        // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
        public void output(String name){
            int len = name.length();
            for (int i = 0; i < len; i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }
    }

    public void init(){
        // 注意这里要申明为final
        final Outputer out = new Outputer();

        // 线程1:打印zhangsan
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    out.output("zhangsan");
                }
            }

        }).start();

        // 线程2:打印lisi
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    out.output("lisi");
                }
            }

        }).start();
    }

    public static void main(String[] args) {
        new TraditionalThreadSychronized().init();
    }
}

如果不考虑线程安全问题的话,理想结果应该是zhangsan,lisi 循环打印,但是这里没有使用同步机制,输出结果中可以发现,出现了原本不想要的结果:
这里写图片描述
可以发现,打印出来的数据有些有问题。

解决办法: 采用同步机制,使用synchronized

使用同步解决线程安全问题

1、使用当前对象锁

class Outputer{

        // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
        public void output(String name){

            synchronized(this){
                int len = name.length();
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }

        }
    }

2、使用当前对象属性的对象锁

    class Outputer{
        private String xxx = "";

        // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
        public void output(String name){

            synchronized(xxx){
                int len = name.length();
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }

        }
    }

3、使用同步方法

    class Outputer{
        private String xxx = "";

        // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
        public synchronized void output(String name){
            int len = name.length();
            for (int i = 0; i < len; i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }
    }

其实,使用同步方法,就是使用的当前对象的对象锁

输出结果:

这里写图片描述

打印结果中不再出现之前的打印问题,zhangsan,lisi都完整打印出来了,当然如果要实现轮流依次打印那种,之后会给出实现方案的,这里只讨论线程安全机制。

同步之间的互斥问题

第一种情况:同步代码块与同步方法

package test01;

public class TraditionalThreadSychronized {

    class Outputer{

        // 同步代码块形式
        public void output(String name){
            synchronized(this){
                int len = name.length();
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }
        }

        // 同步方法形式
        public synchronized void output2(String name){
            int len = name.length();
            for (int i = 0; i < len; i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }
    }

    public void init(){
        // 注意这里要申明为final
        final Outputer out = new Outputer();

        // 线程1:打印zhangsan
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 调用的是同步代码块形式的方法output
                    out.output("zhangsan");
                }
            }

        }).start();

        // 线程2:打印lisi
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 调用的是同步方法形式的方法output2
                    out.output2("lisi");
                }
            }

        }).start();
    }

    public static void main(String[] args) {
        new TraditionalThreadSychronized().init();
    }
}

结果:不会出现线程安全问题。

分析:第一个线程调用的是同步代码块的方法output,第二个线程调用的是同步方法形式的方法output2,那么结果会怎么样了,会出现线程安全问题吗?答案当然是不会,前面说到了,同步方法其实使用的就是当前对象的对象锁,而同步代码块中用的也是当前对象的对象锁,所以肯定是互斥的,即不能当一个线程拿到了这个对象的对象锁的时候,其他对象不能调用当前对象需要对象锁的方法,说的通俗点就是,线程1进入output方法的时候,拿到了当前对象的对象锁,线程2没有当前对象的对象锁,所以访问不了方法output2,同样线程2拿到之后,线程1也访问不了output方法。

特殊情况:当然如果同步代码块中使用的是其他对象锁,那么就会出现线程安全问题了,因为两个方法使用的不是同一个对象的对象锁。

第二种情况:同步代码块与静态同步方法

package test01;

public class TraditionalThreadSychronized {

    static class Outputer{

        // 同步代码块形式
        public void output(String name){
            synchronized(this){
                int len = name.length();
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }
        }

        // 同步方法形式
        public synchronized void output2(String name){
            int len = name.length();
            for (int i = 0; i < len; i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }

        // 静态同步代码块
        public static synchronized void output3(String name){
            int len = name.length();
            for (int i = 0; i < len; i++) {
                System.out.print(name.charAt(i));
            }
            System.out.println();
        }

    }

    public void init(){
        // 注意这里要申明为final
        final Outputer out = new Outputer();

        // 线程1:打印zhangsan
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 调用的是同步代码块形式的方法output
                    out.output("zhangsan");
                }
            }

        }).start();

        // 线程2:打印lisi
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 调用的是静态同步方法形式的方法output3
                    out.output3("lisi");
                }
            }

        }).start();
    }

    public static void main(String[] args) {
        new TraditionalThreadSychronized().init();
    }
}

这里写图片描述

结果:会出现线程安全问题。
分析:同步代码块使用的是当前对象的对象锁,而静态同步方法使用的是当前静态类class的对象锁。
解决办法:同步代码块中使用当前类class的锁,如下:

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

第三种情况:同步方法与静态同步方法

代码就省略了
结果:会出现线程安全问题
分析:同步方法使用的是当前类的对象的对象锁,而静态同步方法使用的是当前类的class对象的对象锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值