java多线程基本概述(三)——同步方法
非线程安全其实是在多个线程对同一个对象实例的变量进行并发访问的时候发生,产生的后果就是脏读,也就是取到的数据是修改过的。而线程安全就是获得的实例变量的值是经过同步处理的,从而不会出现脏读现象。
什么时候使用同步呢?可以运用Brian的同步规则:
如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你就必须使用同步,而且,读写线程都必须使用相同发的监视器锁同步。
1.1、同步语句
1.1.1、实例变量非线程安全
如果我们把多个线程并发访问的实例变量转化成方法里面的局部变量,那么就不会产生线程不安全的情况了。因为每个线程拿到的变量都是该线程自己拥有,类似于ThreadLocal类的思想。下面这个例子将变量变为局部变量从而实现线程安全。
package soarhu;
import java.util.concurrent.TimeUnit;
class ObjectMonitor{
void add(String name){
try {
int num = 0; //方法里面的局部变量,不会出现并发竞争的情况。
if(name.equals("a")){
num = 100;
System.out.println("a set finish");
TimeUnit.MILLISECONDS.sleep(2);
}else{
num = 200;
System.out.println("b set finish");
}
System.out.println("user: "+name+" num: "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
} class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.add("a");
}
} class ThreadB extends Thread{
private ObjectMonitor monitor ; ThreadB(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.add("B");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitor();
ThreadA threadA = new ThreadA(objectMonitor);
ThreadB threadB = new ThreadB(objectMonitor);
threadA.start();
threadB.start();
}
}
输出结果:
a set finish
b set finish
user: B num: 200
user: a num: 100
如果将第6行的代码移到方法外面,则会出现线程安全的问题,改完后,运行结果:
a set finish
b set finish
user: B num: 200
user: a num: 200
因为此时add()方法访问的变量num为类成员变量,而且add方法没有进行同步,那么a,b两个线程就会同时进入add()方法对num变量进行修改。当a线程把num设置成100后。执行打印语句之前,这时候b线程进入了add()方法,设置num的值为200,那么最终就出现了脏读。解决方法是在add()方法上加入synchronized。使其成为同步方法。这样就会同步访问该方法,从而避免线程不安全的问题了。加入后程序的执行结果为:
a set finish
user: a num: 100
b set finish
user: B num: 200
1.1.2、多个对象多个锁
稍微修改一下上面方法,使其a,b两个线程拥有不同的锁对象,那么add()同步方法的同步意义对现在a,b两个线程没有意义了。因为锁对象不同。
package soarhu;
import java.util.concurrent.TimeUnit;
class ObjectMonitor{
int num = 0;
synchronized void add(String name){
try {
if(name.equals("a")){
num = 100;
System.out.println("a set finish");
TimeUnit.MILLISECONDS.sleep(2);
}else{
num = 200;
System.out.println("b set finish");
}
System.out.println("user: "+name+" num: "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
} class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.add("a");
}
} class ThreadB extends Thread{
private ObjectMonitor monitor ; ThreadB(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.add("B");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitor();
ObjectMonitor objectMonitor2 = new ObjectMonitor();
ThreadA threadA = new ThreadA(objectMonitor);
ThreadB threadB = new ThreadB(objectMonitor2);//使用不同的同步对象
threadA.start();
threadB.start();
}
}
输出结果:
a set finish
b set finish
user: B num: 200
user: a num: 100
结果正确,且执行方式不是同步方式,而是异步方式。那么如何使这个方式仍然按照同步的方式进行访问呢?很简单,用同步代码块
修改部分代码,如下:
class ObjectMonitor{
int num = 0;
void add(String name){
synchronized (Integer.TYPE) { //这里使用同步代码块,这里的监视器jvm里只有一份,故可以起到监视器同步的作用
try {
if (name.equals("a")) {
num = 100;
System.out.println("a set finish");
TimeUnit.MILLISECONDS.sleep(2);
} else {
num = 200;
System.out.println("b set finish");
}
System.out.println("user: " + name + " num: " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.1.3、synchronized方法与锁对象
有上面的代码可以知道,synchronized方法的锁监视器对象就是该方法所属的对象。下面看看对多个方法的调用。
package soarhu;
import java.util.concurrent.TimeUnit;
class ObjectMonitor{
synchronized void a(String name){
try {
System.out.println("a method start: "+Thread.currentThread().getName()+" begin time "+System.currentTimeMillis());
TimeUnit.MILLISECONDS.sleep(5);
System.out.println("a end time: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
} void b(String name){
try {
System.out.println("b method start: "+Thread.currentThread().getName()+" begin time "+System.currentTimeMillis());
TimeUnit.MILLISECONDS.sleep(5);
System.out.println("b end time: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.a("a");
}
} class ThreadB extends Thread{
private ObjectMonitor monitor ; ThreadB(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.b("B");
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitor();
ThreadA threadA = new ThreadA(objectMonitor);
ThreadB threadB = new ThreadB(objectMonitor);
threadA.start();
threadB.start();
}
}
输出结果:
a method start: Thread-0 begin time 1492413285921
b method start: Thread-1 begin time 1492413285921
b end time: 1492413285927
a end time: 1492413285927
可以看到a,b两个线程同步访问。虽然a线程拥有objectMonitor的锁,但是b方法并没有加同步块,所以b线程任然可以访问该监视器对象的其他非同步方法。
在b()方法加上同步关键字后,
synchronized void b(String name){
try {
System.out.println("b method start: "+Thread.currentThread().getName()+" begin time "+System.currentTimeMillis());
TimeUnit.MILLISECONDS.sleep(5);
System.out.println("b end time: " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出结果:
a method start: Thread-0 begin time 1492413504482
a end time: 1492413504489
b method start: Thread-1 begin time 1492413504489
b end time: 1492413504494
已经同步了,那么我们可以得到结论:
A线程现持有监视器对象的锁,B线程可以异步的调用该监视器对象中的非同步方法,如果B线程需要调用该监视器对象的同步方法则需要等待A线程释放锁。
1.1.4、脏读
虽然通过同步代码块对赋值进行同步,但在取值的时候,如果不加同步代码块也可能会出现脏读的情况,实例代码如下:
package soarhu;
import java.util.concurrent.TimeUnit;
class ObjectMonitor{
private String username ="c";
private String password ="ccc";
synchronized void set(String username,String password){
try {
this.username=username;
TimeUnit.MILLISECONDS.sleep(5);
this.password = password;
System.out.println("SET->thread-name: "+Thread.currentThread().getName()+" username: "+username+" password: "+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
} void get(){
System.out.println("GET->thread-name: "+Thread.currentThread().getName()+" username: "+username+" password: "+password);
}
} class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.set("a","aaa");
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitor();
ThreadA threadA = new ThreadA(objectMonitor);
threadA.start();
TimeUnit.MILLISECONDS.sleep(2);
objectMonitor.get();
}
}
输出结果:
GET->thread-name: main username: a password: ccc
SET->thread-name: Thread-0 username: a password: aaa
虽然set()方法是同步方法,但是get()方法不是同步方法,main线程读取值的过程中会读到中间状态的值(易变性)。解决办法是在get()方法加上同步代码块即可。
synchronized void get(){
System.out.println("thread-name: "+Thread.currentThread().getName()+" username: "+username+" password: "+password);
}
输出结果:
SET->thread-name: Thread-0 username: a password: aaa
GET->thread-name: main username: a password: aaa
此时已经线程安全,不会出现脏读的情况了。set,get方法同步进行。可以理解为ACID里面的脏读,a线程修改了变量没有提交,但是b线程现在读到了未提交的变量。即为脏读。
1.1.4、锁重入
synchronized拥有锁重入的功能,也就是在使用此关键字时,当一个线程得到一个对象锁后,再次请求此对象的锁是可以再次得到该对象的锁的,这也证明了在一个synchronized方法/块的内部调用该类其他的synchronized方法/块时,是永远可以得到锁的。
package soarhu;
class ObjectMonitor{ synchronized void set1(){
System.out.println("set1");
set2();
} synchronized void set2(){
System.out.println("set2");
set3();
} synchronized void set3(){
System.out.println("set3");
}
} class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.set1();
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitor();
ThreadA threadA = new ThreadA(objectMonitor);
threadA.start();
}
}
输出结果:
set1
set2
set3
可重入锁的概念就是自己可以获取自己的内部锁。当set1()没有释放锁的情况下调用set2().而且能调用通,说明这个锁是可以重入的。可重入锁也支持在父子类继承环境中。
package soarhu;
class ObjectMonitor{
synchronized void set1(){
System.out.println("set1()");
} }
class ObjectMonitorSub extends ObjectMonitor{
@Override
synchronized void set1() {
super.set1();
System.out.println("set2()");
}
}
class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.set1();
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitorSub();
ThreadA threadA = new ThreadA(objectMonitor);
threadA.start();
}
}
输出结果:
set1()
set2()
可以知道,子类可以重入父类的锁。
1.1.5、出现异常时,锁会被释放
package soarhu;
class ObjectMonitor{
synchronized void set1(){
if (Thread.currentThread().getName().equals("a")){
System.out.println("threadName: "+Thread.currentThread().getName()+" run time: "+ System.currentTimeMillis());
while (true){
try {
Thread.sleep(3000);
throw new RuntimeException("hello kitty");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}else{
System.out.println("thread b run time: "+ System.currentTimeMillis());
}
} } class ThreadA extends Thread{
private ObjectMonitor monitor ; ThreadA(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.set1();
}
}
class ThreadB extends Thread{
private ObjectMonitor monitor ; ThreadB(ObjectMonitor monitor) {
super();
this.monitor = monitor;
} @Override
public void run() {
monitor.set1();
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
ObjectMonitor objectMonitor = new ObjectMonitor();
ThreadA threada = new ThreadA(objectMonitor);
ThreadB threadb = new ThreadB(objectMonitor);
threada.setName("a");
threadb.setName("b");
threada.start();
Thread.sleep(500);
threadb.start();
}
}
输出结果:
threadName: a run time: 1492416360445
thread b run time: 1492416363445
Exception in thread "a" java.lang.RuntimeException: hello kitty
at soarhu.ObjectMonitor.set1(Test.java:9)
at soarhu.ThreadA.run(Test.java:31)
a线程运行3秒后,抛出异常,此时b线程若获得锁则会执行b的那行打印语句,如果不释放锁,则不会有这行输出。根据时间差可以看到差了3000毫秒,正好是a线程的执行时间。a异常一旦抛出,b线程就有执行的机会了。
java多线程基本概述(三)——同步方法的更多相关文章
- java多线程中的三种特性
java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...
- “全栈2019”Java多线程第二十章:同步方法产生死锁的例子
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java多线程学习(三)volatile关键字
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- 如何实现有返回值的多线程 JAVA多线程实现的三种方式
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口.执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable ...
- Java 多线程基础(三) start() 和 run()
Java 多线程基础(三) start() 和 run() 通过之前的学习可以看到,创建多线程过程中,最常用的便是 Thread 类中的 start() 方法和线程类的 run() 方法.两个方法都包 ...
- Java多线程——<一>概述、定义任务
一.概述 为什么使用线程?从c开始,任何一门高级语言的默认执行顺序是“按照编写的代码的顺序执行”,日常开发过程中写的业务逻辑,但凡不涉及并发的,都是让一个任务顺序执行以确保得到想要的结果.但是,当你的 ...
- [转载] java多线程总结(三)
转载自: http://www.cnblogs.com/lwbqqyumidi/p/3821389.html 作者:Windstep 本文主要接着前面多线程的两篇文章总结Java多线程中的线程安全问题 ...
- Java多线程编程核心技术(三)多线程通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
- Java多线程-线程的同步(同步方法)
线程的同步是保证多线程安全访问竞争资源的一种手段.线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些 ...
- JAVA多线程实现的三种方式
JAVA多线程实现方式主要有三种:继承Thread类.实现Runnable接口.使用ExecutorService.Callable.Future实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...
随机推荐
- C#同步,异步的理解,包括5.0中await和async(学习笔记)
之前在工作中一直用的是同步线程,就是先进入画面的load事件,然后在里面进行数据库调用的处理.后面又遇到了公司软件中一些比较古老的代码,一开始在那块古老代码中增加机能的时候,我想用到数据库的数据给画面 ...
- C#自动弹出窗口并定时自动关闭
最近做个小项目,用到一个小功能:后台线程定时查询数据库,不符合条件的记录弹出消息提醒(在窗口最前面),并且过几秒钟再自动关闭弹出的窗口. 所以从网上找来资料,如下: WinForm 下实现一个自动关闭 ...
- .NET入行之工作前
时间就像轻风一样,刻意感受的时候几乎把你吹倒,不留意的时候又从你身边轻轻飘走了:长此以后,我怕自己会变得麻木,忘记了原来的样子.所以还是决定给自己留点什么,万一哪天忘记了,还可以再翻起来. 工作两年的 ...
- Servlet中表单的重复提交
1.用户登录页面: ->设置一个UUID到session中 ->将UUID添加到隐藏域中,提交到服务器 <% //进入页面,设置一个UUID,将UUID添加到session中 Str ...
- 1935: [Shoi2007]Tree 园丁的烦恼
1935: [Shoi2007]Tree 园丁的烦恼 Time Limit: 15 Sec Memory Limit: 357 MBSubmit: 648 Solved: 273[Submit][ ...
- ST HW3
7. Use the following method printPrimes() for questions a-f below. /******************************** ...
- 关于在链路聚合下做smart,monitor link的转发测试
关于在链路聚合下做smart,monitor link的转发测试 这次又要开始瞎折腾了.没事找事了.毕竟我还是很无聊的: 这个实验主要要涉及到以下几个方面: 1.gvrp的应用: 2.Smart Li ...
- Javascript基础知识小测试(一)
这里罗列了<你不知道的js>上卷的一些知识点以及小问题,如果你想巩固一下js那么就和我一起来看看吧. 如果你能不看书就回答上80%的问题说明你js的这一部分学得还不错,再接再厉. 作用域和 ...
- CSS后代选择器、子元素选择器、相邻兄弟选择器区别与详解
派生选择器用的很多,派生选择器具体包括为后代选择器.子元素选择器.相邻兄弟选择器,我们来理解一下他们之间的具体用法与区别. 1.css后代选择器语法:h1 em {color:red;} 表示的是从h ...
- 解决MVC中JsonResult返回 弹出文件下载对话框
设置一下返回类型为HTML TEXT就可以了 JsonResult json = Json(xxx, JsonRequestBehavior.DenyGet); json.ContentType = ...