Java多线程——synchronized

什么是线程安全?

“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。而“线程安全”就是已获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

非线程变量”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。

两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”问题。

synchronized同步方法(synchronized关键字修饰方法)

每个对象有一个锁
如果两个线程分别访问同一个类的两个不同对象的相同名称的同步方法,效果却是以异步的方式运行的。因为多个对象会产生多个锁。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以同步方法的前提是多个线程访问的是同一个对象。

解决脏读
发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了对象锁,所以其他线程只能等待A线程执行完毕才可以调用synchronized同步方法,但是可以随意调用非synchronized同步方法。
所以修改变量的方法和读取变量的方法都加入synchronized关键字,就能避免脏读。

synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁时,再次请求此对象锁时是可以再次获得该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象还没有被释放,当其再次想要获得这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
子类可以继承父类的同步方法,也是“可重入锁”。

出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

同步不具有继承性
如果父类有一个同步方法X,子类继承并重写(覆盖)了X方法,但是没有添加synchronized关键字,那么子类中的X方法就不是同步方法。加上synchronized关键字即可成为同步方法。

synchronized同步语句块(synchronized(x){ })

synchronized(this){ }
和synchronized同步方法类似,取得的是该对象的对象锁。

synchronized(x){ }
x是任意对象,取得的是任意一个对象的锁。
优点:
比方说,一个类People里面有两个成员变量a和b,有changeA()和changeB()两个方法,分别用来修改a和b。现在要做的是使这个类变成线程安全的。
方法1:
使用synchronized同步方法(也就是在两个方法中添加synchronized关键字),这样虽然可以实现线程安全,但是当线程1在使用People类的实例化对象people的changeA()方法时,线程2不能调用people的changeB()方法,因为people的对象锁已经被线程1占用了。但是变量a和变量b没关系呀,我不想让这两个变量之间也要同步!!
现在发现了问题,虽然实现了线程安全,但矫枉过正了,导致效率很低。解决方法是方法2.
方法2:
使用synchronized(x)同步语句块,在changeA()方法中使用synchronized(a){ }语句块,在changeB()方法中使用synchronized(b){ }语句块,分别对该对象的变量a和b上锁。这样两个线程分别调用people对象的这两个方法会是异步的。

synchronized static同步方法 synchronized(ClassName.class)

这两个都是取得class锁,而不是对象锁。这两个锁互不影响。

不同对象的对象锁互不影响,是异步的。

不同对象的class锁是同一个class锁,是同步的。

特殊情况:锁String对象

众所周知,在JVM中具有String常量池缓存的功能,所以下面程序运行结果为true。

    public static void main(String[] args){
        String s1 = "a";
        String s2 = "a";
        System.out.println(s1 == s2);
    }
}

解释一下:
JVM的String常量池先在内存中新建了一个"a",然后把内存地址给了s1。
然后又把"a"的内存地址给了s2,也就是s1和s2引用的内存中的同一个"a"。
==是判断String对象的内存地址是否相同,所以很显然相同。
所以synchronized(x)中的x一般不用String对象,因为会带来一些意料之外的错误。
比如说,你新建了两个String对象,并取同样的值,并在分别对其上锁,你以为你锁的是两个对象,其实锁的是一个对象。因为这两个String对象的内存地址是一样的,其实就是一个对象,相当于一个对象的两个别名而已。

线程死锁

线程死锁是一个经典的多线程问题,导致死锁的原因是不同的线程都在等待根本不可能能被释放的锁,从而导致所有的任务都无法继续完成。
比如,People类有两个锁lock1和lock2,有两个方法method1和method2,method1要先占用lock1,然后占用lock2。method2要先占用lock2,再占用lock1。现在,线程1调用method1,占用了lock1;线程2调用method2,占用了lock2。然后,就没有然后了…线程1在等待lock2,线程2在等待lock1,他们没有完成自己的任务就不会释放已经占有的锁,所以就这样一直等待下去,形成死锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值