本章知识点:
- synchronized对象监视器为Object时的使用。
- synchronized对象监视器为Class时的使用。
- 非线程安全是如何出现的。
- 关键字volatile的主要作用。
- 关键字volatile与synchronized的区别及使用情况。
2.1 synchronized同步方法
2.1.1 方法内的变量为线程安全
方法内的变量不存在线程安全问题,永远都是线程安全的。
2.1.2 实例变量非线程安全
在两个线程访问同一个对象中的同步方法时,一定是线程安全的。
2.1.3 多个对象多个锁
public class MainTest02 {
public static void main(String[] args) {
HasSelfPrivateNum ha01 = new HasSelfPrivateNum();
HasSelfPrivateNum ha02 = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(ha01);
threadA.start();
ThreadB threadB = new ThreadB(ha02);
threadB.start();
}
}
关键字synchronized取得的锁都是对象锁,而不是把一段代码或者方法当做锁,所以哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属的对象锁Lock,那么其他线程只能呈现等待状态,前提是多个线程访问的是同一个对象。
2.1.4 synchronized方法与锁对象
1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2)A线程先持有object对象的Lock锁,B线程如果在这个时候调用object对象中的synchronized类型的方法,则需要等待,也是同步。
2.1.5 synchronized锁重入
关键字synchronized拥有锁重入的功能,就是在使用synchronized的时候,当一个线程得到一个对象锁后,再一次请求此对象锁的时候是可以再次得到该对象的锁的,也证明在一个synchronized方法内部调用本类的其他synchronized方法时候,是永远可以得到锁的。
可重入锁也支持在父子类继承的环境中。也就是说当存在父子类继承关系时候,子类是完全可以通过可重入锁调用父类的同步方法的。
2.1.6 出现异常,锁自动释放
当一个线程执行一段代码出现异常时候,其所持有的锁会自动释放。
2.1.7 同步不具有继承性
同步不可以继承。也就是说子类重写父类synchronized方法时候,要实现同步,还得要在子类方法前面加上synchronized关键字。
2.2 synchronized同步语句块
synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。
2.2.1 synchronized方法的弊端
直接对一个方法进行加锁,其实在时间消耗上会有很大的问题。
2.2.2 synchronized同步代码块的使用
当两个并发线程访问同一个对象object中的synchronized(this) 同步代码块时,一段时间内只能有一个线程被执行,另一个线程处于等待状态。
package demo03;
public class ObjectService {
public void serviceMethod(){
try {
synchronized (this){
System.out.println(Thread.currentThread().getName()+" begin time"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" end time"+System.currentTimeMillis());
}
}catch (Exception ex){
ex.printStackTrace();
}
}
}
package demo03;
public class ThreadA extends Thread{
private ObjectService objectService;
public ThreadA(ObjectService objectService){
this.objectService=objectService;
}
@Override
public void run() {
super.run();
objectService.serviceMethod();
}
}
package demo03;
public class ThreadB extends Thread{
private ObjectService objectService;
public ThreadB(ObjectService objectService){
this.objectService=objectService;
}
@Override
public void run() {
super.run();
objectService.serviceMethod();
}
}
package demo03;
public class Run {
public static void main(String[] args) {
ObjectService objectService = new ObjectService();
ThreadA a= new ThreadA(objectService);
a.setName("A");
a.start();
ThreadB b = new ThreadB(objectService);
b.setName("B");
b.start();
}
}
2.2.3 一半异步,一半同步
不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
2.2.4 synchronized代码块间的同步性
在使用同步synchronized(this)代码块时候要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他的synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的对象监视器是一个。
要注意:
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
2.2.5 将任意对象作为对象监视器
这个任意对象大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。
锁非this对象有一定的优点:
如果在一个类中有多个synchronized方法,这个时候虽然能实现同步,但是方法之间会受到阻塞,影响效率,但是如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的。不与其他锁this同步方法争抢this锁,则可以大大提高运行效率。
但是要注意:
使用synchronized(非this对象X)同步代码块格式进行操作时,对象监视器必须是同一个对象,如果不是的话,运行的效果就是异步了。
2.2.6 细化验证三个结论
(1)当多个线程同时执行synchronized(X){}同步代码块时呈同步效果。
(2)当其他线程执行x对象中synchronized同步方法时呈同步效果。
(3)当其他线程执行x对象方法里面的synchronized(this)代码块也呈现同步效果。
2.2.7 静态同步synchronized方法与synchronized(class)代码块
关键字synchronized还可以应用在静态方法上,表示对当前的java文件对应的Class类进行持锁。
package demo04;
public class Service {
synchronized public static void printA(){
System.out.println("线程名称A"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名称A"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
}
synchronized public static void printB(){
System.out.println("线程名称B"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名称B"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
}
}
package demo04;
public class ThreadA extends Thread{
@Override
public void run() {
super.run();
Service.printA();
}
}
package demo04;
public class ThreadB extends Thread{
@Override
public void run() {
super.run();
Service.printB();
}
}
package demo04;
public class Run {
public static void main(String[] args) {
ThreadA threadA = new ThreadA();
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB();
threadB.setName("B");
threadB.start();
}
}
打印结果
线程名称AA时间=1577800514282
线程名称AA时间=1577800517282
线程名称BB时间=1577800517282
线程名称BB时间=1577800520282
主要是看在两个线程方法中都是直接通过类执行静态方法,并非是创建了对象执行,如果打印是同步的,那就证明确实是对当前的Class类进行持锁。
要注意:
对象锁只是对当前对象进行持锁,但是Class锁是对类的所有对象实例起作用。
示例如下:
package demo05;
public class Service05 {
public void print() {
synchronized (Service05.class){
System.out.println("线程"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package demo05;
public class ThreadA extends Thread{
private Service05 service05;
public ThreadA(Service05 service05){
this.service05=service05;
}
@Override
public void run() {
super.run();
service05.print();
}
}
package demo05;
public class ThreadB extends Thread{
private Service05 service05;
public ThreadB(Service05 service05){
this.service05=service05;
}
@Override
public void run() {
super.run();
service05.print();
}
}
package demo05;
public class Run05 {
public static void main(String[] args) {
Service05 service05 = new Service05();
ThreadA threadA = new ThreadA(service05);
threadA.setName("A");
threadA.start();
ThreadB threadB = new ThreadB(service05);
threadB.setName("B");
threadB.start();
}
}
线程A
线程B
2.2.8 数据类型String的常量池特性
在JVM中具有String常量缓存池的功能。因此在大多数情况下,同步synchronized 代码块都不使用String作为锁对象。
2.2.9 多线程的死锁
在设计程序的时候一定要避免双方相互持有对方的锁,只要出现互相等待对方释放锁就有可能出现死锁。
2.2.10 锁对象的改变
只要锁对象不变,即使对象的属性被改变,运行的结果还是同步。
2.3 volatile关键字
volatile的作用是使变量在多个线程间可见,是因为当线程访问变量的时候,强制性从公共堆栈中进行取值。
package demo06;
public class Service06 extends Thread{
volatile private boolean isRunning = true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
System.out.println("线程被开启");
while(isRunning){
}
System.out.println("线程被停止了");
}
}
package demo06;
public class Run06 {
public static void main(String[] args) {
Service06 service06 = new Service06();
service06.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service06.setRunning(false);
System.out.println("已经赋值false");
}
}
打印结果:
线程被开启
已经赋值false
线程被停止了
注意:
- volatile最致命的问题是不支持原子性。
- synchronized和volatile的比较:
(1) 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。
(2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
(3)volatile能保证数据的可见性,但是不能保证原子性,而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。
(4)关键字volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一段代码或者方法,它包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。