java 多线程8 : synchronized锁机制 之 方法锁
脏读
一个常见的概念。在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的。注意这里 局部变量是不存在脏读的情况
多线程线程实例变量非线程安全
看一段代码:
public class ThreadDomain13
{
private int num = 0; public void addNum(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();
}
}
}
写两个线程分别去add字符串"a"和字符串"b":
public class MyThread13_0 extends Thread
{
private ThreadDomain13 td; public MyThread13_0(ThreadDomain13 td)
{
this.td = td;
} public void run()
{
td.addNum("a");
}
}
public class MyThread13_1 extends Thread
{
private ThreadDomain13 td; public MyThread13_1(ThreadDomain13 td)
{
this.td = td;
} public void run()
{
td.addNum("b");
}
}
写一个主函数分别运行这两个线程:
public static void main(String[] args)
{
ThreadDomain13 td = new ThreadDomain13();
MyThread13_0 mt0 = new MyThread13_0(td);
MyThread13_1 mt1 = new MyThread13_1(td);
mt0.start();
mt1.start();
}
看一下运行结果:
a set over!
b set over!
b num = 200
a num = 200
按照正常来看应该打印"a num = 100"和"b num = 200"才对,现在却打印了"b num = 200"和"a num = 200",这就是线程安全问题。我们可以想一下是怎么会有线程安全的问题的:
1、mt0先运行,给num赋值100,然后打印出"a set over!",开始睡觉
2、mt0在睡觉的时候,mt1运行了,给num赋值200,然后打印出"b set over!",然后打印"b num = 200"
3、mt1睡完觉了,由于mt0的num和mt1的num是同一个num,所以mt1把num改为了200了,mt0也没办法,对于它来说,num只能是100,mt0继续运行代码,打印出"a num = 200"
分析了产生问题的原因,解决就很简单了,给addNum(String userName)方法加同步即可:
多线程线synchronized关键字加到方法上
public class ThreadDomain13
{
private int num = 0; public synchronized void addNum(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();
}
}
}
看一下运行结果:
a set over!
a num = 100
b set over!
b num = 200
多个对象多个锁
在同步的情况下,把main函数内的代码改一下:
public static void main(String[] args)
{
ThreadDomain13 td0 = new ThreadDomain13();
ThreadDomain13 td1 = new ThreadDomain13();
MyThread13_0 mt0 = new MyThread13_0(td0);
MyThread13_1 mt1 = new MyThread13_1(td1);
mt0.start();
mt1.start();
}
看一下运行结果:
a set over!
b set over!
b num = 200
a num = 100
打印结果的方式变了,打印的顺序是交叉的,这又是为什么呢?
这里有一个重要的概念。关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,这里如果是把一段代码或方法(函数)当作锁,其实获取的也是对象锁,只是监视器(对象)不同而已,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,其他线程都只能呈等待状态。但是这有个前提:既然锁叫做对象锁,那么势必和对象相关,所以多个线程访问的必须是同一个对象。
如果多个线程访问的是多个对象,那么Java虚拟机就会创建多个锁,就像上面的例子一样,创建了两个ThreadDomain13对象,就产生了2个锁。既然两个线程持有的是不同的锁,自然不会受到"等待释放锁"这一行为的制约,可以分别运行addNum(String userName)中的代码。
上面我们认识了对象锁,对象锁这个概念,比较抽象,确实不太好理解,看一个例子,在一个实体类中定义一个同步方法和一个非同步方法:
public class ThreadDomain14_0
{
public synchronized void methodA()
{
try
{
System.out.println("Begin methodA, threadName = " +
Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("End methodA, threadName = " +
Thread.currentThread().getName() + ", end Time = " +
System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
} public void methodB()
{
try
{
System.out.println("Begin methodB, threadName = " +
Thread.currentThread().getName() + ", begin time = " +
System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("End methodB, threadName = " +
Thread.currentThread().getName());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
一个线程调用其同步方法,一个线程调用其非同步方法:
public class MyThread14_0 extends Thread
{
private ThreadDomain14_0 td; public MyThread14_0(ThreadDomain14_0 td)
{
this.td = td;
} public void run()
{
td.methodA();
}
}
public class MyThread14_1 extends Thread
{
private ThreadDomain14_0 td; public MyThread14_1(ThreadDomain14_0 td)
{
this.td = td;
} public void run()
{
td.methodB();
}
}
写一个main函数去掉用这两个线程:
public static void main(String[] args)
{
ThreadDomain14_0 td = new ThreadDomain14_0();
MyThread14_0 mt0 = new MyThread14_0(td);
mt0.setName("A");
MyThread14_1 mt1 = new MyThread14_1(td);
mt1.setName("B");
mt0.start();
mt1.start();
}
看一下运行效果:
Begin methodA, threadName = A
Begin methodB, threadName = B, begin time = 1443697780869
End methodB, threadName = B
End methodA, threadName = A, end Time = 1443697785871
从结果看到,第一个线程调用了实体类的methodA()方法,第二个线程完全可以调用实体类的methodB()方法。但是我们把methodB()方法改为同步就不一样了,就不列修改之后的代码了,看一下运行结果:
Begin methodA, threadName = A
End methodA, threadName = A, end Time = 1443697913156
Begin methodB, threadName = B, begin time = 1443697913156
End methodB, threadName = B
从这个例子我们得出两个重要结论:
1、A线程持有Object对象的Lock锁,B线程可以以异步方式调用Object对象中的非synchronized类型的方法
2、A线程持有Object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需要等待,也就是同步
synchronized锁重入
关键字synchronized拥有锁重入的功能。所谓锁重入的意思就是:当一个线程得到一个对象锁后,再次请求此对象锁时时可以再次得到该对象的锁的。看一个例子:
public class ThreadDomain16
{
public synchronized void print1()
{
System.out.println("ThreadDomain16.print1()");
print2();
} public synchronized void print2()
{
System.out.println("ThreadDomain16.print2()");
print3();
} public synchronized void print3()
{
System.out.println("ThreadDomain16.print3()");
}
}
public class MyThread16 extends Thread
{
public void run()
{
ThreadDomain16 td = new ThreadDomain16();
td.print1();
}
}
public static void main(String[] args)
{
MyThread16 mt = new MyThread16();
mt.start();
}
看一下运行结果:
ThreadDomain16.print1()
ThreadDomain16.print2()
ThreadDomain16.print3()
看到可以直接调用ThreadDomain16中的打印语句,这证明了对象可以再次获取自己的内部锁。这种锁重入的机制,也支持在父子类继承的环境中。
异常自动释放锁
最后一个知识点是异常。当一个线程执行的代码出现异常时,其所持有的锁会自动释放。模拟的是把一个long型数作为除数,从MAX_VALUE开始递减,直至减为0,从而产生ArithmeticException。看一下例子:
public class ThreadDomain17
{
public synchronized void testMethod()
{
try
{
System.out.println("Enter ThreadDomain17.testMethod, currentThread = " +
Thread.currentThread().getName());
long l = Integer.MAX_VALUE;
while (true)
{
long lo = 2 / l;
l--;
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public class MyThread17 extends Thread
{
private ThreadDomain17 td; public MyThread17(ThreadDomain17 td)
{
this.td = td;
} public void run()
{
td.testMethod();
}
}
public static void main(String[] args)
{
ThreadDomain17 td = new ThreadDomain17();
MyThread17 mt0 = new MyThread17(td);
MyThread17 mt1 = new MyThread17(td);
mt0.start();
mt1.start();
}
看一下运行结果:
Enter ThreadDomain17.testMethod, currentThread = Thread-0
Enter ThreadDomain17.testMethod, currentThread = Thread-1
java.lang.ArithmeticException: / by zero
at com.xrq.example.e17.ThreadDomain17.testMethod(ThreadDomain17.java:14)
at com.xrq.example.e17.MyThread17.run(MyThread17.java:14)
java.lang.ArithmeticException: / by zero
at com.xrq.example.e17.ThreadDomain17.testMethod(ThreadDomain17.java:14)
at com.xrq.example.e17.MyThread17.run(MyThread17.java:14)
因为打印结果是静态的,所以不是很明显。在l--前一句加上Thread.sleep(1)结论会更明显,第一句打出来之后,整个程序都停住了,直到Thread-0抛出异常后,Thread-1才可以运行,这也证明了我们的结论。
java 多线程8 : synchronized锁机制 之 方法锁的更多相关文章
- java 多线程并发 synchronized 同步机制及方式
2. 锁机制 3. 并发 Excutor框架 4. 并发性与多线程介绍 1. synchronized 参考1. synchronized 分两种方式进行线程的同步:同步块.同步方法 1. 方法同步 ...
- “全栈2019”Java多线程第三十章:尝试获取锁tryLock()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java多线程-同步:synchronized 和线程通信:生产者消费者模式
大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...
- 二、多线程基础-乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁
1.10乐观锁_悲观锁_重入锁_读写锁_CAS无锁机制_自旋锁1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将 比较-设置 ...
- Java 多线程基础(九)join() 方法
Java 多线程基础(九)join 方法 一.join() 方法介绍 join() 定义 Thread 类中的,作用是:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程.如:线 ...
- 再谈mysql锁机制及原理—锁的诠释
加锁是实现数据库并发控制的一个非常重要的技术.当事务在对某个数据对象进行操作前,先向系统发出请求,对其加锁.加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更 ...
- Java多线程学习——synchronized锁机制
Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用) 例子:两个人操作同一个银行账户,丈夫在ATM ...
- java 多线程9 : synchronized锁机制 之 代码块锁
synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...
- java 多线程:Thread 并发线程: 方法同步synchronized关键字,与static的结合
1.方法内的变量是安全的 方法内定义的变量,每个变量对应单独的内存变量地址,多个线程之间相互不影响.多个线程之间的变量根本没有一毛钱关系 public class ThreadFuncVarSafe ...
随机推荐
- 【独立开发人员er Cocos2d-x实战 001】csb文件导出和载入
使用cocos studio进行资源文件导出: 然后在cocosproject中进行载入csb文件: auto myLayout = CSLoader::createNode("/res/ ...
- 前端开发不容错过的jQuery图片滑块插件(转)
作为前端开发者,我们会碰到很到各种各样的jQuery插件.今天要分享的几款jQuery图片滑块插件,也就是jQuery焦点图插件,基本上会在每个网站都有应用,可以下载看看,也许你可以用到. 1.jQu ...
- java开发工具之myeclipse调优
-vmargs -Xms512m //堆的最小值-Xmx512m //堆的最大值(两者设置相同,避免运行时的自动扩张)-XX:PermSize=256m //永久代的最小值 -XX:MaxPermSi ...
- tarfile模块可以方操作tar归档文件
# -*- coding: utf-8 -*- #python 27 #xiaodeng #Python自带的tarfile模块可以方便读取tar归档文件 #http://www.open-open. ...
- try/except/else语句
# -*- coding: utf-8 -*- #python 27 #xiaodeng #Python学习手册 868 #try/except/else语句 #try语句分句形式 except: # ...
- 【微信小程序】:评论、回复和删除功能 -- 2017/7/14
1.理论核心:传参->pid,评论父id需要在wxml页面传递:小程序端和WEB端不同核心:前者操纵数据,后者操纵DOM元素对象 2.不废话,直接代码:wxml <view class=& ...
- CentOS安装配置Samba
介绍 Samba可以让我们在windows中访问linux系统中的文件,如果用来调试linux虚拟机中的代码会非常的方便 1.安装 yum -y update yum install samba sa ...
- 【转】排名前十位的Linux发行版介绍
Linux Distributions众多,据说有350个之多.distrowatch.com网站评出了排名前十位的Linux发行版.它们是:1.Ubuntu,当前最新版本是10.10:2.Fedor ...
- HDUOJ--1865 1string
1sting Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Subm ...
- socket概述和字节序、地址转换函数
一.什么是socket socket可以看成是用户进程与内核网络协议栈的编程接口. socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信. socket API是一层抽象的网 ...