多线程_对象及变量的并发访问

1.synchronized同步方法

1.1 方法内的变量为线程安全

"非线程安全"问题存在于"实例变量"中,如果是方法内部的私有变量,则不存在"非现场安全"问题,即方法内的变量(局部变量)为线程安全
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
我们发现因为num值是方法内的变量,所以即使a线程赋值后,沉睡了2秒,b线程给num赋值,但这两个num并非同一个,所以不会产生覆盖的情况

1.2 实例变量非线程安全

我们将num的位置变动一下
在这里插入图片描述
结果如下:
在这里插入图片描述
首先myThread和myThread2线程操作的是同一个对象,而同一个对象中,成员num只有一份,myThread线程给num赋值后,休眠了,此时myThread2线程重写给num赋值,导致最终显示的结果如上图

本实验是两个线程同时访问一个没有同步的方法
怎么理解同时访问呢?a线程访问方法还出出去的时候,b访问了这个访问,这就是同时访问

我们发现如果两个线程同时操作对象的实例变量,就可能出现"非线程安全"问题,此时我们只要在方法上加上synchronized关键字即可,保证a线程访问方法的时候,b线程不能访问这个方法

1.3 多个对象多个锁

suo
在这里插入图片描述
运行结果如下:
在这里插入图片描述
两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果是以异步的方式运行的,但这种的异步的方式却不会造成"非线程安全"问题,为什么呢?很简单,因为两个线程访问了两个不同的对象,并没有共享数据

关键字synchronized在非静态方法上时,取得的锁是对象锁,也就是this
所以当myThread线程在执行num对象的同步方法时,锁对象是num对象,同理myThread线程的锁对象视num2对象

所以同步方法的前提是:多个线程访问同一个对象的同步方法

总结:
1."非线程安全"问题发生的前提是:多个线程访问同享数据,比如静态成员变量,或者同一个实例对象的成员变量
2.同步方法的使用前提:多个线程访问静态同步方法(锁是类对象),或者访问同一个对象的同步方法

1.4 synchronized方法和锁对象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
执行结果如下:
在这里插入图片描述
从中我们看到,两个线程一同进入了methodA方法,当我们队methodA加了锁以后
在这里插入图片描述
我们要记住,只有共享资源的读写操作才需要同步,不是共享资源的,就没必要同步

我们在已经拥有同步方法methodA的基础上,再加一个非同步的methodB方法
在这里插入图片描述
同时两个自定义线程分别调用不同的方法
a线程的run方法中调用object对象的methodA方法
b线程的run方法中调用object对象的methodb方法
在这里插入图片描述
结果如下:
在这里插入图片描述
虽然A线程先持有了object对象锁,但是B线程完全可以异步调用非同步的方法

如果我们也给methodB方法加上synchronized关键字
运行结果如下:
在这里插入图片描述
总结:
1.A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
2.A线程先持有object对象的Lock锁,B线程如果调用object对象中的synchronized类型的方法,则需要等待,当A线程执行完,才能开始执行,也就是同步

1.5 脏读

发生脏读的情况是:在读取实例变量时,此值已经被其他线程更改过了
也就是说改和读的操作没有同步
在这里插入图片描述
在这里插入图片描述

结果如下:
在这里插入图片描述
我们发现getValue是先于setValue打印的,也就是说在getValue()时,线程还在休眠中,并未运行结束,所以获取到的值不一致

我们之前也做过一个实验,见 2.4 变量实例和线程安全,在那个实验中我们发现,多个线程在调用同一个对象的同一个方法时,如果此方法没加synchronized,会出现脏读的情况,A线程将变量a设值后休眠了,然后B线程将变量a重新设值,此时在A线程中打印a就会出现B线程设置的值

那我们这里是将方法加了synchronized,造成脏读的原因在于a线程在改的时候,main线程去读了,虽然a线程拿到了对象索,但是main线程访问的方法不是同步方法,本质上和上面的问题产生原因一样

解决方法:读写同步,通过给getValue加上同步synchronized关键字
结果如下:
在这里插入图片描述
脏读产生的前提:操作实例变量,不同线程争抢实例变量

1.6 synchronized锁重入

当一个线程得到一个对象锁后,再次请求此对象锁是可以再次得到该对象的锁的,也就说在一个synchronized调用本类的其他synchronized方法时,是永远可以得到的
在这里插入图片描述
结果如下:
在这里插入图片描述
“可重入锁”的概念是:自己可以再次获取自己的内部锁,如1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想获取这个对象的锁时,还是可以获取的,如果不可锁重入,就会造成死锁

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

1.7 出现异常,锁自动释放

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

1.8 同步不具有继承性

同步可以继承
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行结果如下:
在这里插入图片描述
我们发现sub虽然重写了main的ServiceMethod()方法,但同步并没有继承,所以a,b两个线程同时访问了sub的ServiceMethod方法,但是在sub调用父类的ServiceMethod()方法,这个方法是同步的,所以b线程执行完后,a线程才进去执行

我们如果想要同步的话,还需要在子类的方法中添加synchronized关键词

2.synchronized同步语句块

synchronized方法在某些情况下是有弊端的

2.1 synchronized方法的弊端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果显示为
在这里插入图片描述
同时我们打印出beginTime1,beginTime2,endTime1,endTime2
我们发现beginTime1和beginTime2的时间基本是一样的,而endTime1和endTime2相差了3秒

我们用sleep()方法来模拟实际开发时,所要进行的操作,这部分操作是不需要进行读和写的,同时也是因为sleep()方法也被同步了,造成了最终需要6秒来执行完这个程序

