synchronized关键字使用总结

文章目录

  • 对象及变量的并发访问
    • synchronized同步方法
      • 方法内的变量为线程安全
      • 实例变量非线程安全
      • 多个对象多个锁
      • synchronized方法与锁对象
      • 脏读
      • synchronized锁重入
      • 出现异常,锁自动释放
      • 同步不具有继承性
    • synchronized同步语句块
      • synchronized方法的弊端
      • synchronized同步代码块的使用
      • synchronized代码块间的同步性
      • 将任意对象作为对象监视器
      • 细化结论
      • 静态同步synchronized方法与synchronized(class)代码块
      • 数据类型String的常量池特性
      • 内部类和静态内部类
      • 锁对象的改变

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

对象及变量的并发访问

以下整理了通用结论。

synchronized同步方法

方法内的变量为线程安全

方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。

实例变量非线程安全

如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。

在两个线程访问同一个对象中的同步方法时一定是线程安全的。

同步的单词为synchronized,异步的单词为asynchronized

多个对象多个锁

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象
但如果多个线程访问多个对象,则JVM会创建多个锁。

调用关键字synchronized声明的方法一定是排队运行的。

synchronized方法与锁对象

只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。

测试得出结论:
1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2)A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

脏读

虽然在赋值时进行了同步,但在取值时有可能出现一些意想不到的意外,这种情况就是脏读(dirtyRead)。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。

不仅要知道脏读是通过synchronized关键字解决的,还要知道如下内容:

1)当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。
2)当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。

synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

可重入锁也支持在父子类继承的环境中。
当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

出现异常,锁自动释放

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

例如:线程a出现异常并释放锁,线程b进入方法正常打印,实验的结论就是出现异常的锁被自动释放了。

同步不具有继承性

同步不能继承,所以还得在子类的方法中添加synchronized关键字

synchronized同步语句块

synchronized方法的弊端

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间。

synchronized同步代码块的使用

方法中使用下面方式来同步代码块:

synchronized(this){
	// 业务代码
	...
}
  • 当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

  • 当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。

  • 不在synchronized块中就是异步执行,在synchronized块中就是同步执行。

synchronized代码块间的同步性

在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。

和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。也就是说,如果这两种混合使用,也是同步执行的。

将任意对象作为对象监视器

多个线程调用同一个对象中的不同名称的synchronized同步方法synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的,阻塞的。

这说明synchronized同步方法或synchronized(this)同步代码块分别有两种作用:

  • synchronized同步方法
    1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
    2)同一时间只有一个线程可以执行synchronized同步方法中的代码。
  • synchronized(this)同步代码块
    1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
    2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

其实Java还支持对“任意对象”作为“对象监视器”来实现同步的功能。这个“任意对象”大多数是实例变量及方法的参数,使用格式为:synchronized(非this对象)

根据前面对synchronized(this)同步代码块的作用总结可知,synchronized(非this对象)格式的作用只有1种:synchronized(非this对象x)同步代码块

  1. 在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。
  2. 当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象x)同步代码块中的代码。

锁非this对象具有一定的优点
如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this对象)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。

使用“synchronized(非this对象x)同步代码块”格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行。

细化结论

synchronized(非this对象x)”格式的写法:是将x对象本身作为“对象监视器” ,这样就可以得出以下3个结论:

  1. 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
  2. 当其他线程执行x对象中synchronized同步方法时呈同步效果。
  3. 当其他线程执行x对象方法里面的synchronized(this)代码块时也呈现同步效果。

但需要注意:如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

静态同步synchronized方法与synchronized(class)代码块

关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前的*.java文件对应的Class类进行持锁。

synchronized关键字加到static静态方法上是给Class类上锁,Class锁可以对类的所有对象实例起作用(虽然是不同对象,但静态的同步方法还是同步运行);
而synchronized关键字加到非static静态方法上是给对象上锁。

同步synchronized(class)代码块 写法:

synchronized(A.class){
	// 业务代码
	...
}

同步synchronized(class)代码块的作用其实和synchronized static方法的作用一样。

数据类型String的常量池特性

在JVM中具有String常量池缓存的功能。
案例:因为String的两个值都是AA,两个线程持有相同的锁,所以造成线程B不能执行。

这就是String常量池所带来的问题。
因此在大多数的情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如newObject()实例化一个Object对象,但它并不放入缓存中。

内部类和静态内部类

也适用于上面的结论。

锁对象的改变

在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的。

还需要提示一下,只要对象不变,既使对象的属性被改变,运行的结果还是同步。

你可能感兴趣的:(Java多线程,java,开发语言)