1、synchronized同步方法:
"非线程安全"其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",
也就取到的数据其实是被更改过的。
"线程安全"就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
1> 方法内的变量为线程安全
"非线程安全"问题存在于实例变量中,如果是方法内部的私有变量,则不存在"非线程安全"问题。
方法中的变量不存在非线程安全问题,永远都是线程安全的。方法内部的变量是私有的特性造成的。
2> 实例变量非线程安全
如果多个线程共同访问1个对象中的实例变量,则由可能出现"非线程安全",变量值会出现覆盖的情况。
//这时只需要在方法前加上synchronized关键字即可。
//两个线程访问同一个对象中的同步方法时一定是线程安全的
3> 多个对象多个锁
//关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁。
同步的单词为synchronized,异步的单词为asynchronized。
4> synchronized方法与锁对象
调用关键字synchronized声明的方法一定是排队进行的,只有共享资源的读写访问才需要同步。
A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中非synchronized类型的方法。
A线程先持有object对象的Lock锁,B线程如果再这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。
5> 脏读
多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字来实现同步。
虽然在赋值时进行了同步,但在取值时可能发生意想不到的意外,这种情况就是脏读(dirty Read)。
6> synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时
是可以再次得到该对象的锁的。这也证明在synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
//自己可以再次获取自己的内部锁。
7> 出现异常,锁自动释放
当个一个线程执行的代码出现异常时,其所持有的锁会自动释放
8> 同步不具有继承性
同步不能继承,所以还的在子类的方法中添加synchronized关键字。
2、synchronized同步语句块
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用方法执行一个长时间的任务,那么B线程必须要等待很长的时间,
在这种情况下可以使用synchronized同步语句块来解决。
1> synchronized方法的弊端:
等待耗时比较长。
2> synchronized同步代码块的使用
当一个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程
执行完这个代码块以后才能执行该代码块。
3> 用同步代码块解决同步方法的弊端
当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。
4> 一半异步,一半同步
5> synchronized代码块间的同步性:
在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问Object的一个synchronized(this)同步代码块时,
其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的"对象监视器"是一个。
6> 验证同步synchronized(this)代码块时锁定当前对象的
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
7> 将任意对象作为对象监视器:
多个线程调用同一个对象的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按照顺序执行,也就是同步的,阻塞的的。
a> synchronized同步方法:
*对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
*同一时间只有一个线程可以执行synchronized同步方法中的代码。
b> synchronized(this)同步代码块:
*对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
*同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
8> 细化验证过3个结论:
synchronizd(非this对相关x)格式的写法是将x对象本身作为"对象监视器"。
a> 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
b> 当其他线程执行x对象中的synchronized同步方法时呈同步效果。
c> 当其他线程执行x对象方法里的synchronized(this)代码块时也呈现同步效果。
9> 静态同步synchronized方法synchronized(class)代码块
关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当的*.java文件的对应的Class类进行持锁。
10> 数据类型String的常量池特性:
大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()
实例化一个Object对象,但它并不放入缓存中。
11> 同步synchronized方法无限等待与解决:
12> 多线程的死锁:
不同的线程都在等待根本不可能被释放锁,从而导致所有的任务都无法继续完成。
13> 内置类与静态内置类
14> 内置类与同步:实验1
15> 内置类与同步:实验2
16> 锁对象的改变
在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,
则这些线程之间就是同步的,如果分别获得锁对象,这些线程就是异步的。
synchronized关键字最主要的三种使用方式的总结:
- 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能!
3、volatile关键字:
volatile的主要作用是使变量在多个线程间可见。
1> 关键字volatile与死循环
运用多线程技术可以解决。
2> 解决同步死循环
实现了Runnable后的车需运行在-server服务器模式中的64bit的JVM上时,会出现死循环。
解决的办法是使用volatile关键字。
//关键字volatile的作用是强制从公共堆栈中取出变量的值,而不是从线程私有数据栈中取出变量的值。
3> 解决异步死循环
是什么原因造成将JVM设置为-server时就出现死循环呢?
在启动RunThread.java线程时,如变量private boolean isRunning=true;存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程运行的效率,
线程一直在私有堆栈中取得isRunning的值是true。而代码thread.setRunning(false);虽然被执行,更新的却是公共堆栈中的isRunning变量值false,
所以一直就是死循环的状态。
其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用volatile关键字了,
它主要作用就是当线程访问isRunning这个变量时,强制性从公共堆栈中进行取值。
使用volatile关键字增加了实例变量在多个线程之间的可见性。但volatile最致命的缺点是不支持原子性。
synchronized和volatile的比较:
a> 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定要比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,
以及代码块。随着jdk新版本的发布,synchronized关键字在执行效率上得到很多提升,在开发中主要使用synchronized。
b> 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
c> volatile能保证数据的可见性,但不能保证数据的原子性。而synchronized可以保证原子性,也可以间接保证
可见性,因为它会将私有内存和公共内存中的数据做同步。
d> 关键字volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
4> volatile非原子的特性
关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。
关键字volatile主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。
关键字volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。
但在这里需要注意的是:如果修改实例变量中的数据,比如i++,也就是i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全的。可以用synchronized解决。
volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。
volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一实例变量还是需要加锁同步。
5> 使用原子类进行i++操作
除了在i++操作时使用synchronized关键字实现同步外,还可以使用AtomicInteger原子类进行实现。
原子操作时不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(automic)类型就是一个原子操作可用的类型,
它可以在没有锁的情况下做到线程安全)(thread-safe)
6> 原子类也并不完全安全
变量调用的addAndGet()方法是原子的,但方法和方法之间的调用却不是原子的。解决这样的问题必须要用同步。
7> synchronized代码块有volatile同步的功能
关键字synchronized可以使多个线程访问同一个资源具有同步性,而且他还具有将工作内存中国的私有
变量与公共内存中的变量同步的功能。
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某一个代码块。它包含两个特征:互斥性和可见性。
同步synchronized不仅可以解决一个线程看到的对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,
都可以看到又一个锁保护之前所有的修改结果。