由前言:

在第一章已经出现了非线程安全的情况。“非线程安全”其实会发生在多个线程同时对同一个对象中的实例变量进行访问时发生。产生的结果就是脏读(读到被修改过的数据)。

“线程安全”获得的实例变量是经过同步处理的,不会出现脏读的情况。

2.1.1方法内的变量为线程安全:

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

在方法内部声明变量,是不存在“非线程安全”问题的。

变量所在类:

public class SafeVariable {
public void addI(String username) {
try {
int num = 0;
if(username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程代码1:

public class Thread1 extends Thread {
private SafeVariable sv ;
public Thread1(SafeVariable sv) {
this.sv = sv;
} @Override
public void run() {
sv.addI("a");
}
}

线程代码2:

public class Thread2 extends Thread {
private SafeVariable sv ;
public Thread2(SafeVariable sv) {
this.sv = sv;
} @Override
public void run() {
sv.addI("b");
}
}

执行代码:

public class Main {
public static void main(String[] args) {
SafeVariable sv = new SafeVariable();
Thread1 thread1 = new Thread1(sv);
thread1.start();
Thread2 thread2 = new Thread2(sv);
thread2.start();
}
}

执行结果:

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

2.1.2实例变量非线程安全:

如果多个线程同时访问一个对象的实例变量,则可能会出现“非线程安全问题”。

有多个实例变量可能会出现交叉的情况,如果仅有一个实例变量时可能会出现覆盖的情况。

实例变量所在类:

public class SafeVariable1 {
private int num = 0;
public void addI(String username) {
try {
if ("a".equals(username)) {
num = 100;
System.out.println("a set over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程代码1:

public class Thread3 extends Thread {
private SafeVariable1 sv ;
public Thread3(SafeVariable1 sv) {
this.sv = sv;
} @Override
public void run() {
sv.addI("a");
}
}

线程代码2:

public class Thread4 extends Thread {
private SafeVariable1 sv ;
public Thread4(SafeVariable1 sv) {
this.sv = sv;
} @Override
public void run() {
sv.addI("b");
}
}

执行代码:

public class Main {
public static void main(String[] args) {
SafeVariable1 sv = new SafeVariable1();
Thread3 thread3 = new Thread3(sv);
thread3.start();
Thread4 thread4 = new Thread4(sv);
thread4.start();
}
}

执行结果:

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

此处:若想解决“非线程安全问题”,只需要在addI()方法前加关键字synchronized即可实现。

实例变量所在类:

public class SafeVariable1 {
private int num = 0;
synchronized public void addI(String username) {
try {
if ("a".equals(username)) {
num = 100;
System.out.println("a set over!");
Thread.sleep(1000);
} else {
num = 200;
System.out.println("b set over");
}
System.out.println(username + " num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

从上述可以看出,在两(多)个线程同时访问同一个对象中的同步方法时,一定是线程安全的。

2.1.3多个对象多个锁:

实例变量所在类:

public class SafeVariable2 {
private int num = 0;
synchronized public void addI(String username) {
try {
if("a".equals(username)) {
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) {
e.printStackTrace();
}
}
}

线程代码1:

public class Thread5 extends Thread {
private SafeVariable2 sv;
public Thread5(SafeVariable2 sv) {
this.sv = sv;
} @Override
public void run() {
sv.addI("a");
}
}

线程代码2:

public class Thread6 extends Thread {
private SafeVariable2 sv;
public Thread6(SafeVariable2 sv) {
this.sv = sv;
} @Override
public void run() {
sv.addI("b");
}
}

执行代码:

public class Main {
public static void main(String[] args) {
SafeVariable2 sv1 = new SafeVariable2();
SafeVariable2 sv2 = new SafeVariable2();
Thread5 thread5 = new Thread5(sv1);
thread5.start();
Thread6 thread6 = new Thread6(sv2);
thread6.start();
}
}

执行结果:

该示例:两个线程分别访问了一个类的两个实例,效果却是异步的。

原因:关键字synchronized取得的锁都是对象锁,而不是把一段代码或者方法(函数)当作锁。

所以要想实现同步效果,必须是多个线程访问同一个对象时才能够实现,而此示例中却是两个线程访问各自的对象,创建的也是各自对象的锁,所以展现的结果自然为异步的。

同步的单词:synchronized      异步的单词:asynchronized

2.1.4synchronized方法与锁对象:

证明synchronized加锁的是对象。

对象类:

public class MyObject {
public void methodA() {
try {
System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程代码1:

public class Thread7 extends Thread {
private MyObject object;
public Thread7(MyObject object) {
this.object = object;
} @Override
public void run() {
object.methodA();
}
}

线程代码2:

public class Thread8 extends Thread {
private MyObject object;
public Thread8(MyObject object) {
this.object = object;
} @Override
public void run() {
object.methodA();
}
}

执行代码:

public class Main {
public static void main(String[] args) {
MyObject object = new MyObject();
Thread7 thread7 = new Thread7(object);
thread7.setName("A");
Thread8 thread8 = new Thread8(object);
thread8.setName("B");
thread7.start();
thread8.start();
}
}

执行结果:

修改后的对象类:

public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

修改后的结果:

上述对比发现:调用关键字synchronized声明的方法一定是排队运行的。那现在有猜想:同一个对象中的其他非synchronized声明的方法被调用时会怎么样呢?

对象类:

public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
} public void methodB() {
try {
System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程代码1:

public class Thread9 extends Thread {
private MyObject object;
public Thread9(MyObject object) {
this.object = object;
} @Override
public void run() {
object.methodA();
}
}

线程代码2:

public class Thread10 extends Thread {
private MyObject object;
public Thread10(MyObject object) {
this.object = object;
} @Override
public void run() {
object.methodB();
}
}

执行代码:

public class Main {
public static void main(String[] args) {
MyObject object = new MyObject();
Thread9 thread9 = new Thread9(object);
thread9.setName("A");
Thread10 thread10 = new Thread10(object);
thread10.setName("B");
thread9.start();
thread10.start();
}
}

执行结果:

可以看出,虽然线程A先持有了object对象的锁,但是线程B仍可以完全调用非synchronized类型的方法。

继续测试,此时将线程methodB()也加上同步锁synchronized关键字。

修改对象类:

public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
} synchronized public void methodB() {
try {
System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

综上得出结论:

  1. A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
  2. A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法时,需要等待,也就是同步。

2.1.5脏读:

在2.1.4中通过synchronized关键字在赋值的时候使用了同步,但是在取值时也有可能遇到意想不到的意外。这种情况就是脏读,发生脏读的情况是:在读取实例变量时,此值已经被其他线程修改过了。

公共类:

public class PublicVar {
public String username = "A";
public 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 thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password );
} catch (InterruptedException e) {
e.printStackTrace();
}
} public void getValue() {
System.out.println("getValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password );
}
}

线程代码:

public class Thread11 extends Thread {
public PublicVar pv;
public Thread11(PublicVar pv) {
this.pv = pv;
} @Override
public void run() {
pv.setValue("B","BB");
}
}

执行代码:

public class Main {
public static void main(String[] args) {
try {
PublicVar pv = new PublicVar();
Thread11 thread11 = new Thread11(pv);
thread11.start();
Thread.sleep(5000);//打印结果受此值大小影响
pv.getValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

出现脏读的原因在于getValue()方法不是同步的,所以可以在任意时刻进行调用。解决方法是加上synchronized关键字。

总结:

当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已经被同时赋值,不存在脏读的基本环境。

2.1.6synchronized锁重入:

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

服务类代码:

public class Myservice {
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 Thread12 extends Thread {
@Override
public void run() {
Myservice myservice = new Myservice();
myservice.service1();
}
}

执行代码:

public class Main {
public static void main(String[] args) {
Thread12 thread12 = new Thread12();
thread12.start();
}
}

执行结果:

可重入锁概念:自己可以再次获得自己的内部锁,比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获得这个对象的锁的时候还是可以获得的,如果不可锁重入,会导致进入死循环。

可重入锁也可以出现在继承中。

父类:

public class Father {
public int i = 10;
synchronized public void fatherWay() {
try {
i--;
System.out.println("father print i = " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

子类:

public class Son extends Father {
synchronized public void sonWay() {
try {
while (i > 0) {
i--;
System.out.println("son print i = " + i);
Thread.sleep(1000);
this.fatherWay();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程代码:

public class Thread13 extends Thread {
@Override
public void run() {
Son son = new Son();
son.sonWay();
}
}

执行代码:

public class Main {
public static void main(String[] args) {
Thread13 thread13 = new Thread13();
thread13.start();
}
}

执行结果:

上述示例表明:当存在父子类继承时,子类完全可以使用锁重入调用父类的同步方法。

2.1.7出现异常,锁自动释放:

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

业务类代码:

public class Service1 {
synchronized public void testMethod() {
if ("a".equals(Thread.currentThread().getName())) {
System.out.println("ThreadName = " + Thread.currentThread().getName() + " run beginTime = " + System.currentTimeMillis());
int i = 1;
while (i == 1) {
if(("" + Math.random()).substring(0,8).equals("0.123456")) {
System.out.println("ThreadName = " + Thread.currentThread().getName() + " run exceptionTime = " + System.currentTimeMillis());
Integer.parseInt("a");
}
}
} else {
System.out.println("Thread B run Time = " + System.currentTimeMillis());
}
}
}

线程代码1:

public class Thread14 extends Thread {
private Service1 service1;
public Thread14(Service1 service1) {
this.service1 = service1;
} @Override
public void run() {
service1.testMethod();
}
}

线程代码2:

public class Thread15 extends Thread {
private Service1 service1;
public Thread15(Service1 service1) {
this.service1 = service1;
} @Override
public void run() {
service1.testMethod();
}
}

执行代码:

public class Main {
public static void main(String[] args) {
try {
Service1 service1 = new Service1();
Thread14 a = new Thread14(service1);
a.setName("a");
a.start();
Thread.sleep(5000);
Thread15 b = new Thread15(service1);
b.setName("b");
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

执行结果:

可以看到的是,当出现异常后,锁就被释放了,线程B能够进入方法正常打印。

2.1.8同步不具有继承性:

同步是不可以被继承的:

父类业务类:

public class FatherService {
synchronized public void fatherService() {
try {
System.out.println("int father 下一步 sleep begin threadName = " + Thread.currentThread().getName()
+ " time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int father 下一步 sleep end threadName = " + Thread.currentThread().getName()
+ " time = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

子类业务类:

public class SonService extends FatherService {
@Override
public void fatherService() {
try {
System.out.println("int son 下一步 sleep begin threadName = " + Thread.currentThread().getName()
+ " time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int son 下一步 sleep end threadName = " + Thread.currentThread().getName()
+ " time = " + System.currentTimeMillis());
super.fatherService();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程代码1:

public class Thread16 extends Thread {
private SonService sonService;
public Thread16(SonService sonService) {
this.sonService = sonService;
} @Override
public void run() {
sonService.fatherService();
}
}

线程代码2:

public class Thread17 extends Thread {
private SonService sonService;
public Thread17(SonService sonService) {
this.sonService = sonService;
} @Override
public void run() {
sonService.fatherService();
}
}

执行代码:

public class Main {
public static void main(String[] args) {
SonService sonService = new SonService();
Thread16 a = new Thread16(sonService);
a.setName("A");
a.start();
Thread17 b = new Thread17(sonService);
b.setName("B");
b.start();
}
}

执行结果:

由结果可以看到,代码的执行是非同步的,这也就说明了synchronized关键字是不可以继承的,此时为了实现同步,必须在子类的方法上加上关键字synchronized才行。

修改后的子类业务类:

public class SonService extends FatherService {
@Override
public synchronized void fatherService() {
try {
System.out.println("int son 下一步 sleep begin threadName = " + Thread.currentThread().getName()
+ " time = " + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int son 下一步 sleep end threadName = " + Thread.currentThread().getName()
+ " time = " + System.currentTimeMillis());
super.fatherService();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

修改后的执行结果:

源码地址:https://github.com/lilinzhiyu/threadLearning

本文内容是书中内容兼具自己的个人看法所成。可能在个人看法上会有诸多问题(毕竟知识量有限,导致认知也有限),如果读者觉得有问题请大胆提出,我们可以相互交流、相互学习,欢迎你们的到来,心成意足,等待您的评价。

2.1synchronized同步方法的更多相关文章

  1. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  2. 又踩.NET Core的坑:在同步方法中调用异步方法Wait时发生死锁(deadlock)

    之前在将 Memcached 客户端 EnyimMemcached 迁移 .NET Core 时被这个“坑”坑的刻骨铭心(详见以下链接),当时以为只是在构造函数中调用异步方法(注:这里的异步方法都是指 ...

  3. Java线程:线程的同步-同步方法

    Java线程:线程的同步-同步方法   线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...

  4. 水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃

    之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock).自己也吃过这个苦头,详见等到花儿也谢了的await. 昨天一个偶然的情况,造成 ...

  5. Linux内核同步方法

    1.原子操作,是其它同步方法的基础. 2.自旋锁,线程试图获取一个已经被别人持有的自旋锁,当前线程处于忙等待,占用cpu资源. 3.读写自旋锁,根据通用性和针对性的特点,普通自旋锁在特定场景下的表现会 ...

  6. 一个类有两个方法,其中一个是同步的,另一个是非同步的; 现在又两个线程A和B,请问:当线程A访问此类的同步方法时,线程B是否能访问此类的非同步方法?

    一个类有两个方法,其中一个是同步的,另一个是非同步的:现在又两个线程A和B,请问:当线程A访问此类的同步方法时,线程B是否能访问此类的非同步方法? 答案:可以 验证 package com.my.te ...

  7. java多线程(二)——锁机制synchronized(同步方法)

    synchronized Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中 ...

  8. Java静态同步方法和非静态同步方法

             所有的非静态同步方法用的都是同一把锁——该实例对象本身.也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁进而执行 ...

  9. 各种同步方法性能比较(synchronized,ReentrantLock,Atomic)

    5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,以及各种Atomic类.了解其性能的优劣程度,有助与我们在特定的 ...

随机推荐

  1. 关于JavaScript日期类型处理的总结

    在任何一门开发语言中,对日期时间类型的处理,必不可少也非常重要,长期以来对于JS的日期类型处理较为苍白.在这里做一个浅显的总结. Date 对象用于处理日期和时间.Date 对象用于处理日期和时间.D ...

  2. [Python] 文科生零基础学编程系列一——对象、集合、属性、方法的基本定义

    1.编程语言: 1.1是什么: 编程语言(programming language),是用来定义计算机程序的形式语言.它是一种被标准化的交流技巧,用来向计算机发出指令. 一种计算机语言让程序员能够准确 ...

  3. LINUX 笔记-文件隐藏属性

    chmod u+s xxx 设置setuid(4775) chmod g+s xxx 设置gid(2775) chmod o+t xxx 设置stick bit,针对目录(1775)

  4. JAVA基础知识总结:三

    一.Java语句的执行结构 1.顺序语句 按照顺序从上往下依次执行的语句,中间没有任何的判断和跳转 2.分支语句 根据不同的条件来产生不同的分支 if语句.switch语句 3.循环语句 重复执行某句 ...

  5. 【前端】javascript实现导航栏筋斗云效果特效

    实现效果: 实现效果如下图所示 实现原理: 什么是筋斗云效果: 这个效果很简单,就是鼠标移到其他导航目录时会有背景图片跟着鼠标滑动到当前的目录. 实现思路: 鼠标经过的时候,利用offsetLeft获 ...

  6. hibernate的操作Blob和Clob类型数据(笔记)

  7. 写出易于调试的SQL

    1.前言 相比高级语言的调试如C# , 调试SQL是件痛苦的事 . 特别是那些上千行的存储过程, 更是我等码农的噩梦. 在将上千行存储过程的SQL 分解到 C# 管理后, 也存在调试的不通畅, 如何让 ...

  8. C#与lua相互调用

    Lua是一种很好的扩展性语言,Lua解释器被设计成一个很容易嵌入到宿主程序的库.LuaInterface则用于实现Lua和CLR的混合编程. (一)C#调用Lua 测试环境:在VS2015中建一个C# ...

  9. 高性能 Lua 技巧(译)

    高性能 Lua 技巧(译) 来源 https://segmentfault.com/a/1190000004372649 此为 Lua Programming Gems 一书的第二章:Lua Perf ...

  10. Muduo阅读笔记--base(二)

    上一篇文章对muduo的入门做了介绍. http://www.cnblogs.com/LCCRNblog/p/5668035.html base文件夹下这么多代码,该如何入手呢?对于我这种第一次接触大 ...