一、synchronized同步代码块
使用关键字 synchronized 声明的方法在某些情况下是有弊端的,比如线程 A 调用同步方法执行一个长时间的任务,那么线程 B 必须等待线程 A 将这个同步方法全部执行完才可以调用该方法,但是很多时候我们不需要对整个方法进行同步,可能只需要对部分代码块进行同步,这个时候就用到了synchronized同步代码块
直接看个使用 synchronized 同步代码块的例子吧
//创建两个线程一起调用的类 TaskA
class TaskA {
//没有使用 synchronized(this) 代码块
public void doLongTimeTask() {
for (int i = 0; i < 50; i++) {
System.out.println("no synchronized " + Thread.currentThread().getName()
+ " i = " + (i + 1));
}
System.out.println("");
//使用了 synchronized(this) 代码块
synchronized (this) {
for (int i = 0; i < 50; i++) {
System.out.println("synchronized " + Thread.currentThread().getName()
+ " i = " + (i + 1));
}
}
}
}
//创建线程 ThreadB1
class ThreadB1 extends Thread {
private TaskA taskA;
public ThreadB1(TaskA taskA) {
this.taskA = taskA;
}
@Override
public void run() {
taskA.doLongTimeTask();
}
}
//创建线程 ThreadA1
public class ThreadA1 extends Thread {
private TaskA taskA;
public ThreadA1(TaskA taskA) {
this.taskA = taskA;
}
@Override
public void run() {
taskA.doLongTimeTask();
}
public static void main(String[] args) {
TaskA task = new TaskA();
ThreadA1 threadA1 = new ThreadA1(task);
threadA1.setName("AAA");
threadA1.start();
ThreadB1 threadB1 = new ThreadB1(task);
threadB1.setName("BBB");
threadB1.start();
}
}
我们截取了三种类型的结果来看:
//两个线程交替进行的结果,只是部分
no synchronized AAA i = 1
no synchronized BBB i = 1
no synchronized AAA i = 2
no synchronized BBB i = 2
no synchronized BBB i = 3
no synchronized AAA i = 3
no synchronized BBB i = 4
no synchronized BBB i = 5
no synchronized AAA i = 4
no synchronized AAA i = 5
no synchronized AAA i = 6
no synchronized AAA i = 7
no synchronized BBB i = 6
no synchronized AAA i = 8
no synchronized BBB i = 7
no synchronized BBB i = 8
no synchronized AAA i = 9
no synchronized BBB i = 9
no synchronized AAA i = 10
...
//两个线程各自同步执行的结果
synchronized AAA i = 45
synchronized AAA i = 46
synchronized AAA i = 47
synchronized AAA i = 48
synchronized AAA i = 49
synchronized AAA i = 50
synchronized BBB i = 1
synchronized BBB i = 2
synchronized BBB i = 3
synchronized BBB i = 4
synchronized BBB i = 5
synchronized BBB i = 6
synchronized BBB i = 7
...
//一个同步一个异步执行的情况
synchronized AAA i = 10
synchronized AAA i = 11
synchronized AAA i = 12
synchronized AAA i = 13
synchronized AAA i = 14
synchronized AAA i = 15
no synchronized BBB i = 42
synchronized AAA i = 16
no synchronized BBB i = 43
synchronized AAA i = 17
no synchronized BBB i = 44
synchronized AAA i = 18
no synchronized BBB i = 45
对于第一种,线程 A 和线程 B 同时访问到了方法 synchronized 块中的部分,这个时候,可以随意执行,随意交替
对于第二种,线程 A 和线程 B 同时访问到了方法不在 synchronized 块中的部分,这个时候,如果线程 A 先进入 synchronized 代码块,那么线程 B 必须等待线程 A 执行完才可以进入
对于第三种,线程 A 访问到了方法在 synchronized 块中的部分,线程 B 则访问到了方法不在 synchronized 块中的部分,此时尽管线程 A 是同步执行的,但是线程 B 是异步执行的,这个时候,线程 B 就是随意执行了
简单的说:不在 synchronized 块中就是异步执行,在 synchronized 块中就是同步执行
二、synchronized代码块的同步性
当一个线程访问 object 的一个 synchronized(this)
同步代码块时,其他线程对同一个 object 中所有其他 synchronized(this)
同步代码块的访问都将被阻塞
//创建一个公共类
class ObjectService2 {
public void serviceMethodA() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName()
+ " methodA begin Time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()
+ " methodA end Time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void serviceMethodB() {
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName()
+ " methodB begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()
+ " methodB end time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
创建两个线程对同一个对象进行操作
//创建线程 ThreadB2
class ThreadB2 extends Thread {
private ObjectService2 service2;
public ThreadB2(ObjectService2 service2) {
this.service2 = service2;
}
@Override
public void run() {
//线程 BBB 访问 ObjectService2 中的 synchronized(this) 同步代码块
service2.serviceMethodB();
}
}
//创建线程 ThreadA2
public class ThreadA2 extends Thread {
private ObjectService2 service2;
public ThreadA2(ObjectService2 service2) {
this.service2 = service2;
}
@Override
public void run() {
//线程 AAA 访问 ObjectService2 中的 synchronized(this) 同步代码块
service2.serviceMethodA();
}
public static void main(String[] args) {
ObjectService2 service2 = new ObjectService2();
ThreadA2 threadA2 = new ThreadA2(service2);
threadA2.setName("AAA");
threadA2.start();
ThreadB2 threadB2 = new ThreadB2(service2);
threadB2.setName("BBB");
threadB2.start();
}
}
结果是:
AAA methodA begin Time = 1540186290804
AAA methodA end Time = 1540186292804
BBB methodB begin time = 1540186292804
BBB methodB end time = 1540186294805
从结果看到,两个线程调用的方法都是同步执行的,线程 B 等待线程 A 执行完 synchronized 代码块之后再执行自己的 synchronized 代码块。这是因为两个线程调用都是对同一个对象(service2)进行操作,即线程 A 先获得对象锁,线程 B 只有等到线程 A 执行完并且释放锁才能执行自己的代码块
三、synchronized代码块和synchronized方法
如果一个类中,既有 synchronized 的代码块,又有 synchronized 修饰的方法,那么结果会是如何?
class Task3 {
//synchronized 方法
synchronized public void otherMethod() {
System.out.println("--------------------------run--otherMethod");
}
//synchronized 代码块
public void doLongTimeTask() {
synchronized (this) {
for (int i = 0; i < 50; i++) {
System.out.println("synchronized "
+ Thread.currentThread().getName() + " i = " + (i + 1));
}
}
}
}
class ThreadB3 extends Thread {
private Task3 task3;
public ThreadB3(Task3 task3) {
this.task3 = task3;
}
@Override
public void run() {
//线程 BBB 调用同步方法
task3.otherMethod();
}
}
public class ThreadA3 extends Thread {
private Task3 task3;
public ThreadA3(Task3 task3) {
this.task3 = task3;
}
@Override
public void run() {
//线程 AAA 调用同步代码块
task3.doLongTimeTask();
}
public static void main(String[] args) {
Task3 task3 = new Task3();
ThreadA3 threadA3 = new ThreadA3(task3);
threadA3.setName("AAA");
threadA3.start();
ThreadB3 threadB3 = new ThreadB3(task3);
threadB3.setName("BBB");
threadB3.start();
}
}
结果是:
...
synchronized AAA i = 40
synchronized AAA i = 41
synchronized AAA i = 42
synchronized AAA i = 43
synchronized AAA i = 44
synchronized AAA i = 45
synchronized AAA i = 46
synchronized AAA i = 47
synchronized AAA i = 48
synchronized AAA i = 49
synchronized AAA i = 50
--------------------------run--otherMethod
从结果看,synchronized(this) 代码块和 synchronized 方法都是同步执行的。这是因为,两个线程都是对同一个对象 task3
执行操作的,线程 A 先访问 synchronized(this) 代码块,即获取了对象 task3
的对象锁,这个时候,类中的另一个 synchronized 方法就被锁定了,线程 B 必须等线程 A 执行完那段代码才可以执行
为了验证这个例子 synchronized(this) 代码块和 synchronized 方法是对同一对象进行操作,我们打印出 this,结果是:
doLongTimeTask:edu.just.syn.Task3@1e542a06
otherMethod:edu.just.syn.Task3@1e542a06
两种方式都是对 this 进行操作,synchronized(this)代码块也是锁定当前对象的
四、将任意对象作为监视器
多个对象调用同一对象中的不同名称的 synchronized 同步方法或者 synchronized(this) 同步代码块时,都是按照顺序来执行的,即同步执行
1.synchronized 同步方法
- 对其他 synchronized 同步方法或 synchronized(this) 同步代码块调用呈阻塞状态
- 同一时间只有一个线程可以执行 synchronized 同步方法中的代码
2.synchronized(this) 同步方法
- 对其他 synchronized 同步方法或者 synchronize(this) 同步代码块调用呈阻塞状态
- 同一时间只有一个线程可以执行 synchronized(this) 同步代码块中的代码
其实 Java 还支持对“任意对象”作为“对象监视器”来实现同步的功能,这个“任意对象”大多数是实例变量或者方法的参数,使用的格式是 synchronized(非 this 对象)
。看个例子吧
class Service {
private String username;
private String password;
private Object anyObject = new Object();
public void setUsernameAndPassword(String username, String password) {
//语句1 System.out.println(Thread.currentThread().getName() + " " + anyObject);
//此时 anyString 对象作为对象监视器
synchronized (anyObject) {
try {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis() + " 进入同步代码块 ");
Thread.sleep(2000);
this.password = password;
this.username = username;
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis() + " 退出同步代码块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
创建两个线程来调用
class ThreadB4 extends Thread {
private Service service;
public ThreadB4(Service service) {
this.service = service;
}
@Override
public void run() {
service.setUsernameAndPassword("BBB","BBB123");
}
}
public class ThreadA4 extends Thread {
private Service service;
public ThreadA4(Service service) {
this.service = service;
}
@Override
public void run() {
service.setUsernameAndPassword("AAA", "AAA123");
}
public static void main(String[] args) {
Service service = new Service();
ThreadA4 threadA4 = new ThreadA4(service);
threadA4.setName("线程 A");
threadA4.start();
ThreadB4 threadB4 = new ThreadB4(service);
threadB4.setName("线程 B");
threadB4.start();
}
}
结果是:
线程名称为:线程 A 在 1540189492294 进入同步代码块
线程名称为:线程 A 在 1540189494295 退出同步代码块
线程名称为:线程 B 在 1540189494295 进入同步代码块
线程名称为:线程 B 在 1540189496296 退出同步代码块
从结果看出,使用 synchronized(anyObject)
的代码块,在“对象监视器”是同一对象的情况下,依然是同步执行的,先是线程 A 执行,然后执行完了之后才是线程 B 执行
怎么理解这里的同一对象呢?我们把语句1的注释拿掉,进行输出,只打印注释掉的语句输出的内容,结果如下:
线程 A java.lang.Object@322b4b46
线程 B java.lang.Object@322b4b46
emmm~~~看到了把,两个线程访问该方法时用到的是同一个对象,这是为啥呢?因为 private Object anyObject = new Object()
这个语句是在方法外面的,anyObject
对象是全局的,每次使用 synchronized(anyObject)
代码块进行同步操作时,对象监视器就是同一个对象 anyString
,此时两个线程同步执行,即按照顺序执行
如果我们把 Object anyObject = new Object()
放在方法内部,此时 anObject
是局部变量,然后输出结果,看看会怎样
线程 A java.lang.Object@45ffb1ad
线程 B java.lang.Object@20210b0
线程名称为:线程 A 在 1540190711705 进入同步代码块 1174385069
线程名称为:线程 B 在 1540190711706 进入同步代码块 33689776
线程名称为:线程 A 在 1540190713706 退出同步代码块
线程名称为:线程 B 在 1540190713707 退出同步代码块
哈哈,怎样,结果也猜到了把,这个时候两个线程访问的不同的变量,从输出的地址不同也能看出。如果 anyString
对象是在方法中的,局部的,那么每个线程调用这个方法都会 new 一个新的 anyString 对象,之后使用 synchronized(anyString)
同步代码块进行同步操作时,因为对象监视器不是同一个对象,此时运行的结果就是异步调用的,即两个线程交叉运行
总结一下:多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码
锁非 this 对象具有一定的优点:如果在一个类中有很多个 synchronized 方法,这是虽然能实现同步,但是会收到阻塞,即每个 synchronized 方法必须等待前一个 synchronized 方法执行完才能执行;但如果使用同步代码块锁非 this 对象,则 synchronized(非 this)
代码块中的程序与 synchronized 同步方法是异步的,它不与锁 this 同步方法争抢 this 锁,可以大大提高运行效率
我们可以再来看一个在 synchronized(非 this 对象)
中,“任意对象” 是方法参数的例子
class MyOneList {
private List list = new ArrayList();
synchronized public void addMethod(String data) {
list.add(data);
}
synchronized public int getSize() {
return list.size();
}
}
//创建业务类,用于存放数据
class MyService {
public MyOneList addServiceMethod(MyOneList myOneList, String data) {
try {
System.out.println(myOneList);
synchronized (myOneList) {
//如果如果集合长度小于 1
if (myOneList.getSize() < 1) {
System.out.println(Thread.currentThread().getName() + " 进入了方法,时间是:" + System.currentTimeMillis());
//模拟从远程花费 2s 取回数据
Thread.sleep(2000);
//存入数据
myOneList.addMethod(data);
System.out.println(Thread.currentThread().getName() + " 存入了数据 " + data + " 时间是:" + System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return myOneList;
}
}
创建两个对象
class ThreadB8 extends Thread {
private MyOneList myOneList;
public ThreadB8(MyOneList myOneList) {
this.myOneList = myOneList;
}
@Override
public void run() {
MyService myService = new MyService();
myService.addServiceMethod(myOneList, "BBB");
}
}
public class ThreadA8 extends Thread {
private MyOneList myOneList;
public ThreadA8(MyOneList myOneList) {
this.myOneList = myOneList;
}
@Override
public void run() {
MyService myService = new MyService();
myService.addServiceMethod(myOneList, "AAA");
}
public static void main(String[] args) throws InterruptedException {
MyOneList oneList = new MyOneList();
ThreadA8 threadA8 = new ThreadA8(oneList);
threadA8.setName("AAA");
threadA8.start();
ThreadB8 threadB8 = new ThreadB8(oneList);
threadB8.setName("BBB");
threadB8.start();
Thread.sleep(4000);
System.out.println("listSize " + oneList.getSize());
}
}
结果是:
edu.just.syn.MyOneList@67c716ea
AAA 进入了方法,时间是:1540192561732
edu.just.syn.MyOneList@67c716ea
AAA 存入了数据 AAA 时间是:1540192563732
listSize 1
从结果看到,两个线程对方法 addServiceMethod
进行处理时,是对同一个对象 myOneList
执行操作的,此时该方法是同步方法,一次性只能在集合中存入一个值,不会出现脏读。本质上,只要两个线程在同步代码块中是对同一个对象进行处理的,那么就是该代码块就是同步执行的
五、参考
《Java多线程核心技术编程》
https://www.cnblogs.com/xrq730/p/4851530.html