java两个线程读取变量_Java多线程编程(二)对象及变量的并发访问

一、synchronized同步方法

1.方法内的变量为线程安全

“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。

示例:由于HasSelfPrivateNum类中的addI(String username)方法的num变量是方法内部的变量,而方法内部的变量是私有的,所以两个线程在分开调用时不会出现非线程安全问题。

packageservice;public classHasSelfPrivateNum {public voidaddI(String username) {try{int num = 0;if (username.equals("a")) {

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();

}

}

}

packageextthread;importservice.HasSelfPrivateNum;public class ThreadA extendsThread {privateHasSelfPrivateNum numRef;publicThreadA(HasSelfPrivateNum numRef) {super();this.numRef =numRef;

}

@Overridepublic voidrun() {super.run();

numRef.addI("a");

}

}

packageextthread;importservice.HasSelfPrivateNum;public class ThreadB extendsThread {privateHasSelfPrivateNum numRef;publicThreadB(HasSelfPrivateNum numRef) {super();this.numRef =numRef;

}

@Overridepublic voidrun() {super.run();

numRef.addI("b");

}

}

packagetest;importservice.HasSelfPrivateNum;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

HasSelfPrivateNum numRef= newHasSelfPrivateNum();

ThreadA athread= newThreadA(numRef);

athread.start();

ThreadB bthread= newThreadB(numRef);

bthread.start();

}

}

a set over!

b set over!

b num=200

a num=100

2.实例变量非线程安全

如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。

如果访问的对象有多个实例变量,则有可能出现交叉的情况。

如果访问的对象仅有1个实例变量,则有可能出现覆盖的情况。

示例1:修改1中的示例的HasSelfPrivateNum类,使得num变量置于addI()方法外,其他保持不变。从输出结果可以看出,过程应该是,首先a线程把num值置为100,然后休眠,在休眠的过程中,b线程把num值置为200,然后a线程再输出num值,就导致了a线程输出的num值也是200。

