线程的安全与数据同步之synchronized

前序

无论是互联网系统还是企业系统,在追求稳定计算的同时也在追求系统更高的吞吐量,这也对我们开发人员提出了更高的要求,如何开发高效率的程序是每个程序员必须掌握的技能,并发或者并行的的程序并不意味着满足越多的Thread,线程的多少对系统的性能来说是一个抛物线,同时多线程的引入也带来了共享数据的安全隐患!

数据同步

接下来我们用代码说明数据不一致的问题:

public class WinRunnable implements Runnable {
    private int index = 1;
    private final static int MAX = 500;
    @Override
    public void run() {
        while (index <= MAX) {
            System.out.println(Thread.currentThread()+"的号码是:"+(index++));
        }
    }

    public static void main(String[] args) {
        final WinRunnable wr = new WinRunnable();
        Thread wr1 = new Thread(wr,"1号窗口");
        Thread wr2 = new Thread(wr,"2号窗口");
        Thread wr3 = new Thread(wr,"3号窗口");
        Thread wr4 = new Thread(wr,"4号窗口");
        wr1.start();
        wr2.start();
        wr3.start();
        wr4.start();
    }
}

运行以上代码我们发现
1. 某个号码出现多次
2. 号码有时候超过了最大值500

问题分析:
线程的执行是由CPU时间片轮询调度
号码出现多次 线程1在执行index = 88,做了++操作,但是没有写操作,此时CPU中断了线程1的执行权,线程2看到的依然是88此时往后操作线程1和2都输出了89

号码超多最大值 同样在while判断时,线程1进入后,操作了++没写操作,此时线程2看到的依然是499,所以是成立的,当线程1操作完之后已经是500了,线程2继续执行从500++就成了501

synchronized关键字

synchronized提供了一种排他机制,也就是同一时间只能一个线程执行某些操作.
synchronized可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么对该对象的所有读和写都将通过同步的方式进行,具体表现:
synchronized关键字提供了一种锁的机制(每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权),能够确保变量间的互斥访问,从而防止数据不一致问题出现
synchronized关键字包括monitor enter 和 monitor exit两个JVM指令,它能确保任何线程在执行monitor enter之前都将从主内存中读取数据,而不是从缓存,在monitor exit之后,共享变量的之必须刷到主内存中
synchronized的指令严格遵守java happens-before规则,既monitor exit之前必定有一个monitor enter指令

synchronized用法

synchronized可以修饰代码块和方法,不能修饰class类和变量

public class Mutex{
    private final static Object MUTEX = new Object();

    public void accessResource(){
        synchronized (MUTEX) {
            try{
                System.out.println(Thread.currentThread()+"睡眠");
                TimeUnit.SECONDS.sleep(100);

            } catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final Mutex mutex = new Mutex();
        for(int i = 1;i <= 5;i++){
            new Thread(){public void run(){
                mutex.accessResource();
            }}.start();
        }
    }
}

运行代码可以看到
在这里插入图片描述
在这里插入图片描述
只有线程0被执行,其余的全部进入阻塞状态

补充 monitor(对象监视器)

每个对象都与一个monitor相关联,一个monitor的lock锁只能被一个线程同一时间获取,在一个线程尝试获取锁时会有一下几件事情出路:
1.如果monitor计数器为0,就意味着该monitor的lock还没有被获取,某个线程获取之后会立即+1操作,从此该线程就是这个monitor的所有者
2.如果一个线程持有monitor所有权后重入,monitor计数器会继续累加
3.如果monitor已经被一个线程获得,其他线程尝试获取monitor所有权时,线程将进入阻塞状态,知道monitor计数器为0后才再次尝试获取

使用synchronized的注意事项

1.与monitor关联的对象不能为空!
2.synchronized作用域太大
同理人无完人,金无足赤啊,保证了数据安全的前提往往是浪费了一部分性能.在synchronized代码块中所有的线程都是串行执行的,一个线程在执行,其他线程就得阻塞,所以在写代码时一定要缩小synchronized域
3.不同的monitor企图锁同样方法
在同样的synchronized中只能存在一个monitor,不然没有意义
4.多个锁的交叉导致死锁
不要使用synchronized嵌套!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值