对象及变量的并发访问。

synchronized同步方法

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

方法内的变量为线程安全

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

实例变量非线程安全

  • 用线程访问的对象中如果有多个变量,则运行的结果有可能出现交叉的情况。
  • 如果对象仅有1个实例变量,则有可能出现覆盖的情况。
  • 两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”问题。
  • 在两个线程访问同一个对象中的同步方法时一定是线程安全的。

多个对象多个锁

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

synchronized方法与锁对象

  • 调用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。
  • A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中非synchronized类型的方法。
  • A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

脏读

  • 在赋值时进行了同步,但在取值时有可能出现一些意料不到的意外,这种情况就是脏读(dirtyRead)。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。
  • 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。
  • 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,也就是说username和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。

synchronized锁重入

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

出现异常,锁自动释放

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

同步不具有继承性

synchronized同步语句块

  • 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间。在这样的情况下可以使用synchronized同步语句块来解决。synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。

synchronized方法的弊端

synchronized同步代码块的使用

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

用同步块代码解决同步方法的弊端

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

一半异步,一半同步

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

synchronized代码块间的同步性

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

验证同步synchronized(this)代码块是锁定当前对象的

  • 和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

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

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

            对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。

            同一时间只有一个线程可以执行synchronized同步方法中的代码。

  • synchronized(this)同步代码块

            对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。

            同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

  • synchronized(非 this 对象)格式的作用只有1种,synchronized(非 this 对象x) 同步代码块。

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

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

  • 锁非this对象具有一定的优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。
  • 使用“synchronized(非 this 对象x)同步代码块”格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行。
  • 同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的,这样极易出现“脏读”问题。

细化验证3个结论

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

            当多个线程同时执行synchronized(x){}同步代码块时呈同步结果。

            当其他线程执行x对象中synchronized同步方法时呈同步效果。

            当其他线程执行x对象方法里面的synchronized(this)代码块时页呈现同步效果。

  • 如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

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

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

数据类型String的常量池特性

  • 在大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但他并不放入缓存中。

同步synchronized方法无限等待与解决

多线程的死锁

  • Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中,“死锁”是必须避免的,因为这会造成线程的“假死”。

内置类与静态内置类

  • 在内置类中有两个同步方法,但使用的却是不同的锁,打印的结果也是异步的。
  • 同步代码块synchronized(class2)对class2上锁后,其他线程只能以同步的方式调用class2中的静态同步方法。

锁对象的改变

  • 在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的。
  • 只要对象不变,即使对象的属性被改变,运行的结果还是同步。

volatile关键字

  • 关键字volatile的主要作用是使变量在多个线程间可见。

关键字volatile与死循环

解决同步死循环

  • 关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

解决异步死循环

volitile非原子的特性

  • 关键字volatile虽然增加了实例变量在多个线程之间的可见性,但他却不具备同步性,那么也就不具备原子性。
  • 关键字volatile主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。
  • volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。

使用原子类进行i++操作

  • 一个原子(atomic)类型就是一个原子操作可用的类型,他可以在没有锁的情况下做到线程安全(thread-safe)。

原子类也并不完全安全

  • 原子类在具有有逻辑性的情况下输出结果也具有随机性。

synchronized代码块有volatile同步的功能

  • 关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。他包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

猜你喜欢

转载自blog.csdn.net/en_joker/article/details/80434528