packageservice;public classHasSelfPrivateNum {private int num = 0;public voidaddI(String username) {try{if (username.equals("a")) {

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!

b set over!

b num=200

a num=200

示例2:修改上例,在方法声明上加上synchronized关键字。从输出结果可以看出,再输出a set over!后等待了2秒才输出第二行,这也就是同步访问,先打印出a,然后打印出b。在两个线程访问同一个对象中的同步方法时一定是线程安全的。

a set over!

a num=100

b set over!

b num=200

3.多个对象多个锁

示例:线程A和B不变,addI方法同步,main方法中创建了两个HasSelfPrivateNum类的对象。从输出结果可以看出,两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式运行得。由于创建了2个业务对象,在系统中产生出2个锁,所以是异步的,打印的效果就是先打印b然后是a。

这里令人疑惑的是,addI方法明明使用了synchronized关键字,为什么还是异步交叉打印的呢?原因是,关键字synchronized取得的锁都是对象锁,而不是 把一段代码或方法当作锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前期是多个线程访问的是同一个对象。但如果多个线程访问多个对象,则虚拟机会创建多个锁。

packageservice;public classHasSelfPrivateNum {private int num = 0;synchronized public voidaddI(String username) {try{if (username.equals("a")) {

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();

}

}

}

packagetest;importservice.HasSelfPrivateNum;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

HasSelfPrivateNum numRef1= newHasSelfPrivateNum();

HasSelfPrivateNum numRef2= newHasSelfPrivateNum();

ThreadA athread= newThreadA(numRef1);

athread.start();

ThreadB bthread= newThreadB(numRef2);

bthread.start();

}

}

a set over!

b set over!

b num=200

a num=100

4.synchronized方法与锁对象

示例1:证明线程锁的是对象。方法不加synchronized关键字。前两行一起打印,后两行一起打印,中间间隔5秒。

packageextobject;public classMyObject {public voidmethodA() {try{

System.out.println("begin methodA threadName="+Thread.currentThread().getName());

Thread.sleep(5000);

System.out.println("end");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageextthread;importextobject.MyObject;public class ThreadA extendsThread {privateMyObject object;publicThreadA(MyObject object) {super();this.object =object;

}

@Overridepublic voidrun() {super.run();

object.methodA();

}

}

packageextthread;importextobject.MyObject;public class ThreadB extendsThread {privateMyObject object;publicThreadB(MyObject object) {super();this.object =object;

}

@Overridepublic voidrun() {super.run();

object.methodA();

}

}

packagetest.run;importextobject.MyObject;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

MyObject object= newMyObject();

ThreadA a= newThreadA(object);

a.setName("A");

ThreadB b= newThreadB(object);

b.setName("B");

a.start();

b.start();

}

}

begin methodA threadName=A

begin methodA threadName=B

end

end

示例2:证明线程锁的是对象。方法加synchronized关键字。第一行与第二行时间间隔5秒,第三行与第四行时间间隔5秒。说明调用synchronized关键字声明的方法一定是排队运行的,另外,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。

begin methodA threadName=A

end

begin methodA threadName=B

end

示例3:两个线程访问同一个对象的两个同步和不同步的方法。两个线程分别调用两种方法,方法b不加synchronized关键字。从输出时间可以看出,一共用了5秒,原因是虽然A线程先持有了object对象的锁,但B线程完全可以异步调用非synchronized类型的方法。

package extobject;

public class MyObject {

synchronized public void methodA() {

try {

System.out.println("begin methodA threadName="+ Thread.currentThread().getName()

+ " begin time="+ System.currentTimeMillis());

Thread.sleep(5000);

System.out.println("methodA end endTime=" + System.currentTimeMillis());

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public void methodB() {

try {

System.out.println("begin methodB threadName="+ Thread.currentThread().getName()

+ " begin time="+ System.currentTimeMillis());

Thread.sleep(5000);

System.out.println("methodB end endTime=" + System.currentTimeMillis());

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

begin methodA threadName=A begin time=1525311747307

begin methodB threadName=B begin time=1525311747307

methodB end endTime=1525311752307

methodA end endTime=1525311752307

示例4:两个线程访问同一个对象的两个同步的方法。两个线程分别调用两种方法,方法b加synchronized关键字。从输出时间可以看出,先打印a然后打印b,一共花了10秒的时间。A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法,则需要等待,也就是同步。

begin methodA threadName=A begin time=1525311629523

methodA end endTime=1525311634524

begin methodB threadName=B begin time=1525311634524

methodB end endTime=1525311639524

5.脏读

脏读就是在读取实例变量时,要读取的值已经被其他线程更改过了。

示例1:getValue()方法不加synchronized关键字。从结果可以看出,在setValue()方法中,首先修改了username,然后休眠5秒,在休眠的过程中getValue()方法已经执行完成了,就造成了只修改名字没有修改密码的情况

packageextthread;importentity.PublicVar;public class ThreadA extendsThread {privatePublicVar publicVar;publicThreadA(PublicVar publicVar) {super();this.publicVar =publicVar;

}

@Overridepublic voidrun() {super.run();

publicVar.setValue("B", "BB");

}

}

packageentity;public classPublicVar {public String username = "A";public String password = "AA";synchronized public voidsetValue(String username, String password) {try{this.username =username;

Thread.sleep(5000);this.password =password;

System.out.println("setValue method thread name="

+Thread.currentThread().getName()+ " username="+username+ " password=" +password);

}catch(InterruptedException e) {

e.printStackTrace();

}

}public voidgetValue() {

System.out.println("getValue method thread name="+Thread.currentThread().getName()+ " username=" +username+ " password=" +password);

}

}

packagetest;importentity.PublicVar;importextthread.ThreadA;public classTest {public static voidmain(String[] args) {try{

PublicVar publicVarRef= newPublicVar();

ThreadA thread= newThreadA(publicVarRef);

thread.start();

Thread.sleep(200);//打印结果受此值大小影响

publicVarRef.getValue();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

getValue method thread name=main username=B password=AA

setValue method thread name=Thread-0 username=B password=BB

示例2:getValue()方法加synchronized关键字。setValue()方法和getValue()方法被依次执行,就不存在脏读的情况。总结一下,脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。

setValue method thread name=Thread-0 username=B password=BB

getValue method thread name=main username=B password=BB

6.synchronized锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求次对象锁时是可以再次得到该对象的锁的。这也证明了在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

示例1:“可重入锁”的意思是,自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获得这个对象的锁的时候还是可以取得的,如果不可锁重入的话,就会造成死锁。示例中的多次获取自己的内部锁就是这个意思。

packageextthread;importmyservice.Service;public class MyThread extendsThread {

@Overridepublic voidrun() {

Service service= newService();

service.service1();

}

}

packagemyservice;public classService {synchronized public voidservice1() {

System.out.println("service1");

service2();

}synchronized public voidservice2() {

System.out.println("service2");

service3();

}synchronized public voidservice3() {

System.out.println("service3");

}

}

packagetest;importextthread.MyThread;public classRun {public static voidmain(String[] args) {

MyThread t= newMyThread();

t.start();

}

}

service1

service2

service3

示例2:可重入锁也支持在父子类继承的环境中。Sub类是Main类的子类,当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法。

packageextthread;importmyservice.Sub;public class MyThread extendsThread {

@Overridepublic voidrun() {

Sub sub= newSub();

sub.operateISubMethod();

}

}

packagemyservice;public classMain {public int i = 10;synchronized public voidoperateIMainMethod() {try{

i--;

System.out.println("main print i=" +i);

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

}

packagemyservice;public class Sub extendsMain {synchronized public voidoperateISubMethod() {try{while (i > 0) {

i--;

System.out.println("sub print i=" +i);

Thread.sleep(100);this.operateIMainMethod();

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packagetest;importextthread.MyThread;public classRun {public static voidmain(String[] args) {

MyThread t= newMyThread();

t.start();

}

}

sub print i=9

main print i=8

sub print i=7

main print i=6

sub print i=5

main print i=4

sub print i=3

main print i=2

sub print i=1

main print i=0

7.出现异常,锁自动释放

示例:当一个线程执行的代码出现异常时,其所持有的锁会自动释放。线程a出现异常并释放锁,线程b进入方法正常打印,出现异常的锁被自动释放了。

packageextthread;importservice.Service;public class ThreadA extendsThread {privateService service;publicThreadA(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.testMethod();

}

}

packageextthread;importservice.Service;public class ThreadB extendsThread {privateService service;publicThreadB(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.testMethod();

}

}

packageservice;public classService {synchronized public voidtestMethod() {if (Thread.currentThread().getName().equals("a")) {

System.out.println("ThreadName=" +Thread.currentThread().getName()+ " run beginTime=" +System.currentTimeMillis());int i = 1;while (i == 1) {if (("" + Math.random()).substring(0, 8).equals("0.123456")) {

System.out.println("ThreadName=" + Thread.currentThread().getName() +

" run exceptionTime="+System.currentTimeMillis());

Integer.parseInt("a");

}

}

}else{

System.out.println("Thread B run Time=" +System.currentTimeMillis());

}

}

}

packagecontroller;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classTest {public static voidmain(String[] args) {try{

Service service= newService();

ThreadA a= newThreadA(service);

a.setName("a");

a.start();

Thread.sleep(500);

ThreadB b= newThreadB(service);

b.setName("b");

b.start();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

ThreadName=a run beginTime=1525315417775

ThreadName=a run exceptionTime=1525315418077

Exception in thread "a" java.lang.NumberFormatException: For input string: "a"

at java.lang.NumberFormatException.forInputString(Unknown Source)

at java.lang.Integer.parseInt(Unknown Source)

at java.lang.Integer.parseInt(Unknown Source)

at service.Service.testMethod(Service.java:14)

at extthread.ThreadA.run(ThreadA.java:16)

Thread B run Time=1525315418276

8.同步不具有继承性

示例1:子类不加synchronized关键字。由前两行可以看出来,子类是非同步调用的。

packageextthread;importservice.Sub;public class MyThreadA extendsThread {privateSub sub;publicMyThreadA(Sub sub) {super();this.sub =sub;

}

@Overridepublic voidrun() {

sub.serviceMethod();

}

}

packageextthread;importservice.Sub;public class MyThreadB extendsThread {privateSub sub;publicMyThreadB(Sub sub) {super();this.sub =sub;

}

@Overridepublic voidrun() {

sub.serviceMethod();

}

}

packageservice;public classMain {synchronized public voidserviceMethod() {try{

System.out.println("int main 下一步 sleep begin threadName="+Thread.currentThread().getName()+ " time="+System.currentTimeMillis());

Thread.sleep(5000);

System.out.println("int main 下一步 sleep end threadName=" +Thread.currentThread().getName()+ " time="+System.currentTimeMillis());

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageservice;public class Sub extendsMain {

@Overridepublic voidserviceMethod() {try{

System.out.println("int sub 下一步 sleep begin threadName="+Thread.currentThread().getName()+ " time=" +System.currentTimeMillis());

Thread.sleep(5000);

System.out.println("int sub 下一步 sleep end threadName="+Thread.currentThread().getName()+ " time="+System.currentTimeMillis());super.serviceMethod();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packagecontroller;importservice.Sub;importextthread.MyThreadA;importextthread.MyThreadB;public classTest {public static voidmain(String[] args) {

Sub subRef= newSub();

MyThreadA a= newMyThreadA(subRef);

a.setName("A");

a.start();

MyThreadB b= newMyThreadB(subRef);

b.setName("B");

b.start();

}

}

int sub 下一步 sleep begin threadName=A time=1525316376431

int sub 下一步 sleep begin threadName=B time=1525316376431

int sub 下一步 sleep end threadName=A time=1525316381431

int sub 下一步 sleep end threadName=B time=1525316381431

int main 下一步 sleep begin threadName=A time=1525316381431

int main 下一步 sleep end threadName=A time=1525316386432

int main 下一步 sleep begin threadName=B time=1525316386432

int main 下一步 sleep end threadName=B time=1525316391433

示例2:子类加synchronized关键字。可以看出,同步不能继承, 还需要在子类的方法中添加synchronized关键字才可以。

int sub 下一步 sleep begin threadName=A time=1525316522710

int sub 下一步 sleep end threadName=A time=1525316527710

int main 下一步 sleep begin threadName=A time=1525316527710

int main 下一步 sleep end threadName=A time=1525316532710

int sub 下一步 sleep begin threadName=B time=1525316532710

int sub 下一步 sleep end threadName=B time=1525316537711

int main 下一步 sleep begin threadName=B time=1525316537711

int main 下一步 sleep end threadName=B time=1525316542711

二、synchronized同步语句块

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长的时间。在这样的情况下可以使用synchronized语句块来解决。

1.synchronized方法的弊端

示例:测试用synchronized关键字声明方法的弊端。从输出结果可以看出,从开始到结束一共花费了6秒的时间,这个时间很长,所以来说弊端就比较明显了。

packagecommonutils;public classCommonUtils {public static longbeginTime1;public static longendTime1;public static longbeginTime2;public static longendTime2;

}

packagemytask;public classTask {privateString getData1;privateString getData2;public synchronized voiddoLongTimeTask() {try{

System.out.println("begin task");

Thread.sleep(3000);

getData1= "长时间处理任务后从远程返回的值1 threadName="+Thread.currentThread().getName();

getData2= "长时间处理任务后从远程返回的值2 threadName="+Thread.currentThread().getName();

System.out.println(getData1);

System.out.println(getData2);

System.out.println("end task");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packagemythread;importcommonutils.CommonUtils;importmytask.Task;public class MyThread1 extendsThread {privateTask task;publicMyThread1(Task task) {super();this.task =task;

}

@Overridepublic voidrun() {super.run();

CommonUtils.beginTime1=System.currentTimeMillis();

task.doLongTimeTask();

CommonUtils.endTime1=System.currentTimeMillis();

}

}

packagemythread;importcommonutils.CommonUtils;importmytask.Task;public class MyThread2 extendsThread {privateTask task;publicMyThread2(Task task) {super();this.task =task;

}

@Overridepublic voidrun() {super.run();

CommonUtils.beginTime2=System.currentTimeMillis();

task.doLongTimeTask();

CommonUtils.endTime2=System.currentTimeMillis();

}

}

packagetest;importmytask.Task;importmythread.MyThread1;importmythread.MyThread2;importcommonutils.CommonUtils;public classRun {public static voidmain(String[] args) {

Task task= newTask();

MyThread1 thread1= newMyThread1(task);

thread1.start();

MyThread2 thread2= newMyThread2(task);

thread2.start();try{

Thread.sleep(10000);

}catch(InterruptedException e) {

e.printStackTrace();

}long beginTime =CommonUtils.beginTime1;if (CommonUtils.beginTime2

beginTime=CommonUtils.beginTime2;

}long endTime =CommonUtils.endTime1;if (CommonUtils.endTime2 >CommonUtils.endTime1) {

endTime=CommonUtils.endTime2;

}

System.out.println("耗时:" + ((endTime - beginTime) / 1000));

}

}

begin task

长时间处理任务后从远程返回的值1 threadName=Thread-0

长时间处理任务后从远程返回的值2 threadName=Thread-0

end task

begin task

长时间处理任务后从远程返回的值1 threadName=Thread-1

长时间处理任务后从远程返回的值2 threadName=Thread-1

end task

耗时:6秒

2.synchronized同步代码块的使用

当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

示例:serviceMethod方法没有使用synchronized关键字,而是使用了synchronized (this)同步代码块,从输出结果可以看出,虽然解决了同步的问题,但是效率没有提高,一共执行时间还是4秒。

packageservice;public classObjectService {public voidserviceMethod() {try{synchronized (this) {

System.out.println("begin time=" +System.currentTimeMillis());

Thread.sleep(2000);

System.out.println("end end=" +System.currentTimeMillis());

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageextthread;importservice.ObjectService;public class ThreadA extendsThread {privateObjectService service;publicThreadA(ObjectService service) {super();this.service =service;

}

@Overridepublic voidrun() {super.run();

service.serviceMethod();

}

}

packageextthread;importservice.ObjectService;public class ThreadB extendsThread {privateObjectService service;publicThreadB(ObjectService service) {super();this.service =service;

}

@Overridepublic voidrun() {super.run();

service.serviceMethod();

}

}

packagetest.run;importservice.ObjectService;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

ObjectService service= newObjectService();

ThreadA a= newThreadA(service);

a.setName("a");

a.start();

ThreadB b= newThreadB(service);

b.setName("b");

b.start();

}

}

begin time=1525317861377

end end=1525317863378

begin time=1525317863378

end end=1525317865378

3.用同步代码块解决同步的弊端

示例:修改1中示例的task类。输出有两种结果,可以看出,两个线程总执行时间为3秒,当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。

packagemytask;public classTask {privateString getData1;privateString getData2;public voiddoLongTimeTask() {try{

System.out.println("begin task");

Thread.sleep(3000);

String privateGetData1= "长时间处理任务后从远程返回的值1 threadName="+Thread.currentThread().getName();

String privateGetData2= "长时间处理任务后从远程返回的值2 threadName="+Thread.currentThread().getName();synchronized (this) {

getData1=privateGetData1;

getData2=privateGetData2;

}

System.out.println(getData1);

System.out.println(getData2);

System.out.println("end task");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

begin task

begin task

长时间处理任务后从远程返回的值1 threadName=Thread-1

长时间处理任务后从远程返回的值2 threadName=Thread-0

end task

长时间处理任务后从远程返回的值1 threadName=Thread-0

长时间处理任务后从远程返回的值2 threadName=Thread-0

end task

耗时:3秒

begin task

begin task

长时间处理任务后从远程返回的值1 threadName=Thread-0

长时间处理任务后从远程返回的值2 threadName=Thread-1

end task

长时间处理任务后从远程返回的值1 threadName=Thread-1

长时间处理任务后从远程返回的值2 threadName=Thread-1

end task

耗时:3秒

4.一半异步,一半同步

示例:测试不再synchronized块中就是异步执行,在synchronized块中就是同步执行。从输出结果可以看出,非同步时交叉打印输出结果,同步时都是排队打印的,线程0和线程1都是按顺序执行完100个输出结果。同一时间只能有一个线程访问synchronized块中的内容。

packagemytask;public classTask {public voiddoLongTimeTask() {for (int i = 0; i < 100; i++) {

System.out.println("nosynchronized threadName="+ Thread.currentThread().getName() + " i=" + (i + 1));

}

System.out.println("");synchronized (this) {for (int i = 0; i < 100; i++) {

System.out.println("synchronized threadName="+ Thread.currentThread().getName() + " i=" + (i + 1));

}

}

}

}

packagemythread;importmytask.Task;public class MyThread1 extendsThread {privateTask task;publicMyThread1(Task task) {super();this.task =task;

}

@Overridepublic voidrun() {super.run();

task.doLongTimeTask();

}

}

packagemythread;importmytask.Task;public class MyThread2 extendsThread {privateTask task;publicMyThread2(Task task) {super();this.task =task;

}

@Overridepublic voidrun() {super.run();

task.doLongTimeTask();

}

}

packagetest;importmytask.Task;importmythread.MyThread1;importmythread.MyThread2;public classRun {public static voidmain(String[] args) {

Task task= newTask();

MyThread1 thread1= newMyThread1(task);

thread1.start();

MyThread2 thread2= newMyThread2(task);

thread2.start();

}

}

nosynchronized threadName=Thread-0 i=1

nosynchronized threadName=Thread-0 i=2

nosynchronized threadName=Thread-0 i=3

nosynchronized threadName=Thread-0 i=4

nosynchronized threadName=Thread-0 i=5

nosynchronized threadName=Thread-0 i=6

nosynchronized threadName=Thread-0 i=7

nosynchronized threadName=Thread-0 i=8

nosynchronized threadName=Thread-0 i=9

nosynchronized threadName=Thread-0 i=10

nosynchronized threadName=Thread-0 i=11

nosynchronized threadName=Thread-0 i=12

nosynchronized threadName=Thread-0 i=13

nosynchronized threadName=Thread-0 i=14

nosynchronized threadName=Thread-0 i=15

nosynchronized threadName=Thread-0 i=16

nosynchronized threadName=Thread-0 i=17

nosynchronized threadName=Thread-0 i=18

nosynchronized threadName=Thread-0 i=19

nosynchronized threadName=Thread-0 i=20

nosynchronized threadName=Thread-0 i=21

nosynchronized threadName=Thread-0 i=22

nosynchronized threadName=Thread-0 i=23

nosynchronized threadName=Thread-0 i=24

nosynchronized threadName=Thread-0 i=25

nosynchronized threadName=Thread-0 i=26

nosynchronized threadName=Thread-0 i=27

nosynchronized threadName=Thread-0 i=28

nosynchronized threadName=Thread-0 i=29

nosynchronized threadName=Thread-0 i=30

nosynchronized threadName=Thread-0 i=31

nosynchronized threadName=Thread-0 i=32

nosynchronized threadName=Thread-0 i=33

nosynchronized threadName=Thread-0 i=34

nosynchronized threadName=Thread-0 i=35

nosynchronized threadName=Thread-0 i=36

nosynchronized threadName=Thread-0 i=37

nosynchronized threadName=Thread-1 i=1

nosynchronized threadName=Thread-1 i=2

nosynchronized threadName=Thread-0 i=38

nosynchronized threadName=Thread-1 i=3

nosynchronized threadName=Thread-1 i=4

nosynchronized threadName=Thread-1 i=5

nosynchronized threadName=Thread-1 i=6

nosynchronized threadName=Thread-1 i=7

nosynchronized threadName=Thread-1 i=8

nosynchronized threadName=Thread-1 i=9

nosynchronized threadName=Thread-1 i=10

nosynchronized threadName=Thread-1 i=11

nosynchronized threadName=Thread-1 i=12

nosynchronized threadName=Thread-1 i=13

nosynchronized threadName=Thread-1 i=14

nosynchronized threadName=Thread-1 i=15

nosynchronized threadName=Thread-1 i=16

nosynchronized threadName=Thread-1 i=17

nosynchronized threadName=Thread-1 i=18

nosynchronized threadName=Thread-1 i=19

nosynchronized threadName=Thread-1 i=20

nosynchronized threadName=Thread-1 i=21

nosynchronized threadName=Thread-1 i=22

nosynchronized threadName=Thread-1 i=23

nosynchronized threadName=Thread-1 i=24

nosynchronized threadName=Thread-1 i=25

nosynchronized threadName=Thread-1 i=26

nosynchronized threadName=Thread-1 i=27

nosynchronized threadName=Thread-1 i=28

nosynchronized threadName=Thread-1 i=29

nosynchronized threadName=Thread-1 i=30

nosynchronized threadName=Thread-1 i=31

nosynchronized threadName=Thread-1 i=32

nosynchronized threadName=Thread-1 i=33

nosynchronized threadName=Thread-1 i=34

nosynchronized threadName=Thread-1 i=35

nosynchronized threadName=Thread-1 i=36

nosynchronized threadName=Thread-1 i=37

nosynchronized threadName=Thread-1 i=38

nosynchronized threadName=Thread-1 i=39

nosynchronized threadName=Thread-1 i=40

nosynchronized threadName=Thread-1 i=41

nosynchronized threadName=Thread-1 i=42

nosynchronized threadName=Thread-1 i=43

nosynchronized threadName=Thread-1 i=44

nosynchronized threadName=Thread-1 i=45

nosynchronized threadName=Thread-1 i=46

nosynchronized threadName=Thread-1 i=47

nosynchronized threadName=Thread-1 i=48

nosynchronized threadName=Thread-1 i=49

nosynchronized threadName=Thread-1 i=50

nosynchronized threadName=Thread-1 i=51

nosynchronized threadName=Thread-1 i=52

nosynchronized threadName=Thread-1 i=53

nosynchronized threadName=Thread-1 i=54

nosynchronized threadName=Thread-1 i=55

nosynchronized threadName=Thread-1 i=56

nosynchronized threadName=Thread-1 i=57

nosynchronized threadName=Thread-1 i=58

nosynchronized threadName=Thread-1 i=59

nosynchronized threadName=Thread-1 i=60

nosynchronized threadName=Thread-1 i=61

nosynchronized threadName=Thread-1 i=62

nosynchronized threadName=Thread-1 i=63

nosynchronized threadName=Thread-1 i=64

nosynchronized threadName=Thread-1 i=65

nosynchronized threadName=Thread-1 i=66

nosynchronized threadName=Thread-1 i=67

nosynchronized threadName=Thread-1 i=68

nosynchronized threadName=Thread-1 i=69

nosynchronized threadName=Thread-1 i=70

nosynchronized threadName=Thread-1 i=71

nosynchronized threadName=Thread-1 i=72

nosynchronized threadName=Thread-1 i=73

nosynchronized threadName=Thread-1 i=74

nosynchronized threadName=Thread-1 i=75

nosynchronized threadName=Thread-1 i=76

nosynchronized threadName=Thread-1 i=77

nosynchronized threadName=Thread-1 i=78

nosynchronized threadName=Thread-1 i=79

nosynchronized threadName=Thread-1 i=80

nosynchronized threadName=Thread-1 i=81

nosynchronized threadName=Thread-1 i=82

nosynchronized threadName=Thread-1 i=83

nosynchronized threadName=Thread-1 i=84

nosynchronized threadName=Thread-1 i=85

nosynchronized threadName=Thread-1 i=86

nosynchronized threadName=Thread-1 i=87

nosynchronized threadName=Thread-1 i=88

nosynchronized threadName=Thread-1 i=89

nosynchronized threadName=Thread-1 i=90

nosynchronized threadName=Thread-1 i=91

nosynchronized threadName=Thread-1 i=92

nosynchronized threadName=Thread-1 i=93

nosynchronized threadName=Thread-1 i=94

nosynchronized threadName=Thread-1 i=95

nosynchronized threadName=Thread-1 i=96

nosynchronized threadName=Thread-1 i=97

nosynchronized threadName=Thread-1 i=98

nosynchronized threadName=Thread-1 i=99

nosynchronized threadName=Thread-1 i=100

synchronized threadName=Thread-1 i=1

synchronized threadName=Thread-1 i=2

synchronized threadName=Thread-1 i=3

synchronized threadName=Thread-1 i=4

synchronized threadName=Thread-1 i=5

synchronized threadName=Thread-1 i=6

synchronized threadName=Thread-1 i=7

synchronized threadName=Thread-1 i=8

synchronized threadName=Thread-1 i=9

synchronized threadName=Thread-1 i=10

synchronized threadName=Thread-1 i=11

synchronized threadName=Thread-1 i=12

synchronized threadName=Thread-1 i=13

synchronized threadName=Thread-1 i=14

synchronized threadName=Thread-1 i=15

synchronized threadName=Thread-1 i=16

synchronized threadName=Thread-1 i=17

synchronized threadName=Thread-1 i=18

synchronized threadName=Thread-1 i=19

synchronized threadName=Thread-1 i=20

synchronized threadName=Thread-1 i=21

synchronized threadName=Thread-1 i=22

synchronized threadName=Thread-1 i=23

synchronized threadName=Thread-1 i=24

synchronized threadName=Thread-1 i=25

synchronized threadName=Thread-1 i=26

synchronized threadName=Thread-1 i=27

synchronized threadName=Thread-1 i=28

synchronized threadName=Thread-1 i=29

synchronized threadName=Thread-1 i=30

synchronized threadName=Thread-1 i=31

synchronized threadName=Thread-1 i=32

synchronized threadName=Thread-1 i=33

synchronized threadName=Thread-1 i=34

synchronized threadName=Thread-1 i=35

synchronized threadName=Thread-1 i=36

synchronized threadName=Thread-1 i=37

synchronized threadName=Thread-1 i=38

synchronized threadName=Thread-1 i=39

synchronized threadName=Thread-1 i=40

synchronized threadName=Thread-1 i=41

synchronized threadName=Thread-1 i=42

synchronized threadName=Thread-1 i=43

synchronized threadName=Thread-1 i=44

synchronized threadName=Thread-1 i=45

synchronized threadName=Thread-1 i=46

synchronized threadName=Thread-1 i=47

synchronized threadName=Thread-1 i=48

synchronized threadName=Thread-1 i=49

synchronized threadName=Thread-1 i=50

synchronized threadName=Thread-1 i=51

synchronized threadName=Thread-1 i=52

synchronized threadName=Thread-1 i=53

synchronized threadName=Thread-1 i=54

synchronized threadName=Thread-1 i=55

synchronized threadName=Thread-1 i=56

synchronized threadName=Thread-1 i=57

synchronized threadName=Thread-1 i=58

synchronized threadName=Thread-1 i=59

synchronized threadName=Thread-1 i=60

synchronized threadName=Thread-1 i=61

synchronized threadName=Thread-1 i=62

synchronized threadName=Thread-1 i=63

synchronized threadName=Thread-1 i=64

synchronized threadName=Thread-1 i=65

synchronized threadName=Thread-1 i=66

synchronized threadName=Thread-1 i=67

synchronized threadName=Thread-1 i=68

synchronized threadName=Thread-1 i=69

synchronized threadName=Thread-1 i=70

synchronized threadName=Thread-1 i=71

synchronized threadName=Thread-1 i=72

synchronized threadName=Thread-1 i=73

synchronized threadName=Thread-1 i=74

synchronized threadName=Thread-1 i=75

synchronized threadName=Thread-1 i=76

synchronized threadName=Thread-1 i=77

synchronized threadName=Thread-1 i=78

synchronized threadName=Thread-1 i=79

synchronized threadName=Thread-1 i=80

synchronized threadName=Thread-1 i=81

synchronized threadName=Thread-1 i=82

synchronized threadName=Thread-1 i=83

synchronized threadName=Thread-1 i=84

synchronized threadName=Thread-1 i=85

synchronized threadName=Thread-1 i=86

synchronized threadName=Thread-1 i=87

synchronized threadName=Thread-1 i=88

synchronized threadName=Thread-1 i=89

synchronized threadName=Thread-1 i=90

synchronized threadName=Thread-1 i=91

synchronized threadName=Thread-1 i=92

synchronized threadName=Thread-1 i=93

synchronized threadName=Thread-1 i=94

synchronized threadName=Thread-1 i=95

synchronized threadName=Thread-1 i=96

synchronized threadName=Thread-1 i=97

synchronized threadName=Thread-1 i=98

synchronized threadName=Thread-1 i=99

synchronized threadName=Thread-1 i=100

nosynchronized threadName=Thread-0 i=39

nosynchronized threadName=Thread-0 i=40

nosynchronized threadName=Thread-0 i=41

nosynchronized threadName=Thread-0 i=42

nosynchronized threadName=Thread-0 i=43

nosynchronized threadName=Thread-0 i=44

nosynchronized threadName=Thread-0 i=45

nosynchronized threadName=Thread-0 i=46

nosynchronized threadName=Thread-0 i=47

nosynchronized threadName=Thread-0 i=48

nosynchronized threadName=Thread-0 i=49

nosynchronized threadName=Thread-0 i=50

nosynchronized threadName=Thread-0 i=51

nosynchronized threadName=Thread-0 i=52

nosynchronized threadName=Thread-0 i=53

nosynchronized threadName=Thread-0 i=54

nosynchronized threadName=Thread-0 i=55

nosynchronized threadName=Thread-0 i=56

nosynchronized threadName=Thread-0 i=57

nosynchronized threadName=Thread-0 i=58

nosynchronized threadName=Thread-0 i=59

nosynchronized threadName=Thread-0 i=60

nosynchronized threadName=Thread-0 i=61

nosynchronized threadName=Thread-0 i=62

nosynchronized threadName=Thread-0 i=63

nosynchronized threadName=Thread-0 i=64

nosynchronized threadName=Thread-0 i=65

nosynchronized threadName=Thread-0 i=66

nosynchronized threadName=Thread-0 i=67

nosynchronized threadName=Thread-0 i=68

nosynchronized threadName=Thread-0 i=69

nosynchronized threadName=Thread-0 i=70

nosynchronized threadName=Thread-0 i=71

nosynchronized threadName=Thread-0 i=72

nosynchronized threadName=Thread-0 i=73

nosynchronized threadName=Thread-0 i=74

nosynchronized threadName=Thread-0 i=75

nosynchronized threadName=Thread-0 i=76

nosynchronized threadName=Thread-0 i=77

nosynchronized threadName=Thread-0 i=78

nosynchronized threadName=Thread-0 i=79

nosynchronized threadName=Thread-0 i=80

nosynchronized threadName=Thread-0 i=81

nosynchronized threadName=Thread-0 i=82

nosynchronized threadName=Thread-0 i=83

nosynchronized threadName=Thread-0 i=84

nosynchronized threadName=Thread-0 i=85

nosynchronized threadName=Thread-0 i=86

nosynchronized threadName=Thread-0 i=87

nosynchronized threadName=Thread-0 i=88

nosynchronized threadName=Thread-0 i=89

nosynchronized threadName=Thread-0 i=90

nosynchronized threadName=Thread-0 i=91

nosynchronized threadName=Thread-0 i=92

nosynchronized threadName=Thread-0 i=93

nosynchronized threadName=Thread-0 i=94

nosynchronized threadName=Thread-0 i=95

nosynchronized threadName=Thread-0 i=96

nosynchronized threadName=Thread-0 i=97

nosynchronized threadName=Thread-0 i=98

nosynchronized threadName=Thread-0 i=99

nosynchronized threadName=Thread-0 i=100

synchronized threadName=Thread-0 i=1

synchronized threadName=Thread-0 i=2

synchronized threadName=Thread-0 i=3

synchronized threadName=Thread-0 i=4

synchronized threadName=Thread-0 i=5

synchronized threadName=Thread-0 i=6

synchronized threadName=Thread-0 i=7

synchronized threadName=Thread-0 i=8

synchronized threadName=Thread-0 i=9

synchronized threadName=Thread-0 i=10

synchronized threadName=Thread-0 i=11

synchronized threadName=Thread-0 i=12

synchronized threadName=Thread-0 i=13

synchronized threadName=Thread-0 i=14

synchronized threadName=Thread-0 i=15

synchronized threadName=Thread-0 i=16

synchronized threadName=Thread-0 i=17

synchronized threadName=Thread-0 i=18

synchronized threadName=Thread-0 i=19

synchronized threadName=Thread-0 i=20

synchronized threadName=Thread-0 i=21

synchronized threadName=Thread-0 i=22

synchronized threadName=Thread-0 i=23

synchronized threadName=Thread-0 i=24

synchronized threadName=Thread-0 i=25

synchronized threadName=Thread-0 i=26

synchronized threadName=Thread-0 i=27

synchronized threadName=Thread-0 i=28

synchronized threadName=Thread-0 i=29

synchronized threadName=Thread-0 i=30

synchronized threadName=Thread-0 i=31

synchronized threadName=Thread-0 i=32

synchronized threadName=Thread-0 i=33

synchronized threadName=Thread-0 i=34

synchronized threadName=Thread-0 i=35

synchronized threadName=Thread-0 i=36

synchronized threadName=Thread-0 i=37

synchronized threadName=Thread-0 i=38

synchronized threadName=Thread-0 i=39

synchronized threadName=Thread-0 i=40

synchronized threadName=Thread-0 i=41

synchronized threadName=Thread-0 i=42

synchronized threadName=Thread-0 i=43

synchronized threadName=Thread-0 i=44

synchronized threadName=Thread-0 i=45

synchronized threadName=Thread-0 i=46

synchronized threadName=Thread-0 i=47

synchronized threadName=Thread-0 i=48

synchronized threadName=Thread-0 i=49

synchronized threadName=Thread-0 i=50

synchronized threadName=Thread-0 i=51

synchronized threadName=Thread-0 i=52

synchronized threadName=Thread-0 i=53

synchronized threadName=Thread-0 i=54

synchronized threadName=Thread-0 i=55

synchronized threadName=Thread-0 i=56

synchronized threadName=Thread-0 i=57

synchronized threadName=Thread-0 i=58

synchronized threadName=Thread-0 i=59

synchronized threadName=Thread-0 i=60

synchronized threadName=Thread-0 i=61

synchronized threadName=Thread-0 i=62

synchronized threadName=Thread-0 i=63

synchronized threadName=Thread-0 i=64

synchronized threadName=Thread-0 i=65

synchronized threadName=Thread-0 i=66

synchronized threadName=Thread-0 i=67

synchronized threadName=Thread-0 i=68

synchronized threadName=Thread-0 i=69

synchronized threadName=Thread-0 i=70

synchronized threadName=Thread-0 i=71

synchronized threadName=Thread-0 i=72

synchronized threadName=Thread-0 i=73

synchronized threadName=Thread-0 i=74

synchronized threadName=Thread-0 i=75

synchronized threadName=Thread-0 i=76

synchronized threadName=Thread-0 i=77

synchronized threadName=Thread-0 i=78

synchronized threadName=Thread-0 i=79

synchronized threadName=Thread-0 i=80

synchronized threadName=Thread-0 i=81

synchronized threadName=Thread-0 i=82

synchronized threadName=Thread-0 i=83

synchronized threadName=Thread-0 i=84

synchronized threadName=Thread-0 i=85

synchronized threadName=Thread-0 i=86

synchronized threadName=Thread-0 i=87

synchronized threadName=Thread-0 i=88

synchronized threadName=Thread-0 i=89

synchronized threadName=Thread-0 i=90

synchronized threadName=Thread-0 i=91

synchronized threadName=Thread-0 i=92

synchronized threadName=Thread-0 i=93

synchronized threadName=Thread-0 i=94

synchronized threadName=Thread-0 i=95

synchronized threadName=Thread-0 i=96

synchronized threadName=Thread-0 i=97

synchronized threadName=Thread-0 i=98

synchronized threadName=Thread-0 i=99

synchronized threadName=Thread-0 i=100

5.synchronized代码块间的同步性

在使用synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞。

示例:从输出结果可以看出,线程A在访问object的同步代码块时,线程B只能等待线程A访问的代码块访问完成才可以继续访问object的对应的另一个同步代码块。

packageservice;public classObjectService {public voidserviceMethodA() {try{synchronized (this) {

System.out.println("A begin time=" +System.currentTimeMillis());

Thread.sleep(2000);

System.out.println("A end end=" +System.currentTimeMillis());

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}public voidserviceMethodB() {synchronized (this) {

System.out.println("B begin time=" +System.currentTimeMillis());

System.out.println("B end end=" +System.currentTimeMillis());

}

}

}

packageextthread;importservice.ObjectService;public class ThreadA extendsThread {privateObjectService service;publicThreadA(ObjectService service) {super();this.service =service;

}

@Overridepublic voidrun() {super.run();

service.serviceMethodA();

}

}

packagetest.run;importservice.ObjectService;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

ObjectService service= newObjectService();

ThreadA a= newThreadA(service);

a.setName("a");

a.start();

ThreadB b= newThreadB(service);

b.setName("b");

b.start();

}

}

A begin time=1525335613278

A end end=1525335615279

B begin time=1525335615279

B end end=1525335615279

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

示例1:otherMethod方法不加关键字,线程1在运行100毫秒后线程2接着执行,即异步打印。

packagemytask;public classTask {public voidotherMethod() {

System.out.println("------------------------run--otherMethod");

}public voiddoLongTimeTask() {synchronized (this) {for (int i = 0; i < 10000; i++) {

System.out.println("synchronized threadName="+ Thread.currentThread().getName() + " i=" + (i + 1));

}

}

}

}

packagemythread;importmytask.Task;public class MyThread1 extendsThread {privateTask task;publicMyThread1(Task task) {super();this.task =task;

}

@Overridepublic voidrun() {super.run();

task.doLongTimeTask();

}

}

packagemythread;importmytask.Task;public class MyThread2 extendsThread {privateTask task;publicMyThread2(Task task) {super();this.task =task;

}

@Overridepublic voidrun() {super.run();

task.otherMethod();

}

}

packagetest;importmytask.Task;importmythread.MyThread1;importmythread.MyThread2;public classRun {public static void main(String[] args) throwsInterruptedException {

Task task= newTask();

MyThread1 thread1= newMyThread1(task);

thread1.start();

Thread.sleep(100);

MyThread2 thread2= newMyThread2(task);

thread2.start();

}

}

synchronized threadName=Thread-0 i=8216

synchronized threadName=Thread-0 i=8217

synchronized threadName=Thread-0 i=8218

synchronized threadName=Thread-0 i=8219

synchronized threadName=Thread-0 i=8220

synchronized threadName=Thread-0 i=8221

------------------------run--otherMethod

synchronized threadName=Thread-0 i=8222

synchronized threadName=Thread-0 i=8223

synchronized threadName=Thread-0 i=8224

synchronized threadName=Thread-0 i=8225

synchronized threadName=Thread-0 i=8226

示例2:otherMethod方法加关键字,线程2等待线程1执行完成后才执行,即同步打印。

synchronized threadName=Thread-0 i=9992

synchronized threadName=Thread-0 i=9993

synchronized threadName=Thread-0 i=9994

synchronized threadName=Thread-0 i=9995

synchronized threadName=Thread-0 i=9996

synchronized threadName=Thread-0 i=9997

synchronized threadName=Thread-0 i=9998

synchronized threadName=Thread-0 i=9999

synchronized threadName=Thread-0 i=10000

------------------------run--otherMethod

7.将任意对象作为监视器

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

这说明synchronized同步方法或者synchronized(this)同步代码块分别有两种作用。

(1)synchronized同步方法

①对其他synchronized同步方法或者synchronized(this)同步代码块调用呈阻塞状态。

②同一时间只有一个线程可以执行synchronized同步方法中的代码。

(2)synchronized(this)同步代码块

①对其他synchronized同步方法或者synchronized(this)同步代码块调用呈阻塞状态。

②同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

(3)synchronized(非this对象)同步代码块

①在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象 x)同步代码块中的代码。

②当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象 x)同步代码块中的代码。

示例1:Service类中的anyString字段在setUsernamePassword方法外部声明。线程A和线程B在构造函数中都引入了同一个对象anyString。锁定anyString对象具有一定的优点:如果在一个类中有很多个synchronized方法,这是虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块非锁this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不予其他锁this同步方法争抢this锁,则可以大大提高运行效率。

packageservice;public classService {privateString usernameParam;privateString passwordParam;private String anyString = newString();public voidsetUsernamePassword(String username, String password) {try{//String anyString = new String();

synchronized(anyString) {

System.out.println("线程名称为:" + Thread.currentThread().getName()+ "在" + System.currentTimeMillis() + "进入同步块");

usernameParam=username;

Thread.sleep(3000);

passwordParam=password;

System.out.println("线程名称为:" + Thread.currentThread().getName()+ "在" + System.currentTimeMillis() + "离开同步块");

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageextthread;importservice.Service;public class ThreadA extendsThread {privateService service;publicThreadA(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.setUsernamePassword("a", "aa");

}

}

packageextthread;importservice.Service;public class ThreadB extendsThread {privateService service;publicThreadB(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.setUsernamePassword("b", "bb");

}

}

packagetest;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

Service service= newService();

ThreadA a= newThreadA(service);

a.setName("A");

a.start();

ThreadB b= newThreadB(service);

b.setName("B");

b.start();

}

}

线程名称为:A在1525339161012进入同步块

线程名称为:A在1525339164012离开同步块

线程名称为:B在1525339164012进入同步块

线程名称为:B在1525339167013离开同步块

示例2:Service类中的anyString字段在setUsernamePassword方法外部声明。线程A和线程B在调用setUsernamePassword方法时会分别生成两个不同的对象。由输出结果可以看出,使用synchronized(非this对象x)同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行。

packageservice;public classService {privateString usernameParam;privateString passwordParam;//private String anyString = new String();

public voidsetUsernamePassword(String username, String password) {try{

String anyString= newString();synchronized(anyString) {

System.out.println("线程名称为:" + Thread.currentThread().getName()+ "在" + System.currentTimeMillis() + "进入同步块");

usernameParam=username;

Thread.sleep(3000);

passwordParam=password;

System.out.println("线程名称为:" + Thread.currentThread().getName()+ "在" + System.currentTimeMillis() + "离开同步块");

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

线程名称为:A在1525339564398进入同步块

线程名称为:B在1525339564398进入同步块

线程名称为:A在1525339567399离开同步块

线程名称为:B在1525339567399离开同步块

示例3:从输出结果可以看出,synchronized(非this对象)与同步synchronized方法时异步调用的。由于对象监视器不同,所以运行结果就是异步的。

packageservice;public classService {private String anyString = newString();public voida() {try{synchronized(anyString) {

System.out.println("a begin");

Thread.sleep(3000);

System.out.println("a end");

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}synchronized public voidb() {

System.out.println("b begin");

System.out.println("b end");

}

}

packagetest;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

Service service= newService();

ThreadA a= newThreadA(service);

a.setName("A");

a.start();

ThreadB b= newThreadB(service);

b.setName("B");

b.start();

}

}

a begin

b begin

b end

a end

示例4:同步代码块放在非同步synchronized方法中进行声明,并不能保证方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的,这样极易 出现“脏读”问题。验证多个线程调用同一个方法是随机的。从输出结果也可以看出,同步代码块中的代码是同步打印的,当前线程的“执行”与“退出”是成对出现的。但线程A和线程B的执行确实异步的,这就有可能出现脏读的环境。由于线程执行方法的顺序不确定,所以当A和B两个线程执行带有分支判断的方法时,就会出现逻辑上的错误,有可能出现脏读。

packagemylist;importjava.util.ArrayList;importjava.util.List;public classMyList {private List list = new ArrayList();synchronized public voidadd(String username) {

System.out.println("ThreadName=" + Thread.currentThread().getName()+ "执行了add方法!");

list.add(username);

System.out.println("ThreadName=" + Thread.currentThread().getName()+ "退出了add方法!");

}synchronized public intgetSize() {

System.out.println("ThreadName=" + Thread.currentThread().getName()+ "ִ执行了getSize方法!");int sizeValue =list.size();

System.out.println("ThreadName=" + Thread.currentThread().getName()+ "执行了getSize方法!");returnsizeValue;

}

}

packageextthread;importmylist.MyList;public class MyThreadA extendsThread {privateMyList list;publicMyThreadA(MyList list) {super();this.list =list;

}

@Overridepublic voidrun() {for (int i = 0; i < 100000; i++) {

list.add("threadA" + (i + 1));

}

}

}

packageextthread;importmylist.MyList;public class MyThreadB extendsThread {privateMyList list;publicMyThreadB(MyList list) {super();this.list =list;

}

@Overridepublic voidrun() {for (int i = 0; i < 100000; i++) {

list.add("threadB" + (i + 1));

}

}

}

packagetest;importmylist.MyList;importextthread.MyThreadA;importextthread.MyThreadB;public classTest {public static voidmain(String[] args) {

MyList mylist= newMyList();

MyThreadA a= newMyThreadA(mylist);

a.setName("A");

a.start();

MyThreadB b= newMyThreadB(mylist);

b.setName("B");

b.start();

}

}

ThreadName=A执行了add方法!

ThreadName=A退出了add方法!

ThreadName=B执行了add方法!

ThreadName=B退出了add方法!

ThreadName=A执行了add方法!

ThreadName=A退出了add方法!

ThreadName=B执行了add方法!

ThreadName=B退出了add方法!

ThreadName=B执行了add方法!

ThreadName=B退出了add方法!

ThreadName=A执行了add方法!

ThreadName=A退出了add方法!

...

示例5:业务类Service类的addServiceMethod方法未加synchronized(非this)同步代码块,由于两个线程共同对同一个list对象进行处理,未进行同步时,就会同时对list对象进行处理,两个线程都判断getSize<1,所以两个线程都执行了add方法,结果就是2。

packagemylist;importjava.util.ArrayList;importjava.util.List;public classMyOneList {private List list = new ArrayList();synchronized public voidadd(String data) {

list.add(data);

};synchronized public intgetSize() {returnlist.size();

};

}

packagemythread;importmylist.MyOneList;importservice.MyService;public class MyThread1 extendsThread {privateMyOneList list;publicMyThread1(MyOneList list) {super();this.list =list;

}

@Overridepublic voidrun() {

MyService msRef= newMyService();

msRef.addServiceMethod(list,"A");

}

}

packagemythread;importservice.MyService;importmylist.MyOneList;public class MyThread2 extendsThread {privateMyOneList list;publicMyThread2(MyOneList list) {super();this.list =list;

}

@Overridepublic voidrun() {

MyService msRef= newMyService();

msRef.addServiceMethod(list,"B");

}

}

packageservice;importmylist.MyOneList;public classMyService {publicMyOneList addServiceMethod(MyOneList list, String data) {try{if (list.getSize() < 1) {

Thread.sleep(2000);

list.add(data);

}

}catch(InterruptedException e) {

e.printStackTrace();

}returnlist;

}

}

packagetest;importmylist.MyOneList;importmythread.MyThread1;importmythread.MyThread2;public classRun {public static void main(String[] args) throwsInterruptedException {

MyOneList list= newMyOneList();

MyThread1 thread1= newMyThread1(list);

thread1.setName("A");

thread1.start();

MyThread2 thread2= newMyThread2(list);

thread2.setName("B");

thread2.start();

Thread.sleep(6000);

System.out.println("listSize=" +list.getSize());

}

}

listSize=2

示例6:业务类Service类的addServiceMethod方法加synchronized(list)同步代码块,由于list参数对象在项目中是一份实例,是单例的,而且也正需要对list参数的getSize()方法做同步的调用,所以就对list参数进行了同步处理,这里说明了同步代码块的位置。

packageservice;importmylist.MyOneList;public classMyService {publicMyOneList addServiceMethod(MyOneList list, String data) {try{synchronized(list) {if (list.getSize() < 1) {

Thread.sleep(2000); //模拟从远程花费2秒取回数据

list.add(data);

}

}

}catch(InterruptedException e) {

e.printStackTrace();

}returnlist;

}

}

listSize=1

8.细化验证3个结论

synchronized(非this对象 x)将x对象本身作为“对象监视器”,这样就可以得出以下三个结论:

(1)当多个线程同时执行synchronized(非this对象 x)同步代码块时呈同步效果。

(2)当其他线程执行x对象中的synchronized同步方法时呈同步效果

(3)当其他线程执行x对象方法里面的synchronized(this)同步代码块时也呈同步效果。

但需要注意:如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

示例1:验证当多个线程同时执行synchronized(非this对象 x)同步代码块时呈同步效果。

packagetest1.extobject;public classMyObject {

}

packagetest1.service;importtest1.extobject.MyObject;public classService {public voidtestMethod1(MyObject object) {synchronized(object) {try{

System.out.println("testMethod1 ____getLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

Thread.sleep(2000);

System.out.println("testMethod1 releaseLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

packagetest1.extthread;importtest1.extobject.MyObject;importtest1.service.Service;public class ThreadA extendsThread {privateService service;privateMyObject object;publicThreadA(Service service, MyObject object) {super();this.service =service;this.object =object;

}

@Overridepublic voidrun() {super.run();

service.testMethod1(object);

}

}

packagetest1.extthread;importtest1.extobject.MyObject;importtest1.service.Service;public class ThreadB extendsThread {privateService service;privateMyObject object;publicThreadB(Service service, MyObject object) {super();this.service =service;this.object =object;

}

@Overridepublic voidrun() {super.run();

service.testMethod1(object);

}

}

示例1.1:线程A和线程B共同处理一个object对象,同时执行同步代码块中的对象呈现同步调用。

packagetest1.run;importtest1.extobject.MyObject;importtest1.extthread.ThreadA;importtest1.extthread.ThreadB;importtest1.service.Service;public classRun1_1 {public static voidmain(String[] args) {

Service service= newService();

MyObject object= newMyObject();

ThreadA a= newThreadA(service, object);

a.setName("a");

a.start();

ThreadB b= newThreadB(service, object);

b.setName("b");

b.start();

}

}

testMethod1 ____getLock time=1525352109272 run ThreadName=a

testMethod1 releaseLock time=1525352111272 run ThreadName=a

testMethod1 ____getLock time=1525352111272 run ThreadName=b

testMethod1 releaseLock time=1525352113273 run ThreadName=b

示例1.2:线程A和线程B分别使用不同的“对象监视器”,即分别对两个不同的object对象进行操作,就会出现异步的情况。

packagetest1.run;importtest1.extobject.MyObject;importtest1.extthread.ThreadA;importtest1.extthread.ThreadB;importtest1.service.Service;public classRun1_2 {public static voidmain(String[] args) {

Service service= newService();

MyObject object1= newMyObject();

MyObject object2= newMyObject();

ThreadA a= newThreadA(service, object1);

a.setName("a");

a.start();

ThreadB b= newThreadB(service, object2);

b.setName("b");

b.start();

}

}

testMethod1 ____getLock time=1525352259718 run ThreadName=b

testMethod1 ____getLock time=1525352259718 run ThreadName=a

testMethod1 releaseLock time=1525352261718 run ThreadName=b

testMethod1 releaseLock time=1525352261718 run ThreadName=a

示例2:验证当其他线程执行x对象中的synchronized同步方法时呈同步效果,线程B执行object对象中的同步方法时呈同步效果。

packagetest2.extobject;public classMyObject {synchronized public voidspeedPrintString() {

System.out.println("speedPrintString ____getLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

System.out.println("-----------------");

System.out.println("speedPrintString releaseLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

}

}

importtest2.extobject.MyObject;public classService {public voidtestMethod1(MyObject object) {synchronized(object) {try{

System.out.println("testMethod1 ____getLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

Thread.sleep(5000);

System.out.println("testMethod1 releaseLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

packagetest2.extthread;importtest2.extobject.MyObject;importtest2.service.Service;public class ThreadA extendsThread {privateService service;privateMyObject object;publicThreadA(Service service, MyObject object) {super();this.service =service;this.object =object;

}

@Overridepublic voidrun() {super.run();

service.testMethod1(object);

}

}

packagetest2.extthread;importtest2.extobject.MyObject;public class ThreadB extendsThread {privateMyObject object;publicThreadB(MyObject object) {super();this.object =object;

}

@Overridepublic voidrun() {super.run();

object.speedPrintString();

}

}

packagetest2.run;importtest2.extobject.MyObject;importtest2.extthread.ThreadA;importtest2.extthread.ThreadB;importtest2.service.Service;public classRun {public static void main(String[] args) throwsInterruptedException {

Service service= newService();

MyObject object= newMyObject();

ThreadA a= newThreadA(service, object);

a.setName("a");

a.start();

Thread.sleep(100);

ThreadB b= newThreadB(object);

b.setName("b");

b.start();

}

}

testMethod1 ____getLock time=1525352713297 run ThreadName=a

testMethod1 releaseLock time=1525352718297 run ThreadName=a

speedPrintString ____getLock time=1525352718297 run ThreadName=b

-----------------

speedPrintString releaseLock time=1525352718297 run ThreadName=b

示例3:验证当其他线程执行x对象方法里面的synchronized(this)同步代码块时也呈同步效果。修改示例2中的MyObject类,其他不变。从输出结果来看,线程B执行object对象方法中的synchronized(this)同步代码块时也呈同步效果。

packagetest2.extobject;public classMyObject {public voidspeedPrintString() {synchronized(this) {

System.out.println("speedPrintString ____getLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

System.out.println("-----------------");

System.out.println("speedPrintString releaseLock time="+System.currentTimeMillis()+ " run ThreadName="+Thread.currentThread().getName());

}

}

}

testMethod1 ____getLock time=1525353460512 run ThreadName=a

testMethod1 releaseLock time=1525353465513 run ThreadName=a

speedPrintString ____getLock time=1525353465513 run ThreadName=b

-----------------

speedPrintString releaseLock time=1525353465513 run ThreadName=b

9.静态同步synchronized与synchronized(this)代码块

关键字synchronized还可以应用在static静态方法上,这样写,是对当前的*.java文件对应的Class类进行持锁。

示例1:从输出结果来看,还是同步的效果。但是,synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

packageservice;public classService {synchronized public static voidprintA() {try{

System.out.println("线程名称为" + Thread.currentThread().getName()+ "在" + System.currentTimeMillis() + "进入printA");

Thread.sleep(3000);

System.out.println("线程名称为" + Thread.currentThread().getName()+ "在" + System.currentTimeMillis() + "离开printA");

}catch(InterruptedException e) {

e.printStackTrace();

}

}synchronized public static voidprintB() {

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "进入printB");

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "离开printB");

}

}

packageextthread;importservice.Service;public class ThreadA extendsThread {

@Overridepublic voidrun() {

Service.printA();

}

}

packageextthread;importservice.Service;public class ThreadB extendsThread {

@Overridepublic voidrun() {

Service.printB();

}

}

packagetest;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

ThreadA a= newThreadA();

a.setName("A");

a.start();

ThreadB b= newThreadB();

b.setName("B");

b.start();

}

}

线程名称为A在1525393711028进入printA

线程名称为A在1525393714029离开printA

线程名称为B在1525393714029进入printB

线程名称为B在1525393714029离开printB

示例2:为了验证不是同一个锁。添加非静态方法printC。从输出结果可以看出,出现了异步的情况,原因是持有不同的锁,一个是对象锁,一个是Class锁,而Class锁可以对类的所有对象实例起作用。

packageservice;public classService {synchronized public static voidprintA() {try{

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "进入printA");

Thread.sleep(3000);

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "离开printA");

}catch(InterruptedException e) {

e.printStackTrace();

}

}synchronized public static voidprintB() {

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "进入printB");

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "离开printB");

}synchronized public voidprintC() {

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "进入printC");

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "离开printC");

}

}

线程名称为C在1525394013882进入printC

线程名称为A在1525394013882进入printA

线程名称为C在1525394013882离开printC

线程名称为A在1525394016882离开printA

线程名称为B在1525394016882进入printB

线程名称为B在1525394016882离开printB

示例3:修改main方法,使两个线程分别执行两个不同的对象,由于Class类的所有对象实例起作用,所以即使是同一个Class产生的不同实例,也都是同步的效果。

packagetest;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

Service service1= newService();

Service service2= newService();

ThreadA a= newThreadA(service1);

a.setName("A");

a.start();

ThreadB b= newThreadB(service2);

b.setName("B");

b.start();

}

}

线程名称为A在1525394347966进入printA

线程名称为A在1525394350966离开printA

线程名称为B在1525394350966进入printB

线程名称为B在1525394350966离开printB

示例4:同步synchronized(this)代码块的作用其实是和synchronized static方法的作用一样。

packageservice;public classService {synchronized public static voidprintA() {synchronized(Service.class) {try{

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "进入printA");

Thread.sleep(3000);

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "离开printA");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}synchronized public static voidprintB() {synchronized(Service.class) {

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "进入printB");

System.out.println("线程名称为" + Thread.currentThread().getName() + "在"+ System.currentTimeMillis() + "离开printB");

}

}

}

packagetest;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

Service service1= newService();

Service service2= newService();

ThreadA a= newThreadA(service1);

a.setName("A");

a.start();

ThreadB b= newThreadB(service2);

b.setName("B");

b.start();

}

}

线程名称为A在1525394594986进入printA

线程名称为A在1525394597987离开printA

线程名称为B在1525394597987进入printB

线程名称为B在1525394597987离开printB

10.数据类型String的常量池特性

将synchronized(string)同步块与String联合使用时,要注意由于常量池缓存带来的一些意外情况。

示例1:常量池缓存验证

packagetest;public classTest {public static voidmain(String[] args) {

String a= "a";

String b= "a";

System.out.println(a==b);

}

}

true

示例2:由于两个线程的run方法中传递进去的String值都是AA,由于缓存的存在,并且两个线程持有相同的锁,所以造成线程B不能执行。如果将线程B传递参数换成BB,那么两个线程就可以正常交替执行。

packageservice;public classService {public static voidprint(String stringParam) {try{synchronized(stringParam) {while (true) {

System.out.println(Thread.currentThread().getName());

Thread.sleep(1000);

}

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageextthread;importservice.Service;public class ThreadA extendsThread {privateService service;publicThreadA(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.print("AA");

}

}

packageextthread;importservice.Service;public class ThreadB extendsThread {privateService service;publicThreadB(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.print("AA");

}

}

packagetest;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

Service service= newService();

ThreadA a= newThreadA(service);

a.setName("A");

a.start();

ThreadB b= newThreadB(service);

b.setName("B");

b.start();

}

}

A

A

A

A

A

A

A

A

A

A

...

示例3:大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但它并不放入缓存中。从输出结果可以看出,A和B交替打印,说明持有的锁不是一个。

packageservice;public classService {public static voidprint(Object object) {try{synchronized(object) {while (true) {

System.out.println(Thread.currentThread().getName());

Thread.sleep(1000);

}

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageextthread;importservice.Service;public class ThreadA extendsThread {privateService service;publicThreadA(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.print(newObject());

}

}

packageextthread;importservice.Service;public class ThreadB extendsThread {privateService service;publicThreadB(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.print(newObject());

}

}

A

B

A

B

A

B

A

B

A

B

...

11.同步synchronized方法无限等待与解决

示例1:线程B永远得不到运行的机会,锁死了,而且线程A永远不会结束。

packageservice;public classService {synchronized public voidmethodA() {

System.out.println("methodA begin");boolean isContinueRun = true;while(isContinueRun) {

}

System.out.println("methodA end");

}synchronized public voidmethodB() {

System.out.println("methodB begin");

System.out.println("methodB end");

}

}

packagetest.run;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {

Service service= newService();

ThreadA athread= newThreadA(service);

athread.start();

ThreadB bthread= newThreadB(service);

bthread.start();

}

}

methodA begin

示例2:解决同步锁死问题。分别对两个不同的object对象实例进行锁定,可以解决同步锁死问题。

packageservice;public classService {

Object object1= newObject();

Object object2= newObject();public voidmethodA() {synchronized(object1) {

System.out.println("methodA begin");boolean isContinueRun = true;while(isContinueRun) {

}

System.out.println("methodA end");

}

}public voidmethodB() {synchronized(object2) {

System.out.println("methodB begin");

System.out.println("methodB end");

}

}

}

methodA begin

methodB begin

methodB end

12.多线程的死锁

Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中心,“死锁”是必须避免的,因为这会造成线程的“假死”。

示例1:从输出结果可以看出,两个线程都执行到前一部分然后被锁住。这是因为双方互相持有对方的锁,这里是通过嵌套的代码结构实现死锁的。

packagetest;public class DealThread implementsRunnable {publicString username;public Object lock1 = newObject();public Object lock2 = newObject();public voidsetFlag(String username) {this.username =username;

}

@Overridepublic voidrun() {if (username.equals("a")) {synchronized(lock1) {try{

System.out.println("username = " +username);

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}synchronized(lock2) {

System.out.println("按lock1->lock2代码顺序执行了");

}

}

}if (username.equals("b")) {synchronized(lock2) {try{

System.out.println("username = " +username);

Thread.sleep(3000);

}catch(InterruptedException e) {

e.printStackTrace();

}synchronized(lock1) {

System.out.println("按lock2->lock1代码顺序执行了");

}

}

}

}

}

packagetest;public classRun {public static voidmain(String[] args) {try{

DealThread t1= newDealThread();

t1.setFlag("a");

Thread thread1= newThread(t1);

thread1.start();

Thread.sleep(100);

t1.setFlag("b");

Thread thread2= newThread(t1);

thread2.start();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

username = a

username = b

示例2:可以使用JDK自带的工具来监测是否有死锁现象。

从CMD进入JDK目录下的bin文件夹下

jps

jstack -l id(of run)

Found one Java-level deadlock:

=============================

"Thread-1":

waiting to lock monitor 0x00000000048b95a8 (object 0x00000000d5fea6d8, a java.lang.Object),

which is held by "Thread-0"

"Thread-0":

waiting to lock monitor 0x00000000048b6d18 (object 0x00000000d5fea6e8, a java.lang.Object),

which is held by "Thread-1"

Java stack information for the threads listed above:

===================================================

"Thread-1":

at test.DealThread.run(DealThread.java:37)

- waiting to lock <0x00000000d5fea6d8> (a java.lang.Object)

- locked <0x00000000d5fea6e8> (a java.lang.Object)

at java.lang.Thread.run(Unknown Source)

"Thread-0":

at test.DealThread.run(DealThread.java:24)

- waiting to lock <0x00000000d5fea6e8> (a java.lang.Object)

- locked <0x00000000d5fea6d8> (a java.lang.Object)

at java.lang.Thread.run(Unknown Source)

Found 1 deadlock.

13.内置类与静态内置类

示例1:内置类,必须通过PrivateClass privateClass = publicClass.new PrivateClass();来进行实例化。

packagetest;public classPublicClass {privateString username;privateString password;classPrivateClass {privateString age;privateString address;publicString getAge() {returnage;

}public voidsetAge(String age) {this.age =age;

}publicString getAddress() {returnaddress;

}public voidsetAddress(String address) {this.address =address;

}public voidprintPublicProperty() {

System.out.println(username+ " " +password);

}

}publicString getUsername() {returnusername;

}public voidsetUsername(String username) {this.username =username;

}publicString getPassword() {returnpassword;

}public voidsetPassword(String password) {this.password =password;

}

}

packagetest;importtest.PublicClass.PrivateClass;public classRun {public static voidmain(String[] args) {

PublicClass publicClass= newPublicClass();

publicClass.setUsername("usernameValue");

publicClass.setPassword("passwordValue");

System.out.println(publicClass.getUsername()+ " "+publicClass.getPassword());

PrivateClass privateClass= publicClass.newPrivateClass();

privateClass.setAge("ageValue");

privateClass.setAddress("addressValue");

System.out.println(privateClass.getAge()+ " "+privateClass.getAddress());

}

}

usernameValue passwordValue

ageValue addressValue

示例2:静态内置类,只需要PrivateClass privateClass = newPrivateClass();正常实例化就可以了。

packagetest;public classPublicClass {static privateString username;static privateString password;static classPrivateClass {privateString age;privateString address;publicString getAge() {returnage;

}public voidsetAge(String age) {this.age =age;

}publicString getAddress() {returnaddress;

}public voidsetAddress(String address) {this.address =address;

}public voidprintPublicProperty() {

System.out.println(username+ " " +password);

}

}publicString getUsername() {returnusername;

}public voidsetUsername(String username) {this.username =username;

}publicString getPassword() {returnpassword;

}public voidsetPassword(String password) {this.password =password;

}

}

packagetest;importtest.PublicClass.PrivateClass;public classRun {public static voidmain(String[] args) {

PublicClass publicClass= newPublicClass();

publicClass.setUsername("usernameValue");

publicClass.setPassword("passwordValue");

System.out.println(publicClass.getUsername()+ " "+publicClass.getPassword());

PrivateClass privateClass= newPrivateClass();

privateClass.setAge("ageValue");

privateClass.setAddress("addressValue");

System.out.println(privateClass.getAge()+ " "+privateClass.getAddress());

}

}

usernameValue passwordValue

ageValue addressValue

14.内置类与同步:实验1

示例:内置类中有两个同步方法,但使用的却是不同的锁即持有不同的“对象监视器”,打印的结果也是异步的。

packagetest;public classOutClass {static classInnerClass {public voidmethod1() {synchronized ("其他的锁") {for (int i = 1; i <= 10; i++) {

System.out.println(Thread.currentThread().getName()+ " i="

+i);try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

}

}public synchronized voidmethod2() {for (int i = 11; i <= 20; i++) {

System.out

.println(Thread.currentThread().getName()+ " i=" +i);try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

}

}

}

packagetest;importtest.OutClass.InnerClass;;public classRun {public static voidmain(String[] args) {final InnerClass inner = newInnerClass();

Thread t1= new Thread(newRunnable() {public voidrun() {

inner.method1();

}

},"A");

Thread t2= new Thread(newRunnable() {public voidrun() {

inner.method2();

}

},"B");

t1.start();

t2.start();

}

}

A i=1

B i=11

B i=12

A i=2

A i=3

B i=13

B i=14

A i=4

B i=15

A i=5

A i=6

B i=16

B i=17

A i=7

A i=8

B i=18

A i=9

B i=19

A i=10

B i=20

15.内置类与同步:实验2

示例:InnerClass1内部类有两个方法,InnerClass2内部类有一个方法,由于synchronized(class2)同步代码块对class2上锁,所以其他线程就只能以同步的方式调用class2中的静态同步方法。这里要注意的是i和j是交替异步打印的,但是k是按顺序同步打印的。

packagetest;public classOutClass {static classInnerClass1 {public voidmethod1(InnerClass2 class2) {

String threadName=Thread.currentThread().getName();synchronized(class2) {

System.out.println(threadName+ " 进入InnerClass1类中的method1方法");for (int i = 0; i < 10; i++) {

System.out.println("i=" +i);try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

System.out.println(threadName+ " 离开InnerClass1类中的method1方法");

}

}public synchronized voidmethod2() {

String threadName=Thread.currentThread().getName();

System.out.println(threadName+ " 进入InnerClass1类中的method2方法");for (int j = 0; j < 10; j++) {

System.out.println("j=" +j);try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

System.out.println(threadName+ " 离开InnerClass1类中的method2方法");

}

}static classInnerClass2 {public synchronized voidmethod1() {

String threadName=Thread.currentThread().getName();

System.out.println(threadName+ " 进入InnerClass2类中的method1方法");for (int k = 0; k < 10; k++) {

System.out.println("k=" +k);try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

}

System.out.println(threadName+ " 离开InnerClass2类中的method1方法");

}

}

}

packagetest;importtest.OutClass.InnerClass1;importtest.OutClass.InnerClass2;public classRun {public static voidmain(String[] args) {final InnerClass1 in1 = newInnerClass1();final InnerClass2 in2 = newInnerClass2();// //

// // Thread t1 = new Thread(newRunnable() {public voidrun() {

in1.method1(in2);

}

},"T1");// //

// // Thread t2 = new Thread(newRunnable() {public voidrun() {

in1.method2();

}

},"T2");// //

// // Thread t3 = new Thread(newRunnable() {public voidrun() {

in2.method1();

}

},"T3");// //

// //t1.start();

t2.start();

t3.start();

}

}

T1 进入InnerClass1类中的method1方法

i=0

T2 进入InnerClass1类中的method2方法

j=0

i=1

j=1

i=2

j=2

i=3

j=3

i=4

j=4

i=5

j=5

i=6

j=6

i=7

j=7

i=8

j=8

i=9

j=9

T1 离开InnerClass1类中的method1方法

T3 进入InnerClass2类中的method1方法

k=0

T2 离开InnerClass1类中的method2方法

k=1

k=2

k=3

k=4

k=5

k=6

k=7

k=8

k=9

T3 离开InnerClass2类中的method1方法

16.锁对象的改变

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

示例:A线程在B线程启动50毫秒后启动,A线程一开始取得的锁是“123”,然后在执行过程中将锁对象修改为“456”,然后B线程取得的锁是“456”,由于锁对象不一样,所以是异步的。

packagemyservice;public classMyService {private String lock = "123";public voidtestMethod() {try{synchronized(lock) {

System.out.println(Thread.currentThread().getName()+ " begin "+System.currentTimeMillis());

lock= "456";

Thread.sleep(2000);

System.out.println(Thread.currentThread().getName()+ " end "+System.currentTimeMillis());

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packageextthread;importmyservice.MyService;public class ThreadB extendsThread {privateMyService service;publicThreadB(MyService service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.testMethod();

}

}

packageextthread;importmyservice.MyService;public class ThreadA extendsThread {privateMyService service;publicThreadA(MyService service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.testMethod();

}

}

A begin 1525399414038

B begin 1525399414088

A end 1525399416039

B end 1525399416089

示例2:去掉50毫秒后,由于A和B共同争抢的锁是“123”,所以具有相同的锁对象,因此就是同步的。

packagetest.run;importmyservice.MyService;importextthread.ThreadA;importextthread.ThreadB;public classRun2 {public static void main(String[] args) throwsInterruptedException {

MyService service= newMyService();

ThreadA a= newThreadA(service);

a.setName("A");

ThreadB b= newThreadB(service);

b.setName("B");

a.start();

b.start();

}

}

begin 1525399721246

A end 1525399723247

B begin 1525399723247

B end 1525399725247

示例3:从输出结果可以看出,虽然两个线程都共同对同一个userinfo对象进行锁,但是由于userinfo不变,即使在执行的过程中userinfo对象的属性值发生了变化,但对象还是没有发生变化,运行的结果还是同步的。

packageentity;public classUserinfo {privateString username;privateString password;publicUserinfo() {super();

}publicUserinfo(String username, String password) {super();this.username =username;this.password =password;

}publicString getUsername() {returnusername;

}public voidsetUsername(String username) {this.username =username;

}publicString getPassword() {returnpassword;

}public voidsetPassword(String password) {this.password =password;

}

}

packageservice;importentity.Userinfo;public classService {public voidserviceMethodA(Userinfo userinfo) {synchronized(userinfo) {try{

System.out.println(Thread.currentThread().getName());

userinfo.setUsername("abcabcabc");

Thread.sleep(3000);

System.out.println("end! time=" +System.currentTimeMillis());

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

packageextthread;importservice.Service;importentity.Userinfo;public class ThreadA extendsThread {privateService service;privateUserinfo userinfo;publicThreadA(Service service,

Userinfo userinfo) {super();this.service =service;this.userinfo =userinfo;

}

@Overridepublic voidrun() {

service.serviceMethodA(userinfo);

}

}

packageextthread;importservice.Service;importentity.Userinfo;public class ThreadB extendsThread {privateService service;privateUserinfo userinfo;publicThreadB(Service service,

Userinfo userinfo) {super();this.service =service;this.userinfo =userinfo;

}

@Overridepublic voidrun() {

service.serviceMethodA(userinfo);

}

}

packagetest.run;importservice.Service;importentity.Userinfo;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {try{

Service service= newService();

Userinfo userinfo= newUserinfo();

ThreadA a= newThreadA(service, userinfo);

a.setName("a");

a.start();

Thread.sleep(50);

ThreadB b= newThreadB(service, userinfo);

b.setName("b");

b.start();

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

a

end! time=1525399951169

b

end! time=1525399954169

三、volatile关键字

volatile关键字的主要作用是使变量在多个线程间可见。

1.关键字volatile与死循环

示例:程序开始运行后出现停不下来的情况,这是因为main线程一直在处理while()循环,无法跳出while循环,然后无法修改参数的值为false,导致程序无法执行后面的代码。

packageprintstring;public classPrintString {private boolean isContinuePrint = true;public booleanisContinuePrint() {returnisContinuePrint;

}public void setContinuePrint(booleanisContinuePrint) {this.isContinuePrint =isContinuePrint;

}public voidprintStringMethod() {try{while (isContinuePrint == true) {

System.out.println("run printStringMethod threadName="+Thread.currentThread().getName());

Thread.sleep(1000);

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

packagetest;importprintstring.PrintString;public classRun {public static voidmain(String[] args) {

PrintString printStringService= newPrintString();

printStringService.printStringMethod();

System.out.println("我要停止它!="+Thread.currentThread().getName());

printStringService.setContinuePrint(false);

}

}

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

run printStringMethod threadName=main

...

2.解决同步死循环

示例:通过实现Runnable接口将PrintString类定义为一个线程,然后通过线程修改while循环的判断值为false,可以使while循环跳出。

packageprintstring;public class PrintString implementsRunnable {private boolean isContinuePrint = true;public booleanisContinuePrint() {returnisContinuePrint;

}public void setContinuePrint(booleanisContinuePrint) {this.isContinuePrint =isContinuePrint;

}public voidprintStringMethod() {try{while (isContinuePrint == true) {

System.out.println("run printStringMethod threadName="+Thread.currentThread().getName());

Thread.sleep(1000);

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

@Overridepublic voidrun() {

printStringMethod();

}

}

packagetest;importprintstring.PrintString;public classRun {public static voidmain(String[] args) {

PrintString printStringService= newPrintString();newThread(printStringService).start();

System.out.println("我要停止它!stopThread="+Thread.currentThread().getName());

printStringService.setContinuePrint(false);

}

}

我要停止它!stopThread=main

run printStringMethod threadName=Thread-0

3.解决异步死循环

关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

示例1:private boolean isRunning = true;没有volatile关键字的情况。在Win10结合JDK64bit的环境中,使用Eclipse开发环境运行示例程序后没有问题,未出现死循环。

packageextthread;public class RunThread extendsThread {private boolean isRunning = true;public booleanisRunning() {returnisRunning;

}public void setRunning(booleanisRunning) {this.isRunning =isRunning;

}

@Overridepublic voidrun() {

System.out.println("进入run方法了!");while (isRunning == true) {

}

System.out.println("线程被停止了!");

}

}

packagetest;importextthread.RunThread;public classRun {public static voidmain(String[] args) {try{

RunThread thread= newRunThread();

thread.start();

Thread.sleep(1000);

thread.setRunning(false);

System.out.println("已经赋值为false");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

进入run方法了!

已经赋值为false

线程被停止了!

示例2:private boolean isRunning = true;没有volatile关键字的情况。将示例1的程序运行在JVM设置为Server服务器的环境中,即

d5a12003ba40541282e6147cbbb0694f.png

点击Run后,出现的输出是:

进入run方法了!

已经赋值为false

示例3:volatile private boolean isRunning = true;有volatile关键字的情况。将示例1的程序运行在JVM设置为Server服务器的环境中,不再出现死循环。

进入run方法了!

已经赋值为false

线程被停止了!

原因分析:

造成服务器环境下出现死循环的原因是,在启动RunThread.java线程时,变量private boolean isRunning = true;存在于公共堆栈及线程的私有堆栈中。为了线程运行的效率,服务器模式下,线程一直在私有堆栈中取得的值时true,而代码setContinuePrint(false);虽然被执行,但是更新的却是共有堆栈中的值,所以就一直是死循环状态。这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这个问题要使用volatile关键字,它主要的作用就是当线程访问isRunning这个变量时,强制从公共堆栈中进行取值。简单来说就是,强制从主内存而不是从线程的工作内存中进行写入和读取。

使用volatile关键字增加了实例变量在多个线程之间的可见性,但最致命的缺点是不支持原子性。

关键字synchronized和volatile进行比较:

①关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。

②多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。

③volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。

④关键字volatile解决的是变量在多个线程之间的可见性, 而synchronized解决的是多个线程之间访问资源的同步性。

af3b51c3fc63ea6ecc793b658a8127cb.png

bf531e62d927d4993c076463e0faf472.png

4.volatile非原子的特性

示例1:由于volatile不具备同步性,也就不具备原子性,从输出结果可以看出最后的运行结果值不是10000

packageextthread;public class MyThread extendsThread {volatile public static intcount;private static voidaddCount() {for (int i = 0; i < 100; i++) {

count++;

}

System.out.println("count=" +count);

}

@Overridepublic voidrun() {

addCount();

}

}

packagetest.run;importextthread.MyThread;public classRun {public static voidmain(String[] args) {

MyThread[] mythreadArray= new MyThread[100];for (int i = 0; i < 100; i++) {

mythreadArray[i]= newMyThread();

}for (int i = 0; i < 100; i++) {

mythreadArray[i].start();

}

}

}

...

count=8197

count=8097

count=7797

count=7697

count=7497

count=7397

count=7297

示例2:修改MyThread类,在addCount方法上加上synchronized关键字。从输出结果来看,运行结果值是10000

packageextthread;public class MyThread extendsThread {volatile public static intcount;synchronized private static voidaddCount() {for (int i = 0; i < 100; i++) {

count++;

}

System.out.println("count=" +count);

}

@Overridepublic voidrun() {

addCount();

}

}

...

count=9400

count=9500

count=9600

count=9700

count=9800

count=9900

count=10000

示例3:修改MyThread类,删除count的volatile关键字,输出结果也是正确的。

packageextthread;public class MyThread extendsThread {public static intcount;synchronized private static voidaddCount() {for (int i = 0; i < 100; i++) {

count++;

}

System.out.println("count=" +count);

}

@Overridepublic voidrun() {

addCount();

}

}

...

count=9400

count=9500

count=9600

count=9700

count=9800

count=9900

count=10000

分析:

volatile关键字主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。

volatile关键字提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。但是,如果修改实例变量中的数据,比如i++也就是i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全的。

表达式i++的操作步骤是

从内存中取出i的值;

计算i的值;

将i的值写到内存中;

假如在第二步计算值的时候,另外一个线程也修改了i的值,那么这个时候就会出现脏数据。解决的办法就是使用synchronized关键字,所以说volatile本身并不处理数据的原子性,而是强制对数据的读写即使影响到主内存。

在示例3中也可以发现,如果加了synchronized同步关键字,也就没有必要再使用volatile关键字来声明count变量了。

5.使用原子类进行i++操作

除了在i++操作时使用synchronized关键字实现同步外,还可以使用

原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic)类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全。

示例:从运行结果来看,可以成功累加到5000。

packageextthread;importjava.util.concurrent.atomic.AtomicInteger;public class AddCountThread extendsThread {private AtomicInteger count = new AtomicInteger(0);

@Overridepublic voidrun() {for (int i = 0; i < 10000; i++) {

System.out.println(count.incrementAndGet());

}

}

}

packagetest;importextthread.AddCountThread;public classRun {public static voidmain(String[] args) {

AddCountThread countService= newAddCountThread();

Thread t1= newThread(countService);

t1.start();

Thread t2= newThread(countService);

t2.start();

Thread t3= newThread(countService);

t3.start();

Thread t4= newThread(countService);

t4.start();

Thread t5= newThread(countService);

t5.start();

}

}

...

49993

49994

49995

49996

49997

49998

49999

50000

6.原子类也并不完全安全

原子类在具有逻辑性的情况下输出结果也具有随机性。

示例1:从输出结果可以看出,打印顺序出错了,应该每加1次100再加1次1,是因为aiRef.addAndGet(100)和aiRef.addAndGet(1)方法是原子的,但是方法和方法之间的调用却不是原子的。

packageservice;importjava.util.concurrent.atomic.AtomicLong;public classMyService {public static AtomicLong aiRef = newAtomicLong();public voidaddNum() {

System.out.println(Thread.currentThread().getName()+ "加了100之后的值是:"+ aiRef.addAndGet(100));

aiRef.addAndGet(1);

}

}

packageextthread;importservice.MyService;public class MyThread extendsThread {privateMyService mySerivce;publicMyThread(MyService mySerivce) {super();this.mySerivce =mySerivce;

}

@Overridepublic voidrun() {

mySerivce.addNum();

}

}

packagetest.run;importservice.MyService;importextthread.MyThread;public classRun {public static voidmain(String[] args) {try{

MyService service= newMyService();

MyThread[] array= new MyThread[5];for (int i = 0; i < array.length; i++) {

array[i]= newMyThread(service);

}for (int i = 0; i < array.length; i++) {

array[i].start();

}

Thread.sleep(1000);

System.out.println(service.aiRef.get());

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

Thread-0加了100之后的值是:100

Thread-2加了100之后的值是:300

Thread-1加了100之后的值是:200

Thread-3加了100之后的值是:403

Thread-4加了100之后的值是:504

505

示例2:修改示例1中的MyService类,将addNum方法加上同步关键字。从输出结果来看,结果是正确的,结果是505还保证在过程中累加的顺序也是正确的。

packageservice;importjava.util.concurrent.atomic.AtomicLong;public classMyService {public static AtomicLong aiRef = newAtomicLong();synchronized public voidaddNum() {

System.out.println(Thread.currentThread().getName()+ "加了100之后的值是:"+ aiRef.addAndGet(100));

aiRef.addAndGet(1);

}

}

Thread-0加了100之后的值是:100

Thread-3加了100之后的值是:201

Thread-2加了100之后的值是:302

Thread-1加了100之后的值是:403

Thread-4加了100之后的值是:504

505

7.synchronized代码块有volatile同步的功能

关键字synchronized可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

示例1:没有添加关键字synchronized并在-server服务器模式运行。结果出现了死循环,得到这个结果是因为各线程间的数据值没有可视性造成的。

packageservice;public classService {private boolean isContinueRun = true;public voidrunMethod() {//String anyString = new String();

while (isContinueRun == true) {//synchronized (anyString) {//}

}

System.out.println("ͣ停下来了!");

}public voidstopMethod() {

isContinueRun= false;

}

}

packageextthread;importservice.Service;public class ThreadA extendsThread {privateService service;publicThreadA(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.runMethod();

}

}

packageextthread;importservice.Service;public class ThreadB extendsThread {privateService service;publicThreadB(Service service) {super();this.service =service;

}

@Overridepublic voidrun() {

service.stopMethod();

}

}

packagetest;importservice.Service;importextthread.ThreadA;importextthread.ThreadB;public classRun {public static voidmain(String[] args) {try{

Service service= newService();

ThreadA a= newThreadA(service);

a.start();

Thread.sleep(1000);

ThreadB b= newThreadB(service);

b.start();

System.out.println("已经发起停止的命令了!");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

已经发起停止的命令了!

示例2:添加synchronized同步代码块并在-server服务器模式运行。结果未出现死循环,得到这个结果是因为synchronized关键字可以具有可视性。

packageservice;public classService {private boolean isContinueRun = true;public voidrunMethod() {

String anyString= newString();while (isContinueRun == true) {synchronized(anyString) {

}

}

System.out.println("ͣ停下来了!");

}public voidstopMethod() {

isContinueRun= false;

}

}

已经发起停止的命令了!

停下来了!

synchronized关键字可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特征,互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值