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实现有返回结果的多线程.其中前两种方式线程执行完后都没 ...
随机推荐
- 一道面试题引发的对javascript类型转换的思考
最近群里有人发了下面这题:实现一个函数,运算结果可以满足如下预期结果: add(1)(2) // 3 add(1, 2, 3)(10) // 16 add(1)(2)(3)(4)(5) // 15 对 ...
- JavaWeb之DBUtils
一.什么是DBUtils及作用 DBUtils是apache公司写的.DBUtils是java编程中的数据库操作实用工具,小巧简单实用. DBUtils封装了对JDBC的操作,简化了JDBC操作.可以 ...
- 2017年的golang、python、php、c++、c、java、Nodejs性能对比(golang python php c++ java Nodejs Performance)
2017年的golang.python.php.c++.c.java.Nodejs性能对比 本人在PHP/C++/Go/Py时,突发奇想,想把最近主流的编程语言性能作个简单的比较, 至于怎么比,还是不 ...
- 【解题报告】pojP1436 Horizontally Visible Segments
http://poj.org/problem?id=1436 题目大意:有n条平行于x轴的线段,每条线段有y坐标,如果两条线段有一段x坐标数值相等,且中间没有其它线段阻隔,则称这两条线段"照 ...
- Search a 2D Matrix leetcode
Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the follo ...
- oslo_config中的DuplicateOptError坑
前言: 最近在重写公司的Cinder Driver,我们driver是按照OpenStack的要求,依赖一个叫oslo_config的一个包.这个包的作用就是让driver申明所依赖的选项(可以来自文 ...
- Python--定时给Ta讲笑话
受到这篇文章的启发http://python.jobbole.com/84796/,我也动手写了个程序玩一玩. 接口请求说明: 接口请求地址http://api.1-blog.com/biz/bizs ...
- pom.xml配置文件配置jar(不用记,快速配置)
1:网址:http://mvnrepository.com/ 2:在搜索栏搜索要用的框架;例如spring *以下为示例
- django进阶-3
先看效果图: 登陆admin后的界面: 查看作者: 当然你也可以定制admin, 使界面更牛逼 数据库表结构: app01/models.py from django.db import models ...
- vs打开项目出错:未找到导入的项目“C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\BuildCustomizations\CUDA 5.0.props”的解决办法
有时候由于CUDA升级或者下载的源码原创建项目的CUDA版本与自己的不同,在打开项目的时候发现加载不上,提示:未找到导入的项目“C:\Program Files (x86)\MSBuild\Micro ...