线程安全

多个线程共同访问一个对象的实例变量,那么就可能出现线程不安全的问题。

先看一段代码示例,定义一个对象 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的更多相关文章

  1. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  2. Java多线程同步 synchronized 关键字的使用

    代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...

  3. Java多线程同步方法Synchronized和volatile

    11 同步方法  synchronized – 同时解决了有序性.可见性问题  volatile – 结果可见性问题 12 同步- synchronized synchronized可以在任意对象上加 ...

  4. Java多线程:synchronized的可重入性

    从Java多线程:线程间通信之volatile与sychronized这篇文章中我们了解了synchronized的基本特性,知道了一旦有一个线程访问某个对象的synchronized修饰的方法或代码 ...

  5. java 多线程8 : synchronized锁机制 之 方法锁

    脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数 ...

  6. Java 多线程之 synchronized 和 volatile 的比較

    概述 在做多线程并发处理时,常常须要对资源进行可见性訪问和相互排斥同步操作.有时候,我们可能从前辈那里得知我们须要对资源进行 volatile 或是 synchronized 关键字修饰处理.但是,我 ...

  7. 四、java多线程核心技术——synchronized同步方法与synchronized同步快

    一.synchronized同步方法 论:"线程安全"与"非线程安全"是多线程的经典问题.synchronized()方法就是解决非线程安全的. 1.方法内的变 ...

  8. 关于JAVA多线程并发synchronized的测试与合理使用

    在项目开发中, 或许会碰到JAVA的多线程处理, 为保证业务数据的正常, 必须加上锁机制,  常用的处理方法一般是加上synchronized关键字, 目前JDK版本对synchronized已经做了 ...

  9. java多线程中synchronized关键字的用法

    转自:http://www.cdtarena.com/javapx/201308/9596.html 由于同一进程内的多个线程共享内存空间,在Java中,就是共享实例,当多个线程试图同时修改某个实例的 ...

  10. Java多线程:synchronized关键字和Lock

    一.synchronized synchronized关键字可以用于声明方法,也可以用来声明代码块,下面分别看一下具体的场景(摘抄自<大型网站系统与Java中间件实践>) 案例一:其中fo ...

随机推荐

  1. 如何修改visual-studio的sln文件和project工程名

    关于VS的 .sln 文件和 .suo 文件 *.sln:(Visual Studio.Solution) 通过为环境提供对项目.项目项和解决方案项在磁盘上位置的引用,可将它们组织到解决方案中.比如是 ...

  2. .NET Protobuf包装器库

    Wodsoft Protobuf Wrapper 内容 关于 需求 安装 用法 序列化 反序列化 字段定义 字段排序 非空构造函数对象 获取Protobuf包装器 高级 支持的属性类型与Protobu ...

  3. 菜鸡的Java笔记 - java 枚举

    枚举        枚举属于加强版的多例设计模式            多例设计模式与枚举        多例设计模式的本质在于构造方法的私有化.而后在类的内部产生若干个实例化对象,随后利用一个 st ...

  4. vue3 + vite实现异步组件和路由懒加载

    在 Vue2 中,异步组件和路由懒加载处理使用 import 就可以很轻松实现.但是在Vue 3.x 中异步组件的使用与 Vue 2.x 完全不同了.本文就详细讲讲vue3中异步组件和路由懒加载的实现 ...

  5. 【NetWork】-- 网络原理

    2019-07-18  21:00:25  by冲冲 1. 网络拓扑 把网络中的计算机和通信设备抽象为一个点,把传输介质抽象为一条线,由点和线组成的几何图形就是计算机网络的拓扑结构(Network T ...

  6. Topcoder 14719 - RatingProgressAward(最小割)

    题面传送门 神仙最小割--好久没写过网络流了,故写题解以祭之( 首先考虑一个非常 trivial 的问题:如果知道排列顺序之后怎样计算最大值,用脚趾头想一下就能知道是原序列的最大子段和,因为每个课程之 ...

  7. 一类巧妙利用利用失配树的序列DP

    I.导入 求长度为\(\text{len}\)的包含给定连续子串\(\text{T}\)的 0/1 串的个数.(\(|T|<=15\)) 通常来说这种题目应该立刻联想到状压 DP 与取反集--这 ...

  8. time 查看命令执行时间

    在命令执行完成之后就会打印出CPU的使用情况: real    0m5.064s      <== 实际使用时间(real time) user    0m0.020s     <== 用 ...

  9. linux 两服务器之间的文件传输scp

    Linux scp 命令用于 Linux 之间复制文件和目录. scp 是 secure copy 的缩写, scp 是 linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令. scp 是加 ...

  10. 面向对象编程—self,继承

    目录 1. self 2. init 2.1 使用方式 2.2 init()方法的调用 2.3 总结 3. 继承 3.1 继承的概念 3.2 继承示例 3.2.1 说明 3.3 总结 3.4 多继承 ...