对象及变量的并发访问
2.1 synchronized同步方法
首先在这里说明两个概念:线程安全和非线程安全
线程安全:就是获取的实例变量的值经过同步处理不会出现脏读的现象。
非线程安全:就是多个线程对同一个对象中的实例变量进行并发访问时发生脏读的,即取到的数据其实是被更改过的。
2.1.1 实例变量非线程安全
下面我们用代码来说明一下非线程安全,HasSelfPrivateNum.java
代码如下:
public class HasSelfPrivateNum {
private int num = 0;
public void addI(String username) {
try {
if (("a").equals(username)) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num= " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
自定义线程类 MyThread1.java
, MyThread2.java
代码如下:
public class MyThread1 extends Thread{
private HasSelfPrivateNum hasSelfPrivateNum;
public MyThread1(HasSelfPrivateNum hasSelfPrivateNum){
this.hasSelfPrivateNum=hasSelfPrivateNum;
}
@Override
public void run() {
hasSelfPrivateNum.addI("a");
}
}
public class MyThread2 extends Thread{
private HasSelfPrivateNum hasSelfPrivateNum;
public MyThread2 (HasSelfPrivateNum hasSelfPrivateNum){
this.hasSelfPrivateNum=hasSelfPrivateNum;
}
@Override
public void run() {
hasSelfPrivateNum.addI("b");
}
}
创建运行类 Test.java
,代码如下:
public class Test {
public static void main(String[] args) {
HasSelfPrivateNum hasSelfPrivateNum=new HasSelfPrivateNum();
MyThread1 myThread1=new MyThread1(hasSelfPrivateNum);
myThread1.start();
MyThread2 myThread2=new MyThread2(hasSelfPrivateNum);
myThread2.start();
}
}
代码运行运行结果:
a set over
b set over b
num= 200 a
num= 200
两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则会出现 “非线程安全”的问题,那我们如何解决这个问题,只需要在 public void addI(String username)
方法前面加上关键字synchronized
即可 ,更改后方法的代码如下:
public synchronized void addI(String username)
再次运行程序结果如下:
a set over
a num= 100
b set over
b num= 200
所以得出结论,两个线程访问同一个对象中的同步方法时一定是线程安全的。
2.1.2 多个对象多个锁
其他类的代码不变,修改运行类 Test.java
,代码如下:
public class Test {
public static void main(String[] args) {
HasSelfPrivateNum hasSelfPrivateNum1=new HasSelfPrivateNum();
HasSelfPrivateNum hasSelfPrivateNum2=new HasSelfPrivateNum();
MyThread1 myThread1=new MyThread1(hasSelfPrivateNum1);
myThread1.start();
MyThread2 myThread2=new MyThread2(hasSelfPrivateNum2);
myThread2.start();
}
}
运行程序结果如下:
a set over
b set over
b num= 200
a num= 100
示例是两个线程同时访问同一个类的两个不同的实例的同一个方法,效果却是以异步的方式运行的。本示例创建两个HasSelfPrivateNum(业务对象),在系统中产生2个锁,所以运行结果是异步的。打印的效果是先打印 b,然后再打印a.
那么从上面的运行结果来看,虽然在HasSelfPrivateNum.java
中使用synchronized
关键字,但是打印的顺序却不是同步的,是交叉的。那为什么出现这样的结果呢?
因为关键字synchronized
取得的锁是都是对象锁,而不是把一段代码或者是方法当作锁。所以上面的事了中,哪个线程执行带有synchronized
关键字的方法,哪个线程就持有该方法所属对象的锁Lock,其他线程只能呈现等待状态,前提是多个线程访问的是同一个对象。
但如果多个线程访问多个对象,则JVM会创建多个锁。
那我们在不修改运行类 Test.java
的情况下,如何实现线程安全呢?
修改HasSelfPrivateNum.java
代码如下:
public class HasSelfPrivateNum {
private static int num = 0;
public static synchronized void addI(String username) {
try {
if (("a").equals(username)) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num= " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行程序结果如下:
a set over
a num= 100
b set over
b num= 200
从运行的结果来看,实现类同步的效果,上面的实例中将synchronized
关键字加到非静态(static)方法上运行的效果是不一样的, 其实它们的本质是不同的,synchronized
关键字加到static静态方法上是给Class
类上锁,而synchronized
关键字加到非静态(static)方法上是给对象上锁。
结论:
-
synchronized加在静态方法上给对象上锁。 -
synchronized加在非静态方法上给Class类上锁。
2.2 synchronized同步代码块
2.2.1 synchronized(this)同步代码快的使用
当两个并发线程访问同一个对象object中synchronized(this)同步代码块时,一段时间内只能一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
自定义线程类 MyThread1.java
, MyThread2.java
,运行类 Test.java
代码不变,修改HasSelfPrivateNum.java
,代码如下:
public class ObjectService {
public void serviceMethod() {
try {
synchronized (this){
System.out.println("begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("end time= " + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果
a set over
a num= 100
b set over
b num= 200
上述实验执行synchronized (this)同步代码块,实现线程安全。但使用synchronized (this)需要注意的是,当一个线程访问object的一个synchronized (this)同步代码块时,其他的线程对同一个object中所有其他synchronized (this)同步代码块的访问将被阻塞,这样说明synchronized (this)属于使用“对象监听器”,和synchronized关键字加到非静态(static)方法上是给对象上锁是一样的。
举一个示例验证synchronized (this)属于使用“对象监听器”
在HasSelfPrivateNum.java
新增一个方法,那么在HasSelfPrivateNum.java
类中有两个同步的方法,分别是:addI(),update() ,新增代码如下:
public void update(){
synchronized (this) {
try {
System.out.println("同步代码块update");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
修改MyThread2.java
,更改run()方法中调用类的方法,代码如下:
@Override
public void run() {
hasSelfPrivateNum.update();
}
运行类的代码不变,程序运行的结果如下:
a set over
a num= 100
同步代码块update
2.2.2 synchronized(非this)同步代码快的使用
修改HasSelfPrivateNum.java
类,使用Object 类作为对象监听器 ,代码如下:
public class HasSelfPrivateNum {
private int num = 0;
private Object object = new Object();
public void addI(String username) {
//这个对象可以是任何类
synchronized (object) {
try {
if (("a").equals(username)) {
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num= " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
其他类代码不做修改,程序运行结果如下:
a set over
a num= 100
b set over
b num= 200
锁非this对象有一定的优点:如果一个类紫红有很多synchronized的方法,这时虽然能实现同步,但会出现阻塞,所以会影响效率,但弱国使用同步代码块锁时非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,这样大大提高效率。
2.2.3 synchronized(class)代码块
其实静态同步synchronized和synchronized(class)代码块实现的效果是一样。
下面我们来验证synchronized
关键字加到static静态方法上是给Class
类上锁,而synchronized
关键字加到非静态(static)方法上是给对象上锁。
创建类HasSelfPrivateNum.java
,代码如下:`
public class HasSelfPrivateNum {
public synchronized static void addA() {
try {
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addA() ");
Thread.sleep(2000);
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开 addA() ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void addB() {
try {
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addB() ");
Thread.sleep(2000);
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开 addB() ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void addC() {
try {
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addC() ");
Thread.sleep(2000);
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开 addC() ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
自定义线程类 MyThread1.java
, MyThread2.java
,MyThread3.java
代码如下:
public class MyThread1 extends Thread{
@Override
public void run() {
HasSelfPrivateNum.addA();
}
}
public class MyThread2 extends Thread{
@Override
public void run() {
HasSelfPrivateNum.addB();
}
}
public class MyThread3 extends Thread{
private HasSelfPrivateNum hasSelfPrivateNum;
public MyThread3(HasSelfPrivateNum hasSelfPrivateNum){
this.hasSelfPrivateNum=hasSelfPrivateNum;
}
@Override
public void run() {
hasSelfPrivateNum.addC();
}
}
创建运行类 Test.java
,代码如下:
public class Test {
public static void main(String[] args) {
HasSelfPrivateNum hasSelfPrivateNum=new HasSelfPrivateNum();
MyThread1 myThread1=new MyThread1();
myThread1.start();
MyThread2 myThread2=new MyThread2();
myThread2.start();
MyThread3 myThread3=new MyThread3(hasSelfPrivateNum);
myThread3.start();
}
}
程序运行结果:
线程的名称:Thread-0 在 1618653226240 进入 addA()
线程的名称:Thread-2 在 1618653226241 进入 addC()
线程的名称:Thread-2 在 1618653228245 离开 addC()
线程的名称:Thread-0 在 1618653228245 离开 addA()
线程的名称:Thread-1 在 1618653228246 进入 addB()
线程的名称:Thread-1 在 1618653230247 离开 addB()
出现异步的原因是静态方法 addA()
,addB()
持有一个Class锁,非静态addC()
持有一个对象锁,而Class锁可以对类的所有实例起作用。
下面我们验证一下静态同步synchronized和synchronized(class)代码块实现的效果是一样
修改上述HasSelfPrivateNum.java
,addC()方法的代码,其他类不做修改:`
public void addC() {
synchronized (HasSelfPrivateNum.class){
try {
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入 addC() ");
Thread.sleep(2000);
System.out.println("线程的名称: " + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开 addC() ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果:
线程的名称:Thread-0 在 1618653359853 进入 addA()
线程的名称:Thread-0 在 1618653361857 离开 addA()
线程的名称:Thread-2 在 1618653361857 进入 addC()
线程的名称:Thread-2 在 1618653363859 离开 addC()
线程的名称:Thread-1 在 1618653363859 进入 addB()
线程的名称:Thread-1 在 1618653365863 离开 addB()
从程序的运行结果来看,同步synchronized(class)代码块的作用其实和synchronized的静态方法是一样。
总结:
synchronized同步方法: 静态同步方法 和非静态同步方法,
synchronized同步代码块 :synchronized(this),synchronized(非this),synchronized(class)
看到这里今天的分享就结束了,如果觉得这篇文章还不错,来个分享、点赞、在看三连吧,让更多的人也看到~~
欢迎关注个人公众号 「JavaClub」,定期为你分享一些技术干货。
原文始发于微信公众号(阿福聊编程):对象及变量的并发访问
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/105855.html