对象及变量的并发拜访
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」,定期为你分享一些技术干货。