java多线程3:synchronized
线程安全
多个线程共同访问一个对象的实例变量,那么就可能出现线程不安全的问题。
先看一段代码示例,定义一个对象 MyDomain1
public class MyDomain1 { private int num = 0; public void addI(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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
写两个线程分别去add字符串"a"和字符串"b"
public class Mythread1_1 extends Thread { private MyDomain1 numRef; public Mythread1_1(MyDomain1 numRef) {
super();
this.numRef = numRef;
} @Override
public void run() {
super.run();
numRef.addI("a");
} }
@Test
public void test() throws InterruptedException {
MyDomain1 mythread1 = new MyDomain1();
Mythread1_1 athread = new Mythread1_1(mythread1);
athread.start(); Mythread1_2 bthread = new Mythread1_2(mythread1);
bthread.start(); athread.join();
bthread.join();
System.out.println(Thread.currentThread().getName());
}
执行结果:
a set over!
b set over!
b num=200
a num=200
main
按照正常来看应该打印"a num = 100"和"b num = 200"才对,现在却打印了"b num = 200"和"a num = 200",这就是线程安全问题。
当我们给 MyDomain1的 addI方法加上同步 synchronized 后,
执行结果为:
a set over!
a num=100
b set over!
b num=200
main
多个线程实例,访问同一个共享实例变量,非线程安全问题:多个线程对同一个对象中的同一个实例变量操作时 ,Mythread1 方法用synchronized修饰 可以解决线程非安全问题。
多个对象多个锁
@Test
public void test1() throws InterruptedException {
MyDomain1 mythread1 = new MyDomain1();
Mythread1_1 athread = new Mythread1_1(mythread1);
athread.start();
MyDomain1 mythread2 = new MyDomain1();
Mythread1_2 bthread = new Mythread1_2(mythread2);
bthread.start(); athread.join();
bthread.join();
System.out.println(Thread.currentThread().getName());
}
运行结果:
a set over!
b set over!
b num=200
a num=100
main
第6行,我们再定义一个新对象,当同时执行 athread 和 bthread的时候,打印的顺序是交叉的。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,
所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,
那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。但如果多个线程访问多个对象,则JVM会创建多个锁。
上面的示例就是创建了2个MyDomain1.java类的对象,所以就会产生出2个锁,因此两个线程之间不会受到对方加锁的约束。
synchronized方法与锁对象
在一个实体类, 定义一个同步方法和一个非同步方法:
public class MyDomain2 {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName=" + Thread.currentThread().getName()+ " begin time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 也加上synchronized修饰
public void methodB() {
try {
System.out.println("begin methodB threadName=" + Thread.currentThread().getName() + " begin time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end endTime=" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后定义两个线程类,一个调用同步方法,一个调用非同步方法
public class Mythread2_1 extends Thread { private MyDomain2 object; public Mythread2_1(MyDomain2 object) {
this.object = object;
} @Override
public void run() {
object.methodA();
} }
public class Mythread2_2 extends Thread { private MyDomain2 object; public Mythread2_2(MyDomain2 object) {
this.object = object;
} @Override
public void run() {
object.methodB();
} }
@Test
public void test2() throws InterruptedException {
MyDomain2 object = new MyDomain2();
Mythread2_1 a = new Mythread2_1(object);
a.setName("A");
Mythread2_2 b = new Mythread2_2(object);
b.setName("B"); a.start();
b.start();
a.join();
b.join();
}
运行结果:
begin methodA threadName=A begin time=1639384569539
begin methodB threadName=B begin time=1639384569560
end endTime=1639384574541
end endTime=1639384574564
可以看到methodA和methodB基本同时执行,当我们把methodB也加上 synchronized 修饰后:
begin methodA threadName=A begin time=1639384695430
end endTime=1639384700433
begin methodB threadName=B begin time=1639384700433
end endTime=1639384705437
可以看出methodA执行完之后,methodB方法才开始执行。
因此我们可以得出结论:
1、A线程持有Object对象的Lock锁,B线程可以以异步方式调用Object对象中的非synchronized类型的方法
2、A线程持有Object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需要等待,也就是同步
锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。
这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
定义三个同步方法,它们之间顺序调用:
public class MyDomain3 { 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 Mythread3 extends Thread { public void run() {
MyDomain3 service = new MyDomain3();
service.service1();
} }
@Test
public void test3() throws InterruptedException {
Mythread3 t = new Mythread3();
t.start(); t.join();
}
执行结果:
service1
service2
service3
“可重入锁”的概念是:自己可以再次获取自己的内部锁。
比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
定义子父类:
public class MyDomain3_1_Father { public int i = 10; synchronized public void operateIMainMethod() {
try {
i--;
System.out.println("main print i=" + i);
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} }
public class MyDomain3_1_Son extends MyDomain3_1_Father { synchronized public void operateISubMethod() {
try {
while (i > 0) {
i--;
System.out.println("sub print i=" + i);
Thread.sleep(100);
this.operateIMainMethod();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
线程类执行子类方法
public class Mythread3_1 extends Thread {
@Override
public void run() {
MyDomain3_1_Son sub = new MyDomain3_1_Son();
sub.operateISubMethod();
} }
@Test
public void test3() throws InterruptedException {
// 子类完全可以通过可重入锁调用父类的同步方法
Mythread3_1 t = new Mythread3_1();
t.start(); t.join();
}
执行结果:
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
当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
异常自动释放锁
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
模拟的是把一个long型数作为除数,从MAX_VALUE开始递减,直至减为0,从而产生ArithmeticException。看一下例子:
public class MyDomain4 { synchronized public void testMethod() {
try {
System.out.println(Thread.currentThread().getName() + "进入synchronized方法");
long l = Integer.MAX_VALUE;
while (true) {
long lo = 2 / l;
l--;
}
} catch (Exception e) {
e.printStackTrace();
}
} }
public class Mythread4 extends Thread { private MyDomain4 service; public Mythread4(MyDomain4 service) {
this.service = service;
} public void run() {
service.testMethod();
} }
@Test
public void test4() throws InterruptedException {
MyDomain4 myDomain4 = new MyDomain4();
Mythread4 a = new Mythread4(myDomain4);
Mythread4 b = new Mythread4(myDomain4);
a.start();
b.start();
a.join();
b.join();
}
执行结果:
Thread-0进入synchronized方法
java.lang.ArithmeticException: / by zero
at multithreading.synchronizedDemo.MyDomain4.testMethod(MyDomain4.java:15)
at multithreading.synchronizedDemo.Mythread4.run(Mythread4.java:17)
Thread-1进入synchronized方法
java.lang.ArithmeticException: / by zero
at multithreading.synchronizedDemo.MyDomain4.testMethod(MyDomain4.java:15)
at multithreading.synchronizedDemo.Mythread4.run(Mythread4.java:17)
加锁方式
synchronized 的不同加锁方式有不同是阻塞效果
1:synchronized (this)
在实体类中定义两个同步方法
public class MyDomain8 { public void serviceMethodA() {
synchronized (this) {
try {
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 serviceMethodB() {
synchronized (this) {
System.out.println("B begin time = " + System.currentTimeMillis());
System.out.println("B end time = " + System.currentTimeMillis());
}
}
}
调用同步方法B
public class MyThread8_1 extends Thread { private MyDomain8 td; public MyThread8_1(MyDomain8 td) {
this.td = td;
} public void run() {
td.serviceMethodB();
} }
调用同步方法A
public class MyThread8_2 extends Thread { private MyDomain8 td; public MyThread8_2(MyDomain8 td) {
this.td = td;
} public void run() {
td.serviceMethodA();
} }
@Test
public void test8() throws InterruptedException {
MyDomain8 td = new MyDomain8();
MyThread8_1 a = new MyThread8_1(td);
MyThread8_2 b = new MyThread8_2(td);
a.start();
b.start();
a.join();
b.join();
}
执行结果:
B begin time = 1639386305338
B end time = 1639386305338
A begin time = 1639386305339
A end time = 1639386307339
synchronized(this)块 获得的是一个对象锁,换句话说,synchronized块锁定的是整个对象。
2:synchronized 非静态方法
public class MyDomain9 { public void serviceMethodA() {
synchronized (this) {
try {
System.out.println("A begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} }
} synchronized public void serviceMethodB() {
System.out.println("B begin time = " + System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("B end time = " + System.currentTimeMillis());
}
}
@Test
public void test9() throws InterruptedException {
MyDomain9 td = new MyDomain9();
MyThread9_1 a = new MyThread9_1(td);
MyThread9_2 b = new MyThread9_2(td);
a.start();
b.start();
a.join();
b.join();
}
执行结果:
B begin time = 1639386457971
B end time = 1639386459972
A begin time = 1639386459974
A end time = 1639386461974
结论:(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态
(2)同一时间只有一个线程可以执行synchronized同步方法中的代码
3:synchronized(非this非自身其他对象x)
定义一个非自身对象的锁
public class MyDomain10 { private String anyString = new String(); public void serviceMethodA() {
synchronized (anyString) {
try {
System.out.println("A begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} }
} synchronized public void serviceMethodB() {
try {
System.out.println("B begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("B end time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
public void test10() throws InterruptedException {
MyDomain10 td = new MyDomain10();
MyThread10_1 a = new MyThread10_1(td);
MyThread10_2 b = new MyThread10_2(td);
a.start();
b.start();
a.join();
b.join();
}
执行结果:
B begin time = 1639387076574
A begin time = 1639387076575
B end time = 1639387078577
A end time = 1639387078578
两个方法几乎同时执行,同时结束,说明 synchronized(非this非自身其他对象x)代码块与synchronized方法呈非阻塞状态。
4:synchronized(非this自身对象x)
定义一个分别需要自身对象的同步方法A和 非静态同步方法B
public class MyDomain11 { public void serviceMethodA(MyDomain11 anyString) {
synchronized (anyString) {
try {
System.out.println("A begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} }
} synchronized public void serviceMethodB() {
try {
System.out.println("B begin time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("B end time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void test11() throws InterruptedException {
MyDomain11 td = new MyDomain11();
MyThread11_1 a = new MyThread11_1(td);
MyThread11_2 b = new MyThread11_2(td);
a.start();
b.start();
a.join();
b.join();
}
执行结果:
B begin time = 1639387213624
B end time = 1639387215625
A begin time = 1639387215625
A end time = 1639387217628
可见 synchronized(非this自身对象x)代码块与synchronized方法或synchronized(this)都呈阻塞状态,
这两个测试方法,也能证明 synchronized方法与synchronized(this) 持有的都是对象锁。
5:synchronized静态方法
代表的是对当前.java文件对应的Class类加锁
public class MyDomain12 { public synchronized 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 (InterruptedException e) {
e.printStackTrace();
}
} public static void printB() {
// synchronized静态方法持有的是对当前.java文件对应的Class类加锁
synchronized (MyDomain12.class) {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB()方法");
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB()方法");
} } public synchronized void printC() {
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC()方法");
System.out.println(
"线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC()方法");
} }
@Test
public void test12() throws InterruptedException {
MyDomain12 md = new MyDomain12();
Mythread12_1 mt1 = new Mythread12_1();
Mythread12_2 mt2 = new Mythread12_2();
Mythread12_3 mt3 = new Mythread12_3(md);
mt1.start();
mt2.start();
mt3.start(); mt1.join();
mt2.join();
mt3.join();
}
执行结果:
线程名称为:Thread-0在1639387516115进入printA()方法
线程名称为:Thread-2在1639387516116进入printC()方法
线程名称为:Thread-2在1639387516116离开printC()方法
线程名称为:Thread-0在1639387519119离开printA()方法
线程名称为:Thread-1在1639387519120进入printB()方法
线程名称为:Thread-1在1639387519120离开printB()方法
结论:synchronized静态方法也是互斥的(printA和printB可以看出)
synchronized静态方法与synchronized方法持有的是不同的锁(printC()方法的调用和对printA()方法、printB()方法的调用时异步的)
synchronized静态方法持有的是对当前.java文件对应的Class类加锁
参考文献
1:《Java并发编程的艺术》
2:《Java多线程编程核心技术》
java多线程3:synchronized的更多相关文章
- Java多线程-同步:synchronized 和线程通信:生产者消费者模式
大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...
- Java多线程同步 synchronized 关键字的使用
代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...
- Java多线程同步方法Synchronized和volatile
11 同步方法 synchronized – 同时解决了有序性.可见性问题 volatile – 结果可见性问题 12 同步- synchronized synchronized可以在任意对象上加 ...
- Java多线程:synchronized的可重入性
从Java多线程:线程间通信之volatile与sychronized这篇文章中我们了解了synchronized的基本特性,知道了一旦有一个线程访问某个对象的synchronized修饰的方法或代码 ...
- java 多线程8 : synchronized锁机制 之 方法锁
脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数 ...
- Java 多线程之 synchronized 和 volatile 的比較
概述 在做多线程并发处理时,常常须要对资源进行可见性訪问和相互排斥同步操作.有时候,我们可能从前辈那里得知我们须要对资源进行 volatile 或是 synchronized 关键字修饰处理.但是,我 ...
- 四、java多线程核心技术——synchronized同步方法与synchronized同步快
一.synchronized同步方法 论:"线程安全"与"非线程安全"是多线程的经典问题.synchronized()方法就是解决非线程安全的. 1.方法内的变 ...
- 关于JAVA多线程并发synchronized的测试与合理使用
在项目开发中, 或许会碰到JAVA的多线程处理, 为保证业务数据的正常, 必须加上锁机制, 常用的处理方法一般是加上synchronized关键字, 目前JDK版本对synchronized已经做了 ...
- java多线程中synchronized关键字的用法
转自:http://www.cdtarena.com/javapx/201308/9596.html 由于同一进程内的多个线程共享内存空间,在Java中,就是共享实例,当多个线程试图同时修改某个实例的 ...
- Java多线程:synchronized关键字和Lock
一.synchronized synchronized关键字可以用于声明方法,也可以用来声明代码块,下面分别看一下具体的场景(摘抄自<大型网站系统与Java中间件实践>) 案例一:其中fo ...
随机推荐
- UE4中C++编程(一)
一: C++工程和Gameplay框架 GameInstance 它适合放置独立于关卡的信息,比如说显示UI. GameMode 表示游戏玩法, 包含游戏进行的规则和胜利条件等等信息,游戏模式是和关卡 ...
- [Vue]浅谈Vue3组合式API带来的好处以及选项API的坏处
前言 如果是经验不够多的同志在学习Vue的时候,在最开始会接触到Vue传统的方式(选项式API),后边会接触到Vue3的新方式 -- 组合式API.相信会有不少同志会陷入迷茫,因为我第一次听到新的名词 ...
- 【开源库推荐】#3 Android EventBus的使用
原文地址:[开源库推荐]#3 Android EventBus的使用 | Stars-One的杂货小窝 EventBus的Github Event bus for Android and Java t ...
- c++ 中vector 常见用法(给初学者)
c++ 中 vector vector有两个参数,一个是size,表示当前vector容器内存储的元素个数,一个是capacity,表示当前vector在内存中申请的这片区域所能容纳的元素个数. ca ...
- [cf1270I]Xor on Figures
考虑一个构造:令初始$2^{k}\times 2^{k}$的矩阵为$A$(下标从0开始),再构造一个矩阵$T$,满足仅有$T_{x_{i},y_{i}}=1$(其余位置都为0),定义矩阵卷积$\oti ...
- Spring MVC应用
Spring MVC简介 1.1 经典三层结构 在JavaEE开发中,几乎全部都是基于B/S架构的开发.那么在B/S架构中,系统标准的三层架构包括:表现层.业务层.持久层.三层架构在我们的实际开发中使 ...
- Lilypond+TexLive(LuaLatex+lyluatex)+VS Code实现谱文混排
没想到发文章反而更难被预览了,那就复制一份到随笔里好了. 多次尝试之下,终于实现了现阶段谱文混排的最理想方式: 1. 综合Latex的排版(还有广泛适用人群)的优势以及Lilypond的美观优势: 2 ...
- Python技法1:变长和定长序列拆分
Python中的任何序列(可迭代的对象)都可以通过赋值操作进行拆分,包括但不限于元组.列表.字符串.文件.迭代器.生成器等. 元组拆分 元组拆分是最为常见的一种拆分,示例如下: p = (4, 5) ...
- 毕业设计之mysql+主从复制+keepalived
环境介绍 mysql_VIP:192.168.111.123 mysql_M!:192.168.111.151 mysql_M2:192.168.111.152 安装mysql可以查看 两个数据库都需 ...
- Oracle、MySQL关机操作步骤
一.Oracle数据库单机关机(eg:LEAP系统) 先关闭使用数据库的应用系统,再关闭数据库 关闭数据库执行以下命令 1.关闭Oracle数据库监听器:(使用操作系统下管理Oracle的账户,关闭监 ...