1、synchronized 同步方法
作用:保证原子性、可见性、有序性
1.1 方法内的变量为线程安全
方法中的变量不存在非线程安全问题,永远线程安全,这是由于方法内部的变量具有私有特性
例子:
public class Run {
public static void main(String[] args) throws InterruptedException {
PrivateNum privateNum = new PrivateNum();
ThreadA threadA = new ThreadA(privateNum);
threadA.start();
ThreadB threadB = new ThreadB(privateNum);
threadB.start();
}
}
class PrivateNum {
public void addI(String name) {
try {
int num = 0;
if (name.equals("a")) {
num = 100;
System.out.println("set a over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("set b over");
}
System.out.println("num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private PrivateNum privateNum;
public ThreadA(PrivateNum privateNum) {
this.privateNum = privateNum;
}
@Override
public void run() {
super.run();
privateNum.addI("a");
}
}
class ThreadB extends Thread {
private PrivateNum privateNum;
public ThreadB(PrivateNum privateNum) {
this.privateNum = privateNum;
}
@Override
public void run() {
super.run();
privateNum.addI("b");
}
}
输出:
set a over
set b over
num=200
num=100
1.2 实例变量非线程安全问题与解决方法
如果多个线程共同访问一个对象中的实例变量,可能出现非线程安全问题
对于上一节的代码稍作改动,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
PrivateNum privateNum = new PrivateNum();
ThreadA threadA = new ThreadA(privateNum);
threadA.start();
ThreadB threadB = new ThreadB(privateNum);
threadB.start();
}
}
class PrivateNum {
// num变量为类所拥有而不是方法
private int num = 0;
public void addI(String name) {
try {
if (name.equals("a")) {
num = 100;
System.out.println("set a over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("set b over");
}
System.out.println("num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private PrivateNum privateNum;
public ThreadA(PrivateNum privateNum) {
this.privateNum = privateNum;
}
@Override
public void run() {
super.run();
privateNum.addI("a");
}
}
class ThreadB extends Thread {
private PrivateNum privateNum;
public ThreadB(PrivateNum privateNum) {
this.privateNum = privateNum;
}
@Override
public void run() {
super.run();
privateNum.addI("b");
}
}
输出:
set a over
set b over
num=200
num=200
解决方法:
将public void addI(String name) 修改为 synchronized public void addI(String name)
输出:
set a over
num=100
set b over
num=200
结论:
两个线程访问同一个对象中的同步方法一定是线程安全的。不管哪个线程先运行,线程进入 synchronized 声明的方法就上锁,方法执行完就解锁,之后下一个线程才会进入用 synchronized 声明的方法,当前线程不解锁其他线程执行不了 synchronized 声明的方法。
1.3 同步 synchronized 在字节码指令中的原理
在方法中使用 synchronized 进行同步的原因是使用了 flag 标记 ACC_SYNCHRONIZED ,当调用方法时,调用指令会先检查方法的 ACC_SYNCHRONIZED 访问标志符是否设置,如果设置了,执行线程会持有同步锁,然后执行方法,最后在方法完成时释放锁。
例子:
public class Run {
public static void main(String[] args) {
testMethod();
}
synchronized public static void testMethod() {
}
}
首先使用 javac Run.java 生成 class 文件
然后使用 javap -c -v Run.class 生成字节码:
核心字节码如下:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: invokestatic #7 // Method testMethod:()V
3: return
LineNumberTable:
line 5: 0
line 6: 3
public static synchronized void testMethod();
descriptor: ()V
flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 10: 0
在字节码指令中,对于 synchronized public static void testMethod() 使用了 flag 标记 ACC_SYNCHRONIZED ,说明此方法时同步的。
如果使用 synchronized 标记代码块而不是方法,则使用 monitorenter 和 monitorexit 进行同步处理,例子如下:
public class Run {
public static void main(String[] args) {
Run run = new Run();
run.testMethod();
}
public void testMethod() {
synchronized (this) {
int age = 100;
}
}
}
使用上述同样的方法查看字节码指令:
public void testMethod();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: bipush 100
6: istore_2
7: aload_1
8: monitorexit
9: goto 17
12: astore_3
13: aload_1
14: monitorexit
15: aload_3
16: athrow
17: return
1.4 多个对象多个锁
例子:
public class Run {
public static void main(String[] args) throws InterruptedException {
PrivateNum privateNumA = new PrivateNum();
PrivateNum privateNumB = new PrivateNum();
ThreadA threadA = new ThreadA(privateNumA);
threadA.start();
ThreadB threadB = new ThreadB(privateNumB);
threadB.start();
}
}
class PrivateNum {
private int num = 0;
synchronized public void addI(String name) {
try {
if (name.equals("a")) {
num = 100;
System.out.println("set a over");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("set b over");
}
System.out.println("num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private PrivateNum privateNum;
public ThreadA(PrivateNum privateNum) {
this.privateNum = privateNum;
}
@Override
public void run() {
super.run();
privateNum.addI("a");
}
}
class ThreadB extends Thread {
private PrivateNum privateNum;
public ThreadB(PrivateNum privateNum) {
this.privateNum = privateNum;
}
@Override
public void run() {
super.run();
privateNum.addI("b");
}
}
输出:
set a over
set b over
num=200
num=100
由输出可知,两个线程是以异步的方式进行
在例子中,创建了两个业务对象,在系统中产生了两个锁,线程和业务属于一对一的关系,每个线程都执行自己所属的业务对象的同步方法,而不是执行同一个业务对象,所以不存在争抢关系,所以是异步运行的。
关键字 synchronized 取得的锁都是对象锁,而不是把一段代码或者方法当做锁,所以在例子中,哪个线程先执行 synchronized 关键字的方法,哪个线程就持有该方法所属对象的锁 Lock,其他线程都是处于等待状态,前提是多个线程访问的是同一个对象。如果多个线程访问多个对象,则 JVM 会创建多个锁。
1.5 将 synchronized 方法与对象作为锁
例子:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
ThreadA threadA = new ThreadA(object);
threadA.setName("a");
ThreadB threadB = new ThreadB(object);
threadB.setName("b");
threadA.start();
threadB.start();
}
}
class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
class MyObject {
public void methodA() {
try {
System.out.println("begin methodA thread name=" + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
begin methodA thread name=b
begin methodA thread name=a
end
end
通过观察输出可以看到两个线程可以一同进入 methodA() 方法
将 public void methodA() 方法添加 synchronized 关键字,重新执行:
begin methodA thread name=a
end
begin methodA thread name=b
end
通过输出可以发现,调用 synchronized 声明的方法一定是排队进行运行的。只有共享资源的读写访问才需要同步化,如果不是共享资源,则没有同步的必要性
其他方法在被调用时会有什么效果呢?
例子如下,新增方法:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
ThreadA threadA = new ThreadA(object);
threadA.setName("a");
ThreadB threadB = new ThreadB(object);
threadB.setName("b");
threadA.start();
threadB.start();
}
}
class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
class ThreadB extends Thread {
private MyObject object;
public ThreadB(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodB();
}
}
class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA thread name=" + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endtime" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB() {
try {
System.out.println("begin methodB thread name=" + Thread.currentThread().getName() + " begin time "
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
begin methodA thread name=a
begin methodB thread name=b begin time 1638365776489
end
end endtime1638365781491
通过例子可以得知,虽然线程A持有了object对象的锁,但是线程B可以异步得调用非 synchronized 的方法。
如果方法B添加了 synchronized 关键字,则效果如下:
begin methodA thread name=a
end endtime1638365980157
begin methodB thread name=b begin time 1638365980157
end
通过上述例子可以得出:
- A线程持有 object 对象的 Lock 锁,B线程可以异步调用 object 对象中的非
synchronized类型的方法 - A线程持有 object 对象的 Lock 锁,B线程如果调用 object 对象中的
synchronized方法,则需要等待,也就是同步 - 在方法处添加
synchronized并不是锁方法,是锁当前类的对象 - 在 Java 中,只有“将对象作为锁”,没有“锁方法”
- 在 Java 中,“锁”就是“对象”,“对象”可以映射成“锁”,哪个线程拿到这把锁,哪个线程就可以执行这个对象中的
synchronized方法 - 如果在 X 对象中使用了
synchronized关键字声明非静态方法,则 X 对象就被当成锁
1.6 synchronized 锁重入
“可重入锁”是指自己可以再次获取自己的内部锁。例如,一个线程获得了某个对象锁,此时这个对象锁还没有被释放,当其再次想要获取这个对象锁的时候,还是能够获取到的。
例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
}
}
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");
}
}
class MyThread extends Thread {
@Override
public void run() {
Service service = new Service();
service.service1();
}
}
输出:
service1
service2
service3
由此可见,使用 synchronized 时,当一个线程得到一个对象锁后,再次请求此对象锁时可以得到该对象锁,这也证明在 synchronized 方法/块的内部调用其他 synchronized 方法/块时,是永远可以得到锁的。
1.7 锁重入支持继承的环境
锁重入也支持父子类继承的场景,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
}
}
class Base {
public int i = 10;
synchronized public void operateIInBaseMethod() {
try {
i--;
System.out.println("base print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Sub extends Base {
synchronized public void operateIInSubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
super.operateIInBaseMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThread extends Thread{
@Override
public void run() {
Sub sub=new Sub();
sub.operateIInSubMethod();
}
}
输出:
sub print i=9
base print i=8
sub print i=7
base print i=6
sub print i=5
base print i=4
sub print i=3
base print i=2
sub print i=1
base print i=0
1.8 出现异常,锁自动释放
当一个线程出现异常时,其多持有的锁会自动释放
1.9 重写方法不适用 synchronized
重现方法如果不使用 synchronized 关键字,即是非同步方法,使用后变成同步方法
例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Sub sub = new Sub();
MyThreadA threadA = new MyThreadA(sub);
threadA.setName("a");
threadA.start();
MyThreadB threadB = new MyThreadB(sub);
threadB.setName("b");
threadB.start();
}
}
class Base {
public int i = 10;
synchronized public void serviceMethod() {
try {
System.out.println("in Base,sleep begin,thread Name" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("in Base,sleep end,thread Name" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Sub extends Base {
@Override
public void serviceMethod() {
try {
System.out.println("in sub,sleep begin,thread Name" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("in sub,sleep end,thread Name" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
super.serviceMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadA extends Thread {
private Sub sub;
public MyThreadA(Sub sub) {
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
class MyThreadB extends Thread {
private Sub sub;
public MyThreadB(Sub sub) {
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
输出:
in sub,sleep begin,thread Namea time=1638369761495
in sub,sleep begin,thread Nameb time=1638369761495
in sub,sleep end,thread Namea time=1638369766496
in sub,sleep end,thread Nameb time=1638369766496
in Base,sleep begin,thread Namea time=1638369766496
in Base,sleep end,thread Namea time=1638369771500
in Base,sleep begin,thread Nameb time=1638369771500
in Base,sleep end,thread Nameb time=1638369776501
从输出结果看,线程以异步的方式进行输出,如果在子类的重写方法中添加 synchronized 关键字,输出如下:
in sub,sleep begin,thread Namea time=1638369886262
in sub,sleep end,thread Namea time=1638369891264
in Base,sleep begin,thread Namea time=1638369891264
in Base,sleep end,thread Namea time=1638369896266
in sub,sleep begin,thread Nameb time=1638369896266
in sub,sleep end,thread Nameb time=1638369901267
in Base,sleep begin,thread Nameb time=1638369901267
in Base,sleep end,thread Nameb time=1638369906268
1.10 public static native boolean holdsLock(Object obj) 的使用
作用是当 currentThread 在指定对象上保持锁定时,才会返回 true
例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
System.out.println("A " + Thread.currentThread().holdsLock(Run.class));
synchronized (Run.class) {
System.out.println("B " + Thread.currentThread().holdsLock(Run.class));
}
System.out.println("C " + Thread.currentThread().holdsLock(Run.class));
}
}
输出:
A false
B true
C false
2、 synchronized 同步语句块
与同步方法的差异:
synchronized方法是将当前对象作为锁synchronized代码块是将任意对象作为锁
可以将锁看出一个标识,哪个线程持有这个标识,就可以执行同步方法
2.1 synchronized 同步方法的弊端
例子:
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
Thread.sleep(10000);
long beginTime = Math.min(CommonUtils.beginTime1, CommonUtils.beginTime2);
long endTime = Math.max(CommonUtils.endTime1, CommonUtils.endTime2);
System.out.println("time:" + (endTime - beginTime) / 1000);
}
}
class Task {
private String getData1;
private String getData2;
public synchronized void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1 = "long time task result1 threadName=" + Thread.currentThread().getName();
getData2 = "long time task result2 threadName=" + Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
输出:
begin task
long time task result1 threadName=Thread-0
long time task result2 threadName=Thread-0
end task
begin task
long time task result1 threadName=Thread-1
long time task result2 threadName=Thread-1
end task
time:6
由例子和输出可以看出,在执行耗时任务时,两个线程依次进入耗时函数,在停止3s等待返回值处串行,导致效率不够高
2.2 使用同步代码块解决上述弊端
修改例子:
...
class Task {
private String getData1;
private String getData2;
public void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
// 使用synchronized同步代码块
synchronized (this) {
getData1 = "long time task result1 threadName=" + Thread.currentThread().getName();
getData2 = "long time task result2 threadName=" + Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
...
输出:
begin task
begin task
long time task result1 threadName=Thread-0
long time task result2 threadName=Thread-0
end task
long time task result1 threadName=Thread-1
long time task result2 threadName=Thread-1
end task
time:3
通过上述例子与输出可知,当一个线程访问 object 的一个 synchronized 同步代码块时,另一个线程仍然可以访问该 object 对象中的非 synchronized(this) 代码块
2.3 一半异步,一半同步
下列例子说明了不在 synchronized 块中就是异步执行,在 synchronized 块中就是同步执行:
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
MyThread thread1 = new MyThread(task);
MyThread thread2 = new MyThread(task);
thread1.start();
thread2.start();
}
}
class Task {
public void doLongTimeTask() {
for (int i = 0; i < 10; i++) {
System.out.println("no synchronized threadName=" + Thread.currentThread().getName() + " i=" + i);
}
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("synchronized threadName=" + Thread.currentThread().getName() + " i=" + i);
}
}
}
}
class MyThread extends Thread {
private Task task;
public MyThread(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
输出:
...
no synchronized threadName=Thread-0 i=5
no synchronized threadName=Thread-0 i=6
no synchronized threadName=Thread-0 i=7
no synchronized threadName=Thread-1 i=7
no synchronized threadName=Thread-0 i=8
no synchronized threadName=Thread-0 i=9
no synchronized threadName=Thread-1 i=8
no synchronized threadName=Thread-1 i=9
synchronized threadName=Thread-0 i=0
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-1 i=0
synchronized threadName=Thread-1 i=1
synchronized threadName=Thread-1 i=2
...
2.4 synchronized 代码块间的同步性
在使用 synchronized(this) 同步代码块时需要注意,当一个线程访问 object 的一个 synchronized(this) 同步代码块时,其他线程对同一个 object 中所有其他 synchronized(this) 同步代码的访问将被阻塞,这说明 synchronized 使用的对象监视器是同一个,即使用的锁是同一个,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
ThreadA thread1 = new ThreadA(task);
thread1.setName("a");
ThreadB thread2 = new ThreadB(task);
thread2.setName("b");
thread1.start();
thread2.start();
}
}
class Task {
public void taskMethod1() {
try {
synchronized (this) {
System.out.println("A begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time=" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void taskMethod2() {
synchronized (this) {
System.out.println("B begin time=" + System.currentTimeMillis());
System.out.println("B end time=" + System.currentTimeMillis());
}
}
}
class ThreadA extends Thread {
private Task task;
public ThreadA(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.taskMethod1();
}
}
class ThreadB extends Thread {
private Task task;
public ThreadB(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.taskMethod2();
}
}
输出:
A begin time=1639207075267
A end time=1639207077268
B begin time=1639207077268
B end time=1639207077268
2.5 println() 方法也是同步的
查看 println() 的源码可以发现也是运用 synchronized(this) 同步代码块,这样在输出信息的过程中是同步的,不会出现输出信息交叉导致信息混乱的情况
public void println(char[] x) {
if (getClass() == PrintStream.class) {
writeln(x);
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
public void println(String x) {
if (getClass() == PrintStream.class) {
writeln(String.valueOf(x));
} else {
synchronized (this) {
print(x);
newLine();
}
}
}
2.6 验证 synchronized(this) 同步代码块是锁定当前对象的
和 synchronized 同步方法一样,synchronized(this)也是锁定当前对象的,例子:
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
ThreadA thread1 = new ThreadA(task);
thread1.start();
Thread.sleep(10);
ThreadB thread2 = new ThreadB(task);
thread2.start();
}
}
class Task {
public void otherMethod() {
System.out.println("----run other method----");
}
public void doLongTimeTask() {
synchronized (this) {
for (int i = 0; i < 500; i++) {
System.out.println("synchronized threadName=" + Thread.currentThread().getName() + " i=" + i);
}
}
}
}
class ThreadA extends Thread {
private Task task;
public ThreadA(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
class ThreadB extends Thread {
private Task task;
public ThreadB(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
task.otherMethod();
}
}
输出:
...
synchronized threadName=Thread-0 i=68
synchronized threadName=Thread-0 i=69
synchronized threadName=Thread-0 i=70
synchronized threadName=Thread-0 i=71
----run other method----
synchronized threadName=Thread-0 i=72
synchronized threadName=Thread-0 i=73
...
将 otherMethod() 增加 synchronized 关键字,输出如下:
...
synchronized threadName=Thread-0 i=496
synchronized threadName=Thread-0 i=497
synchronized threadName=Thread-0 i=498
synchronized threadName=Thread-0 i=499
----run other method----
由此可见,同步输出的原因是 synchronized (this) 同步代码块将当前类的对象作为锁,使用 public synchronized void otherMethod() 同步方法是当前方法所在的类的对象作为锁,都是同一把锁,所以运行结果表现出同步的效果
2.7 将任意对象作为锁
多个线程调用同一个对象中的不同名称的 synchronized 同步方法或者 synchronized(this) 同步代码块时,调用顺序是同步,即顺序执行。
synchronized 同步方法或者 synchronized(this) 同步代码块分别有两个作用
synchronized 同步方法的作用:
- 对其他
synchronized同步方法或synchronized(this)同步代码块的调用呈现同步效果 - 同一个时间只有同一个线程可以执行
synchronized同步方法的代码
synchronized(this) 同步代码块的作用:
- 对其他
synchronized同步方法或synchronized(this)同步代码块的调用呈现同步效果 - 同一个时间只有同一个线程可以执行
synchronized(this)同步方法的代码
除了使用 synchronized(this) 的格式来创建同步代码块,其实 Java 还支持将“任意对象”作为锁来实现同步功能,这个”任意对象“大多数是实例变量及方法参数。使用格式为 synchronized(非this对象)
synchronized(非this对象) 同步代码块的作用:当多个线程争抢相同的“非this对象”的锁时。同一时间只有一个线程可以执行 synchronized(非this对象) 同步代码块中的代码,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadA thread1 = new ThreadA(service);
ThreadB thread2 = new ThreadB(service);
thread1.setName("A");
thread2.setName("B");
thread1.start();
thread2.start();
}
}
class Service {
private String user;
private String password;
private String anyString = new String();
public void setUserAndPassword(String user, String password) {
try {
synchronized (anyString) {
System.out.println(
"Thread Name:" + Thread.currentThread().getName() + " at time:" + System.currentTimeMillis()
+ " enter code");
this.user = user;
Thread.sleep(3000);
this.password = password;
System.out.println(
"Thread Name:" + Thread.currentThread().getName() + " at time:" + System.currentTimeMillis()
+ " exit code");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.setUserAndPassword("a", "aa");
}
}
class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
super.run();
service.setUserAndPassword("b", "bb");
}
}
输出:
Thread Name:A at time:1639210081549 enter code
Thread Name:A at time:1639210084551 exit code
Thread Name:B at time:1639210084551 enter code
Thread Name:B at time:1639210087552 exit code
锁非this对象的优点: 如果一个类中有很多 synchronized 方法,虽然能实行同步,但是影响效率;如果使用同步代码块锁非this对象,则 synchronized(非this对象) 代码块中程序与其他的同步方法都是异步的,因为有两把锁,不与其他锁this同步方法争抢this锁,可以大大提高运行效率。
2.8 多个锁就是异步运行
更改2.7中 Service 代码,如下:
class Service {
private String user;
private String password;
public void setUserAndPassword(String user, String password) {
try {
// 锁不同的对象
String anyString = new String();
synchronized (anyString) {
System.out.println(
"Thread Name:" + Thread.currentThread().getName() + " at time:" + System.currentTimeMillis()
+ " enter code");
this.user = user;
Thread.sleep(3000);
this.password = password;
System.out.println(
"Thread Name:" + Thread.currentThread().getName() + " at time:" + System.currentTimeMillis()
+ " exit code");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出如下:
Thread Name:B at time:1639210613260 enter code
Thread Name:A at time:1639210613262 enter code
Thread Name:B at time:1639210616262 exit code
Thread Name:A at time:1639210616264 exit code
可见,想使用 synchronized(非this对象) 同步代码块时,锁必须是同一个,否则就是异步运行
2.9 细化三个结论
synchronized(非this对象x) 的写法是将 x 对象本身作为“对象监视器”,这样就可以分析出3个结论:
- 当多线程同时执行
synchronized(x){}同步代码块时呈现同步效果 - 当其他线程执行 x 对象中的
synchronized同步方法时呈现同步效果 - 当其他线程执行 x 对象中的
synchronized(this)代码块时呈现同步效果
需要注意,如果其他线程不加 synchronized 关键字的方法,则还是会异步调用
验证第一个结论,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
MyObject object = new MyObject();
ThreadA thread1 = new ThreadA(service, object);
ThreadA thread2 = new ThreadA(service, object);
thread1.setName("a");
thread2.setName("b");
thread1.start();
thread2.start();
}
}
class MyObject {
}
class Service {
public void testMethod1(MyObject object) {
synchronized (object) {
try {
System.out.println("testMethod1 get lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("testMethod1 release lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread {
private Service service;
private MyObject object;
public ThreadA(Service service, MyObject object) {
this.service = service;
this.object = object;
}
@Override
public void run() {
super.run();
service.testMethod1(object);
}
}
输出:
testMethod1 get lock time=1639212493316 run ThreadName=a
testMethod1 release lock time=1639212495317 run ThreadName=a
testMethod1 get lock time=1639212495317 run ThreadName=b
testMethod1 release lock time=1639212497320 run ThreadName=b
同步的原因是使用了同一个锁,如果使用不同的锁会怎么样呢?修改 main 函数,如下:
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
ThreadA thread1 = new ThreadA(service, object1);
ThreadA thread2 = new ThreadA(service, object2);
thread1.setName("a");
thread2.setName("b");
thread1.start();
thread2.start();
}
输出:
testMethod1 get lock time=1639212646169 run ThreadName=b
testMethod1 get lock time=1639212646169 run ThreadName=a
testMethod1 release lock time=1639212648170 run ThreadName=a
testMethod1 release lock time=1639212648170 run ThreadName=b
验证第二个结论,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
MyObject object = new MyObject();
ThreadA thread1 = new ThreadA(service, object);
ThreadB thread2 = new ThreadB(service, object);
thread1.setName("a");
thread2.setName("b");
thread1.start();
thread2.start();
}
}
class MyObject {
public synchronized void speedPrintString() {
System.out.println("speedPrintString get lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
System.out.println("----------");
System.out.println("speedPrintString release lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
}
}
class Service {
public void testMethod1(MyObject object) {
synchronized (object) {
try {
System.out.println("testMethod1 get lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("testMethod1 release lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread {
private Service service;
private MyObject object;
public ThreadA(Service service, MyObject object) {
this.service = service;
this.object = object;
}
@Override
public void run() {
super.run();
service.testMethod1(object);
}
}
class ThreadB extends Thread {
private Service service;
private MyObject object;
public ThreadB(Service service, MyObject object) {
this.service = service;
this.object = object;
}
@Override
public void run() {
super.run();
object.speedPrintString();
}
}
输出:
testMethod1 get lock time=1639213210778 run ThreadName=a
testMethod1 release lock time=1639213215781 run ThreadName=a
speedPrintString get lock time=1639213215781 run ThreadName=b
<---------->
speedPrintString release lock time=1639213215781 run ThreadName=b
验证第三个结论,将第二个例子中的 public void speedPrintString() 略作修改:
class MyObject {
public void speedPrintString() {
synchronized (this) {
System.out.println("speedPrintString get lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
System.out.println("<---------->");
System.out.println("speedPrintString release lock time=" + System.currentTimeMillis() + " run ThreadName="
+ Thread.currentThread().getName());
}
}
}
输出:
speedPrintString get lock time=1639213337837 run ThreadName=b
<---------->
speedPrintString release lock time=1639213337837 run ThreadName=b
testMethod1 get lock time=1639213337837 run ThreadName=a
testMethod1 release lock time=1639213342839 run ThreadName=a
2.10 类 Class 的单例性
每一个 *.java 文件对应的 Class 类的实例都是一个,在内存中都是单例的,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
MyClass class1 = new MyClass();
MyClass class2 = new MyClass();
MyClass class3 = new MyClass();
System.out.println(class1.getClass() == class1.getClass());
System.out.println(class1.getClass() == class2.getClass());
System.out.println(class1.getClass() == class3.getClass());
}
}
class MyClass {
}
输出:
true
true
true
Class 类用于描述类的基本信息,包括多少个字段,有多少个构造方法,有多少个普通方法,为了减少对内存的高占用率,在内存中只需要存在一份 Class 类对象就可以了,所以被设计成单例的
2.11 静态同步 synchronized 方法与 synchronized(class) 代码块
关键字 synchronized 还可以应用在 static 静态方法上,如果这样声明,*则是对 .java 对应的 Class 类对象进行加锁,Class 对象是单例的。更具体的说,在静态 static 方法上使用 synchronized 关键字声明同步方法,则是使用当前静态方法所在对应 Class 类的单例对象作为锁。例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.setName("a");
b.setName("b");
a.start();
b.start();
}
}
class Service {
synchronized public static void printA() {
try {
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " enter printA");
Thread.sleep(3000);
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " exit printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void printB() {
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " enter printB");
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " exit printB");
}
}
class ThreadA extends Thread {
@Override
public void run() {
Service.printA();
}
}
class ThreadB extends Thread {
@Override
public void run() {
Service.printB();
}
}
输出:
Thread name:a at 1639569863166 enter printA
Thread name:a at 1639569866168 exit printA
Thread name:b at 1639569866168 enter printB
Thread name:b at 1639569866168 exit printB
虽然该代码段的运行结果和将 synchronized 关键字加到非 static 方法上的效果是一样的,但是本质有所不同。synchronized 关键字加到static 静态方法是将 Class 的对象作为锁,而 synchronized 加到非 static 静态方法是将类的对象作为锁。
2.12 同步 synchronized static 方法可以对类的所有实例对象起作用
Class 锁可以对类的所有对象实例起作用,例子如下:
public class Run {
public static void main(String[] args) throws InterruptedException {
Service serviceA = new Service();
Service serviceB = new Service();
ThreadA a = new ThreadA(serviceA);
ThreadB b = new ThreadB(serviceB);
a.setName("a");
b.setName("b");
a.start();
b.start();
}
}
class Service {
synchronized public static void printA() {
try {
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " enter printA");
Thread.sleep(3000);
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " exit printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void printB() {
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " enter printB");
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " exit printB");
}
}
class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.printA();
}
}
class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.printB();
}
}
输出如下:
Thread name:a at 1639570587052 enter printA
Thread name:a at 1639570590053 exit printA
Thread name:b at 1639570590053 enter printB
Thread name:b at 1639570590053 exit printB
2.12 同步 synchronized(class) 代码块可以对类的所有实例对象起作用
同步 synchronized(class) 代码块的作用其实和 synchronized static 方法的作用一样,将前一节的Serive稍作修改:
class Service {
public static void printA() {
synchronized (Service.class) {
try {
System.out
.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " enter printA");
Thread.sleep(3000);
System.out
.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " exit printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void printB() {
synchronized (Service.class) {
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " enter printB");
System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis()
+ " exit printB");
}
}
}
输出如下:
Thread name:a at 1639570824310 enter printA
Thread name:a at 1639570827310 exit printA
Thread name:b at 1639570827310 enter printB
Thread name:b at 1639570827310 exit printB