Java多线程编程核心技术---对象及变量的并发访问(一)

时间:2023-08-15 16:19:20
synchronized同步方法

“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是渠道的数据其实是被更改过的。而“线程安全”就是获得的实例变量的值是经过同步处理的,不会出现脏读现象。

方法内的变量为线程安全

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

public class HasSelfPrivateNum {
public void add(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=" + username + ", num=" + num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.add("a");
}
}
public class ThreadB extends  Thread{
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.add("b");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRf = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(numRf);
threadA.start();
ThreadB threadB = new ThreadB(numRf);
threadB.start();
}
}

打印结果如下:

a set over
b set over
username=b, num=200
username=a, num=100

方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。


实例变量非线程安全

如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况。

将上面的代码做如下更改:

public class HasSelfPrivateNum {
private int num = 0;//将num改为成员变量
public void add(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=" + username + ", num=" + num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

则会出现以下结果:

a set over
b set over
username=b, num=200
username=a, num=200

两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,择优可能出现“非线程安全”的问题。

将以上代码做如下更改:

public class HasSelfPrivateNum {
private int num = 0;
synchronized public void add(String username){//将add方法改为同步方法
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=" + username + ", num=" + num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

打印结果如下:

a set over
username=a, num=100
b set over
username=b, num=200

多个对象多个锁
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void add(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=" + username + ", num=" + num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
} public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.add("a");
}
} public class ThreadB extends Thread{
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.add("b");
}
} public class Run {
public static void main(String[] args) {
HasSelfPrivateNum numRfA = new HasSelfPrivateNum();
HasSelfPrivateNum numRfB = new HasSelfPrivateNum();
ThreadA threadA = new ThreadA(numRfA);
threadA.start();
ThreadB threadB = new ThreadB(numRfB);
threadB.start();
}
}

打印结果如下:

a set over
b set over
username=b, num=200
username=a, num=100

上面的代码是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步方式运行的。synchronized关键字取得的锁是对象锁,而不是把一段代码或方法当做锁,所以在以上的代码中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有改方法所属对象的锁Lock,那么其他线程只能处于等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则JVM会创建多个锁。


synchronized方法与锁对象
public class MyObject {
public void methodA(){
try {
System.out.println("MethodA begins, threadName=" + Thread.currentThread().getName() + ", time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("MethodA end, time=" + System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
} public class Thread1 extends Thread {
private MyObject myObject; public Thread1(MyObject myObject){
super();
this.myObject = myObject;
} @Override
public void run() {
super.run();
myObject.methodA();
}
} public class Thread2 extends Thread {
private MyObject myObject; public Thread2(MyObject myObject){
super();
this.myObject = myObject;
} @Override
public void run() {
super.run();
myObject.methodA();
}
} public class ThreadMain {
public static void main(String[] args) {
MyObject myObject = new MyObject();
Thread1 t1 = new Thread1(myObject);
t1.setName("A");
Thread2 t2 = new Thread2(myObject);
t2.setName("B");
t1.start();
t2.start();
}
}

打印结果如下:

MethodA begins, threadName=A, time=1463360647823
MethodA begins, threadName=B, time=1463360647823
MethodA end, time=1463360648823
MethodA end, time=1463360648824

从打印结果来看,t1和t2是以异步方式执行methodA的,这两个线程几乎是在同一个时间段内执行了methodA方法。将以上代码做如下更改:

public class MyObject {
synchronized public void methodA(){//同步方法
try {
System.out.println("MethodA begins, threadName=" + Thread.currentThread().getName() + ", time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("MethodA end, time=" + System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

打印结果如下:

MethodA begins, threadName=A, time=1463360888320
MethodA end, time=1463360889320
MethodA begins, threadName=B, time=1463360889320
MethodA end, time=1463360890320

从打印结果来看,t1和t2是以排队方式进入methodA方法的,当t1执行完methodA方法后释放对象锁,t2才能拿到对象锁进入methodA方法。

调用关键字synchronized声明的方法一定是排队运行的。另外,只有共享资源的读写访问才需要同步化,如果不是共享资源则没有同步的必要。

将以上代码做如下更改:

public class MyObject {
synchronized public void methodA(){//methodA同步
try {
System.out.println("MethodA begins, threadName=" + Thread.currentThread().getName() + ", time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("MethodA end, time=" + System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
} public void methodB(){//methodB不同步
try {
System.out.println("methodB begins, threadName=" + Thread.currentThread().getName() + ", time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("methodB end, time=" + System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
} public class Thread1 extends Thread {
private MyObject myObject; public Thread1(MyObject myObject){
super();
this.myObject = myObject;
} @Override
public void run() {
super.run();
myObject.methodA();//调用methodA方法
}
} public class Thread2 extends Thread {
private MyObject myObject; public Thread2(MyObject myObject){
super();
this.myObject = myObject;
} @Override
public void run() {
super.run();
myObject.methodB();//调用methodB方法
}
} public class ThreadMain {
public static void main(String[] args) {
MyObject myObject = new MyObject();
Thread1 t1 = new Thread1(myObject);
t1.setName("A");
Thread2 t2 = new Thread2(myObject);
t2.setName("B");
t1.start();
t2.start();
}
}

打印结果如下:

MethodA begins, threadName=A, time=1463361282231
methodB begins, threadName=B, time=1463361282232
methodB end, time=1463361283232
MethodA end, time=1463361283232

从打印结果可以看出,虽然线程t1线程持有了myObject对象的锁,但是线程t2仍然完全可以以异步的方式调用非synchronized类型的方法。

此时将methodB方法也改为synchronized方法

public class MyObject {
synchronized public void methodA(){//methodA同步
try {
System.out.println("MethodA begins, threadName=" + Thread.currentThread().getName() + ", time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("MethodA end, time=" + System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
} synchronized public void methodB(){//methodB不同步
try {
System.out.println("methodB begins, threadName=" + Thread.currentThread().getName() + ", time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("methodB end, time=" + System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

打印结果如下:

MethodA begins, threadName=A, time=1463361523530
MethodA end, time=1463361524530
methodB begins, threadName=B, time=1463361524530
methodB end, time=1463361525530

可见methodA和methodB为排队运行。

以上代码结论

  • 线程1先持有myObject对象的Lock锁,线程2可以以异步方式调用myObject对象中的非synchronized方法
  • 线程1先持有myObject对象的Lock锁,线程2如果在这时调用myObject对象中的synchronized方法则需等待,也就是同步。

脏读

虽然在赋值时进行了同步,但是在取值时有可能出现一些意想不到的意外,这种情况就是脏读(dirtyRead)。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。

public class PublicVar {
private String username = "a";
private String password = "aa";
synchronized public void setValue(String username, String password){
try {
this.username = username;
Thread.sleep(1000);
this.password = password;
System.out.println("setValue method, threadName=" + Thread.currentThread().getName());
System.out.println("username=" + username + ", password=" + password);
} catch (Exception e) {
e.printStackTrace();
}
} public void getValue() {
System.err.println("setValue method, threadName=" + Thread.currentThread().getName());
System.err.println("username=" + username + ", password=" + password);
}
} public class Thread1 extends Thread {
private PublicVar publicVar; public Thread1(PublicVar publicVar) {
super();
this.publicVar = publicVar;
} @Override
public void run() {
super.run();
publicVar.setValue("b", "bb");
} public static void main(String[] args) {
try {
PublicVar publicVar = new PublicVar();
Thread1 t1 = new Thread1(publicVar);
t1.start();
Thread.sleep(500);//脏读情况受此值影响
publicVar.getValue();
} catch (Exception e) {
e.printStackTrace();
}
}
}

打印结果如下:

setValue method, threadName=main
username=b, password=aa
setValue method, threadName=Thread-0
username=b, password=bb

虽然线程只能以排队方式进入setValue()方法,但是getValue()方法并不是同步的,所以getValue()方法可以在任何时候被调用。以上代码中,在main方法中调用getValue()时,setValue()还没有执行完毕,所以出现脏读。如果将以上代码做如下更改,则不会出现脏读情况:

public class PublicVar {
private String username = "a";
private String password = "aa";
synchronized public void setValue(String username, String password){
try {
this.username = username;
Thread.sleep(1000);
this.password = password;
System.out.println("setValue method, threadName=" + Thread.currentThread().getName());
System.out.println("username=" + username + ", password=" + password);
} catch (Exception e) {
e.printStackTrace();
}
} synchronized public void getValue() {
System.err.println("setValue method, threadName=" + Thread.currentThread().getName());
System.err.println("username=" + username + ", password=" + password);
}
}

此时打印结果如下:

setValue method, threadName=Thread-0
setValue method, threadName=main
username=b, password=bb
username=b, password=bb

可见,方法setValue()和getValue()被一次执行,不会出现脏读情况。

当A线程调用anyObject对象加入synchronized关键的X方法时,A线程就获得了X方法锁,更准确地将就是获得了对象的锁,所以其他线程必须等到A线程执行完毕才可以调用X方法,但是B线程可以随意调用其他的非synchronized同步方法。

当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法。B线程如果调用声明了synchronized关键字声明的非X方法时,必须等A线程将X方法执行完,也就是对象锁释放之后才可以调用。这时A线程已经执行了一个完整的任务,也就是说username和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。


synchronized锁重入

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

public class Service {

	synchronized public void service1() {
System.out.println("service1");
service2();
} synchronized public void service2() {
System.out.println("service2");
service3();
} synchronized public void service3() {
System.out.println("service3");
}
}
public class MyThread extends Thread {
@Override
public void run() {
Service service = new Service();
service.service1();
} public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}

控制台打印结果如下:

service1
service2
service3

“可重入锁”的概念是:自己可以再次获取自己的内部锁。

可重入锁也支持在父子类继承的环境中。

public class Parent {

	public int i = 10;
synchronized public void method1(){
try {
i--;
System.out.println("parent i=" + i);
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
} public class Son extends Parent {
synchronized public void method2(){
try {
while (i > 0) {
i--;
System.out.println("Son i=" + i);
Thread.sleep(100);
this.method1();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} public class MyThread extends Thread {
@Override
public void run() {
Son son = new Son();
son.method2();
} public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}

打印结果如下:

Son i=9
parent i=8
Son i=7
parent i=6
Son i=5
parent i=4
Son i=3
parent i=2
Son i=1
parent i=0

可见,当存在父子类继承关系时,子类完全可以通过“可重入锁”调用父类的同步方法的。


出现异常,锁自动释放

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

public class Service {
synchronized public void testMethod(){
if (Thread.currentThread().getName().equals("a")) {
System.out.println("ThreadName=a, beginTime=" + System.currentTimeMillis());
System.out.println("Exception time=" + System.currentTimeMillis());
Integer.parseInt("hello");//抛异常
}else {
System.out.println("Other thread begin Time =" + System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread {
private Service service; public ThreadA(Service service) {
super();
this.service = service;
} @Override
public void run() {
service.testMethod();
}
} public class ThreadB extends Thread {
private Service service; public ThreadB(Service service) {
super();
this.service = service;
} @Override
public void run() {
service.testMethod();
}
}
public class Test {
public static void main(String[] args) {
try {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
Thread.sleep(500);
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}

控制台打印的结果如下:

ThreadName=a, beginTime=1463443586301
Exception in thread "a" Exception time=1463443586301
java.lang.NumberFormatException: For input string: "hello"
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at com.umgsai.thread4.Service.testMethod(Service.java:13)
at com.umgsai.thread4.ThreadA.run(ThreadA.java:13)
Other thread begin Time =1463443586801

线程a出现异常并且释放锁,线程b进入方法正常打印。可见出现异常时,锁会被自动释放。


同步不具有继承性

同步不可以继承

public class Main {
synchronized public void serviceMethod(){
try {
System.out.println("Main, ThreadName=" + Thread.currentThread().getName() + ",sleep begin time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("Main, ThreadName=" + Thread.currentThread().getName() + ",sleep end time=" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Sub extends Main {
@Override
public void serviceMethod() {
try {
System.out.println("Sub, ThreadName=" + Thread.currentThread().getName() + ",sleep begin time=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println("Sub, ThreadName=" + Thread.currentThread().getName() + ",sleep end time=" + System.currentTimeMillis());
super.serviceMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThreadA extends Thread {
private Sub sub;
public MyThreadA(Sub sub) {
super();
this.sub = sub;
} @Override
public void run() {
sub.serviceMethod();
}
}
public class MyThreadB extends Thread {
private Sub sub;
public MyThreadB(Sub sub) {
super();
this.sub = sub;
} @Override
public void run() {
sub.serviceMethod();
}
}
public class Test {
public static void main(String[] args) {
Sub sub = new Sub();
MyThreadA a = new MyThreadA(sub);
a.setName("A");
a.start();
MyThreadB b = new MyThreadB(sub);
b.setName("B");
b.start();
}
}

控制台打印结果如下:

Sub, ThreadName=A,sleep begin time=1463490849433
Sub, ThreadName=B,sleep begin time=1463490849434
Sub, ThreadName=A,sleep end time=1463490850434
Main, ThreadName=A,sleep begin time=1463490850434
Sub, ThreadName=B,sleep end time=1463490850435
Main, ThreadName=A,sleep end time=1463490851435
Main, ThreadName=B,sleep begin time=1463490851435
Main, ThreadName=B,sleep end time=1463490852435

由打印结果可知,A、B线程以异步方式进入Sub中的serviceMethod方法,以同步方式进入Main中的serviceMethod方法。同步方法不可被集成,还得在子类中添加关键字synchronized。


synchronized同步语句块

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

synchronized方法的弊端
public class Task {
private String getData1;
private String getData2;
public synchronized void doLongTimeTask() {
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 (Exception e) {
e.printStackTrace();
}
}
}
public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
} public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
} @Override
public void run() {
super.run();
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
};
} public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
} @Override
public void run() {
super.run();
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
};
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
try {
Thread.sleep(10000);
} catch (Exception e) {
// TODO: handle exception
}
long beginTime = CommonUtils.beginTime1;
if(CommonUtils.beginTime2 < CommonUtils.beginTime1){
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if(CommonUtils.endTime2 > CommonUtils.endTime1){
endTime = CommonUtils.endTime2;
}
System.out.println("耗时:" + (endTime - beginTime) + "ms");
}
}

控制台打印结果如下:

begin task...
长时间处理任务后的返回值1 threadName=Thread-0
长时间处理任务后的返回值2 threadName=Thread-0
end task...
begin task...
长时间处理任务后的返回值1 threadName=Thread-1
长时间处理任务后的返回值2 threadName=Thread-1
end task...
耗时:6001ms

从运行时间上看,弊端很明显。

synchronized同步代码块的使用

将以上代码中的长时间任务放到synchronized代码块中。

public class Task {
private String getData1;
private String getData2; public void doLongTimeTask() {
try {
System.out.println("begin task..." + System.currentTimeMillis());
Thread.sleep(3000);
synchronized (this) {
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..." + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}

此时执行结果如下:

begin task...1463493214260
begin task...1463493214260
长时间处理任务后的返回值1 threadName=Thread-1
长时间处理任务后的返回值1 threadName=Thread-0
长时间处理任务后的返回值2 threadName=Thread-0
长时间处理任务后的返回值2 threadName=Thread-0
end task...1463493217262
end task...1463493217262
耗时:3003ms

由上面的结果可知,当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。


一半异步,一半同步
public class Task {

	public void doLongTimeTask() {
for (int i = 0; i < 100; i++) {
System.err.println("异步执行,线程名:" + Thread.currentThread().getName() + "---" + i);
}
synchronized(this){
for (int i = 0; i < 100; i++) {
System.out.println("同步执行,线程名:" + Thread.currentThread().getName() + "---" + i);
}
}
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
} @Override
public void run() {
super.run();
task.doLongTimeTask();
};
}
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
} @Override
public void run() {
super.run();
task.doLongTimeTask();
};
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}

打印结果如下:

...
异步执行,线程名:Thread-0---62
异步执行,线程名:Thread-1---46
异步执行,线程名:Thread-0---63
异步执行,线程名:Thread-1---47
异步执行,线程名:Thread-0---64
异步执行,线程名:Thread-1---48
异步执行,线程名:Thread-1---49
异步执行,线程名:Thread-0---65
异步执行,线程名:Thread-1---50
异步执行,线程名:Thread-0---66
...
同步执行,线程名:Thread-0---14
同步执行,线程名:Thread-0---15
同步执行,线程名:Thread-0---16
同步执行,线程名:Thread-0---17
同步执行,线程名:Thread-0---18
同步执行,线程名:Thread-0---19
同步执行,线程名:Thread-0---20
...
同步执行,线程名:Thread-1---47
同步执行,线程名:Thread-1---48
同步执行,线程名:Thread-1---49
同步执行,线程名:Thread-1---50
同步执行,线程名:Thread-1---51
同步执行,线程名:Thread-1---52
同步执行,线程名:Thread-1---53
...

可见,不在synchronized块中的就是异步执行,在synchronized块中的是同步执行。


synchronized代码块间的同步性

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

public class ObjectService {
public void serviceMethodA() {
try {
synchronized (this) {
System.out.println("serviceMethodA begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("serviceMethodA end time=" + System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
} public void serviceMethodB() {
synchronized (this) {
System.out.println("serviceMethodB begin time=" + System.currentTimeMillis());
System.out.println("serviceMethodB end time=" + System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread {

	private ObjectService service;
public ThreadA(ObjectService service) {
super();
this.service = service;
} @Override
public void run() {
super.run();
service.serviceMethodA();
}
} public class ThreadB extends Thread { private ObjectService service;
public ThreadB(ObjectService service) {
super();
this.service = service;
} @Override
public void run() {
super.run();
service.serviceMethodB();
}
}
public class Run {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}

控制台打印结果如下:

serviceMethodA begin time=1463615305982
serviceMethodA end time=1463615307982
serviceMethodB begin time=1463615307982
serviceMethodB end time=1463615307982

由打印结果可见,serviceMethodA和serviceMethodB是同步执行的。


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

和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

public class Task {

	public void doLongTimeTask() {
synchronized(this){
for (int i = 0; i < 100; i++) {
System.out.println("同步执行,线程名:" + Thread.currentThread().getName() + "---" + i);
}
}
} public void otherMethod() {
System.err.println("Run otherMethod....");
}
}
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
} @Override
public void run() {
super.run();
task.doLongTimeTask();
};
} public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
} @Override
public void run() {
super.run();
task.otherMethod();
};
}
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}

控制台打印结果如下:

同步执行,线程名:Thread-0---0
同步执行,线程名:Thread-0---1
Run otherMethod....
同步执行,线程名:Thread-0---2
同步执行,线程名:Thread-0---3
同步执行,线程名:Thread-0---4
同步执行,线程名:Thread-0---5
...

将Task做如下修改:

public class Task {

	public void doLongTimeTask() {
synchronized(this){
for (int i = 0; i < 100; i++) {
System.out.println("同步执行,线程名:" + Thread.currentThread().getName() + "---" + i);
}
}
} synchronized public void otherMethod() {
System.err.println("Run otherMethod....");
}
}

重新运行,控制台打印结果如下:

...
同步执行,线程名:Thread-0---95
同步执行,线程名:Thread-0---96
同步执行,线程名:Thread-0---97
同步执行,线程名:Thread-0---98
同步执行,线程名:Thread-0---99
Run otherMethod....

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

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

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

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

Java支持对“任意对象”作为“对象监视器”来实习同步的功能,这个“任意对象”大多数是实例遍历及方法的参数。使用格式为synchronized(非this对象)。

synchronized(非this对象)格式的作用只有一种:synchronized(非this对象x)同步代码块。

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

验证第1点

public class Service {
private String username;
private String password;
private String anyString = new String();
public void setValue(String usernameParam, String passwordParam) {
try {
synchronized (anyString) {
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
username = usernameParam;
Thread.sleep(3000);
password = passwordParam;
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
System.out.println(username + "-" + password);
} } catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {

	private Service service;
public ThreadA(Service service) {
super();
this.service = service;
} @Override
public void run() {
super.run();
service.setValue("a", "aaa");
}
} public class ThreadB extends Thread { private Service service;
public ThreadB(Service service) {
super();
this.service = service;
} @Override
public void run() {
super.run();
service.setValue("b", "b");
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}

控制台打印结果如下:

A在1463617634358进入同步块
A在1463617637360离开同步块
a-aaa
B在1463617637360进入同步块
B在1463617640361离开同步块
b-b

锁非this对象具有一定的优点:如果在一个类中有多个synchronized方法,这是虽然能实现同步,但会受到阻塞,影响运行效率。但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步执行的,不与其他锁this同步方法争抢this锁,可大大提高运行效率。

将Service做如下修改:

public class Service {
private String username;
private String password;
public void setValue(String usernameParam, String passwordParam) {
try {
String anyString = new String();
synchronized (anyString) {
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
username = usernameParam;
Thread.sleep(3000);
password = passwordParam;
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
System.out.println(username + "-" + password);
} } catch (Exception e) {
e.printStackTrace();
}
}
}

此时控制台打印结果如下:

A在1463618002731进入同步块
B在1463618002732进入同步块
A在1463618005733离开同步块
b-b
B在1463618005733离开同步块
b-b

A、B两个线程此时是异步执行,因为不是同一个锁。

可见,使用synchronized(非this对象x)同步代码块时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行结果就是异步调用了,就会交叉运行。以上代码上将anyStirng放在setValue()方法内部,A线程进入setValue()方法时的anyString与B线程进入setValue()方法时的anyString不是同一个对象。

再看一个例子。

public class Service {
private String anyString = new String();
public void methodA() {
try {
synchronized (anyString) {
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入methodA中的同步块");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开methodA中的同步块");
} } catch (Exception e) {
e.printStackTrace();
}
} synchronized public void methodB() {
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入methodB同步方法");
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开methodB同步方法");
}
}
public class ThreadA extends Thread {

	private Service service;
public ThreadA(Service service) {
super();
this.service = service;
} @Override
public void run() {
super.run();
service.methodA();
}
} public class ThreadB extends Thread { private Service service;
public ThreadB(Service service) {
super();
this.service = service;
} @Override
public void run() {
super.run();
service.methodB();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}

控制台打印结果如下:

A在1463618803891进入methodA中的同步块
B在1463618803892进入methodB同步方法
B在1463618803892离开methodB同步方法
A在1463618806893离开methodA中的同步块

由于对象监视器不同,所以运行结果是异步的。

同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的,这样极易出现“脏读”问题。

public class MyList {
private List list = new ArrayList();
synchronized public void add(String username) {
System.out.println(Thread.currentThread().getName() + " start to execute add()");
list.add(username);
System.out.println(Thread.currentThread().getName() + " finished executing add()");
} synchronized public int getSize() {
System.out.println(Thread.currentThread().getName() + " start to execute getSize()");
int size = list.size();
System.out.println(Thread.currentThread().getName() + " finished executing getSize()");
return size;
}
}
package com.umgsai.thread10;

public class ThreadA extends Thread {

	private MyList list;
public ThreadA(MyList list) {
super();
this.list = list;
} @Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add("thread A" + i);
}
}
} public class ThreadB extends Thread { private MyList list;
public ThreadB(MyList list) {
super();
this.list = list;
} @Override
public void run() {
for (int i = 0; i < 10000; i++) {
list.add("thread B" + i);
}
}
}
public class Run {
public static void main(String[] args) {
MyList list = new MyList();
ThreadA a = new ThreadA(list);
a.setName("A");
a.start();
ThreadB b = new ThreadB(list);
b.setName("B");
b.start();
}
}

控制台打印结果如下:

...
A start to execute add()
A finished executing add()
A start to execute add()
A finished executing add()
B start to execute add()
B finished executing add()
B start to execute add()
B finished executing add()
...

从打印结果来看,同步块中的代码是同步打印的,当前线程的“执行”与“退出”是成对出现的。但是线程A和线程B的执行却是异步的,这就有可能出现脏读的环境。由于线程执行方法的顺序不确定,当A和B两个线程执行带有分支判断的方法时,就会出现逻辑上的错误,有可能出现脏读。下面的例子会显示这个错误。

public class MyOneList {
private List list = new ArrayList<>();
synchronized public void add(String data){
System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " start to add data to list");
list.add(data);
System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " finished adding data to list");
} synchronized public int getSize(){
return list.size();
}
}
public class MyService {
public MyOneList addServiceMethod(MyOneList list, String data) {
try {
if (list.getSize() < 1) {
System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " start to get data from remote server");
Thread.sleep(2000);//模拟从远程取数据
System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " finisd getting data from remote server");
list.add(data);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
public class ThreadA extends Thread {

	private MyOneList list;
public ThreadA(MyOneList list) {
super();
this.list = list;
} @Override
public void run() {
MyService service = new MyService();
service.addServiceMethod(list, "A");
}
} public class ThreadB extends Thread { private MyOneList list;
public ThreadB(MyOneList list) {
super();
this.list = list;
} @Override
public void run() {
MyService service = new MyService();
service.addServiceMethod(list, "B");
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
MyOneList list = new MyOneList();
ThreadA a = new ThreadA(list);
a.setName("A");
a.start();
ThreadB b = new ThreadB(list);
b.setName("B");
b.start();
Thread.sleep(6000);
System.out.println("listSize=" + list.getSize());
}
}

运行结果如下:

1463663650767B start to get data from remote server
1463663650767A start to get data from remote server
1463663652767A finisd getting data from remote server
1463663652767B finisd getting data from remote server
1463663652767B start to add data to list
1463663652767B finished adding data to list
1463663652767A start to add data to list
1463663652767A finished adding data to list
listSize=2

由运行结果可知,A、B线程同时进入addServiceMethod()方法的if判断,开始从远程取数据,同时结束从远程取数据,之后排队进入add()方法,所以导致A、B线程都成功地向list中添加了元素。

要解决以上的问题,只需要做如下修改:

public class MyService {
public MyOneList addServiceMethod(MyOneList list, String data) {
try {
synchronized (list) {
if (list.getSize() < 1) {
System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " start to get data from remote server");
Thread.sleep(2000);//模拟从远程取数据
System.out.println(System.currentTimeMillis() + Thread.currentThread().getName() + " finisd getting data from remote server");
list.add(data);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}

此时控制台打印结果如下:

1463663982188A start to get data from remote server
1463663984189A finisd getting data from remote server
1463663984189A start to add data to list
1463663984189A finished adding data to list
listSize=1

由打印结果可以看出,A线程和B线程排队进入if语句,当A线程执行完add()方法之后,B线程开始进入if,此时list.size()=1,所以B线程无法进入if语句。

由于list参数对象在项目中是一份实例,是单例的,而且也正需要对list参数的getSize()方法做同步调用,所以就对list参数进行同步处理。


细化验证三个结论

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

  1. 当多个线程执行synchronized(x){}同步代码块时呈同步效果。
  2. 当其他线程执行x对象中synchronized同步方法时呈同步效果。
  3. 当其他线程执行x对象中的synchronized(this)代码块时也呈现同步效果。

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

  • 验证第一个结论
public class Service {
public void testMethod1(Object object) {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "-begin-" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "-end-" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class ThreadA extends Thread {

	private Service service;
private Object object;
public ThreadA(Service service, Object object) {
super();
this.service = service;
this.object = object;
} @Override
public void run() {
service.testMethod1(object);
}
} public class ThreadB extends Thread { private Service service;
private Object object;
public ThreadB(Service service, Object object) {
super();
this.service = service;
this.object = object;
} @Override
public void run() {
service.testMethod1(object);
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
Object object = new Object();
ThreadA a = new ThreadA(service, object);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service, object);
b.setName("B");
b.start();
}
}

打印结果如下:

A-begin-1463665022379
A-end-1463665024380
B-begin-1463665024380
B-end-1463665026381

由打印结果可见,A、B线程是以排队方式进入testMethod1()方法的。

对程序做如下修改:

public class Service {
public void testMethod1(Object object) {
// synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "-begin-" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "-end-" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
//}
}
}

此时打印结果如下:

A-begin-1463665210004
B-begin-1463665210005
A-end-1463665212004
B-end-1463665212005

可见不使用“对象监视器”时,A、B线程是以异步的方式进入testMethod1()方法的。

  • 验证第二个结论

    当其他线程执行x对象中synchronized同步方法时呈异步效果
public class MyObject {
synchronized public void print() {
System.out.println(Thread.currentThread().getName() + "-print begin-" + System.currentTimeMillis());
System.out.println("-----");
System.out.println(Thread.currentThread().getName() + "-print end-" + System.currentTimeMillis());
}
}
public class Service {
public void testMethod1(MyObject object) {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "-testMethod1 begin-" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "-testMethod1 end-" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public class ThreadA extends Thread {

	private Service service;
private MyObject object;
public ThreadA(Service service, MyObject object) {
super();
this.service = service;
this.object = object;
} @Override
public void run() {
service.testMethod1(object);
}
} public class ThreadB extends Thread { private Service service;
private MyObject object;
public ThreadB(Service service, MyObject object) {
super();
this.service = service;
this.object = object;
} @Override
public void run() {
object.print();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
MyObject object = new MyObject();
ThreadA a = new ThreadA(service, object);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service, object);
b.setName("B");
b.start();
}
}

控制台打印结果如下:

A-testMethod1 begin-1463665943190
A-testMethod1 end-1463665945190
B-print begin-1463665945191
-----
B-print end-1463665945191

可见,当A线程执行完object对象的testMethod1()方法中的synchronized块后,B线程才能进入object对象中的synchronized方法print()。

  • 验证第三个结论

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

将以上代码做如下修改:

public class MyObject {
public void print() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "-print begin-" + System.currentTimeMillis());
System.out.println("-----");
System.out.println(Thread.currentThread().getName() + "-print end-" + System.currentTimeMillis());
}
}
}

控制台打印结果如下:

A-testMethod1 begin-1463666380642
A-testMethod1 end-1463666382643
B-print begin-1463666382643
-----
B-print end-1463666382643

可见同样是同步效果。


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

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

public class Service {
synchronized public static void printA(){
try {
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-进入printA");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-离开printA");
} catch (Exception e) {
e.printStackTrace();
}
} synchronized public static void printB(){
try {
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-进入printB");
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-离开printB");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
@Override
public void run() {
Service.printA();
}
} public class ThreadB extends Thread {
@Override
public void run() {
Service.printB();
}
}
public class Run {
public static void main(String[] args) {
ThreadA a = new ThreadA();
a.setName("A");
a.start();
ThreadB b = new ThreadB();
b.setName("B");
b.start();
}
}

控制台打印结果如下:

A-1463704032074-进入printA
A-1463704035075-离开printA
B-1463704035075-进入printB
B-1463704035075-离开printB

A、B线程以排队方式分别进入printA()方法和printB()方法。

synchronized关键字加在static静态方法上是给Class类上锁,加在非static方法上是给对象上锁。

public class Service {
synchronized public static void printA(){
try {
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-进入printA");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-离开printA");
} catch (Exception e) {
e.printStackTrace();
}
} synchronized public static void printB(){
try {
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-进入printB");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-离开printB");
} catch (Exception e) {
e.printStackTrace();
}
} synchronized public void printC(){
try {
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-进入printC");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-离开printC");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
@Override
public void run() {
Service.printA();
}
} public class ThreadB extends Thread {
@Override
public void run() {
Service.printB();
}
} public class ThreadC extends Thread {
private Service service;
public ThreadC(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.printC();
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA();
a.setName("A");
ThreadB b = new ThreadB();
b.setName("B");
ThreadC c = new ThreadC(service);
c.setName("C");
a.start();
b.start();
c.start();
}
}

控制台打印结果如下:

A-1463705064927-进入printA
C-1463705064927-进入printC
C-1463705065927-离开printC
A-1463705065927-离开printA
B-1463705065927-进入printB
B-1463705066928-离开printB

从时间轴来看,A线程和C线程同时开始执行,同时结束执行,B线程等待A线程执行结束之后开始执行。

printC()为异步实现,printA()和printB()为同步执行。异步的原因是持有不同的锁,一个是对象锁,一个是Class锁,而Class锁可以对类的所有对象实例起作用。

synchronized(class)代码块的作用和synchronized static方法的作用一样。

将前面的Service类做如下修改:

public class Service {
public static void printA(){
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 (Exception e) {
e.printStackTrace();
}
}
} public static void printB(){
synchronized (Service.class) {
try {
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-进入printB");
System.out.println(Thread.currentThread().getName() + "-" + System.currentTimeMillis() + "-离开printB");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

控制台打印结果如下:

A-1463705744131-进入printA
A-1463705747131-离开printA
B-1463705747131-进入printB
B-1463705747131-离开printB

可见A、B仍热以排队方式进入printA()方法和printB()方法。