其实我们需要同步的是读和写代码

2.2 synchronized同步代码块的使用

当两个并发线程访问同一个对象中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等当前线程执行完才能进入代码块
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
通过时间来看,确实是同步了

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

在这里插入图片描述
运行结果如下:
在这里插入图片描述
通过同步代码块,我们提高了执行的效率

2.4 一半异步,一半同步

不在synchronized代码块中,是异步执行的,在synchronized代码块中是同步执行的

2.5 synchronized代码块间的同步性

当一个线程访问object的一个synchronized (this)同步代码块时,其他线程对同一个object中所有其他synchronized (this)同步代码块的访问将被阻塞,因为synchronized使用的锁是同一个

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

和synchronized方法一样,synchronized(this)也是锁定当前对象
在这里插入图片描述
在这里插入图片描述
结果如下:我们发现是异步打印的
在这里插入图片描述
如果我们修改为
在这里插入图片描述
那么就是同步打印了,在doTask()方法中,全部执行完,才执行other方法

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

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

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

出了使用synchronized(this),java还支持对"任意对象"作为"对象监视器"来实现同步功能,这个"任意对象"大多数是实例变量或者方法的参数,使用方式是synchronized(非this对象)
原理很简单,因为同一个对象中,实例变量只有一个

锁非this对象的优势!
使用synchronized(非this对象)代码块中程序和同步方法是异步的,不与其他锁this方法去争抢this锁,可以大大提高效率

2.8 细化验证三个结论

synchronized(非this对象x)的格式写法是将x对象本身作为"对象监视器",所以有以下三个结论

  • 当多个线程同时执行synchronized(非this对象x){}同步代码块时呈同步效果
  • 当其他线程执行x对象中synchronized方法时呈同步效果
  • 当其他线程执行x对象中synchronized(this)代码块时呈同步效果

1.验证第一个结论
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行结果如下:A线程执行结束,B线程才开始执行,即使用不同的service对象,结果也一样,因为不同的service对象享用同一个obj(可以看做是静态变量)
在这里插入图片描述

2.验证第二个结论
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
3.验证第三个结论
在这里插入图片描述

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

关键字synchronized还可以用在static静态方法上,此时对当前*.java文件的class类进行持锁

类锁和对象锁区别

  • class锁对类的所有实例对象起作用
  • 对象锁只对当前对象起作用

类锁和对象锁不是同一个东西,所以当多个线程同时执行synchronized(class)和synchronized(this)的方法时,是异步的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果如下:
A和B同步:同锁,class锁
A和C异步:不同锁
在这里插入图片描述

验证:class锁对类的所有实例对象起作用
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述

synchronized(class)代码块的作用和synchronized static方法一样
在这里插入图片描述
总结:

  • 类锁和对象锁是不同的锁,是异步执行的
  • 同一个类的实例对象的类锁是同一个(前提是用本类作为类锁)

2.10 数据类型String的常量池特性

synchronized(String)和String联合使用时,要注意常量池带来的一些例外
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
结果显示:一直在打印A线程,这是因为String的两个值,“AA”=“AA”,两个线程有同一个锁,在大多数情况下,synchronized代码块不使用String作为锁对象
在这里插入图片描述

2.11 同步synchronized方法无限等待和解决

同步方法容易造成死循环
在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
a线程在执行method1方法时,进入死循环,线程B永远得不到运行的机会

此时就能使用同步块来解决,这两个静态同步方法,改为普通方法,并在代码中增加synchronized(Object obj),两个方法的同步代码块的锁对象,要求不是同一个对象

2.12 多线程的死锁

java线程死锁,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成,“死锁"是必须避免的,因为这会造成线程的"假死”
在这里插入图片描述
当两个a,b两个线程同时执行,a线程占有lock1,想拿lock2,而b线程占有lock2,想拿lock1,就会造成死锁,即双方互相持有对方想要的锁

2.13 内置类和静态内置类

在这里插入图片描述
在这里插入图片描述
结果如下:
在这里插入图片描述
还有一种是静态内置类:
在这里插入图片描述

2.14 内置类和同步1

测试的条件是内置类中有两个同步方法,但是使用的却是不同的锁,最后的打印结果也将会是异步的
在这里插入图片描述
在这里插入图片描述
打印结果如下:
在这里插入图片描述
由于持有不同的锁,所以打印的结果是无序的

2.15 内置类和同步2

测试同步代码块synchronized(class2)对class2上锁后,其他线程只能以同步的方法调用class2中的静态同步方法

2.16 内置类和同步3

1.内部类和外部类synchronized方法获取synchronized(this)代码块的锁对象不是同一个
2.内部类和外部类static synchronized方法获取synchronized(class)代码块的锁对象不是同一个

2.16 锁对象的改变

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

锁对象在代码运行期间可能会发生变化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
运行结果如下:
在这里插入图片描述
按理说a,b两个线程运行的是同一个对象的同步代码块中代码,应该同步运行
但是a线程在运行时,持有的锁是"123",之后虽然讲锁改为"456",但是a线程还是持有"123"(因为是String类型,不可变),此时b线程运行,持有的锁就是"456"了,两个线程持有的锁不同

去掉Thread.sleep(50)后,就成同步了,因为两个线程一同进入run方法,持有的锁都是"123",虽然将锁改为"456",但是结果还是同步的,因为a和b共同抢的锁是"123"

只要对象不变,即使改变对象的属性,运行的结果还是同步的

猜你喜欢

转载自blog.csdn.net/qq_24099547/article/details/90479768
今日推荐