Java Thread系列(三)线程安全

一、什么是线程安全

线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

线程安全来说,需要满足以下两个特性:

  • 原子性

  • 可见性

public class MyThread extends Thread {

    private int count = 5;

    //synchronized加锁 同步锁
public /*synchronized*/ void run () {
System.out.printf("线程%s执行,count:%s\n", this.currentThread().getName(),--count);
} public static void main(String[] args) {
/**
* 当多个线程访问 MyThread 的 run 方法时,以排队的方式进行处理(这里的排序是按照 CPU 分配的先后顺序而定的)
* 一个线程要执行 synchronized 修饰的方法时
* 1. 尝试获得锁,如果拿到锁则执行该方法
* 2. 这个线程就会不断尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
*/
MyThread thread = new MyThread();
new Thread(thread, "t1").start();
new Thread(thread, "t2").start();
new Thread(thread, "t3").start();
new Thread(thread, "t4").start();
new Thread(thread, "t5").start();
new Thread(thread, "t6").start();
}
}

执行结果:

//MyThread 的 run() 方法不加锁 synchronized
线程t1执行,count:4
线程t4执行,count:1
线程t5执行,count:1
线程t3执行,count:2
线程t2执行,count:3 //run() 加锁 synchronized,期待的结果
线程t1执行,count:4
线程t2执行,count:3
线程t5执行,count:2
线程t4执行,count:1
线程t3执行,count:0

由此可见:

  1. 多个线程要执行 synchronized 修饰的方法时,必须获取对象锁,如果得不到这把锁,就处于等待状态,直到获取这把锁才执行这个方法。

二、什么是锁:对象锁和类锁

多个线程多个锁,多个线程,每个线程都可以拿到自己指定的锁,分别获得锁后执行 synchronized 修辞的方法。

  • 同步(synchronized)的概念就是共享,我们要牢牢记住“共享”这两个字,如果不是共享的资源,就没有必要进行同步。

  • 异步(asynchronized)的概念就是独立,相互之间不受任何制约。eg: Ajax

public class MutiThread {

    private /*static*/ int num = 0;

    /**
* synchronized:对象锁,两个对象,线程获得的就是两个不同的锁,互不影响
* static synchronized:表示类级别的锁,即便多个对象也是相同的锁
*/
public /*static*/ synchronized void printNum (String tag) {
try {
if("a".equalsIgnoreCase(tag)) {
System.out.printf("tag %s 设置成功\n", tag);
num = 100;
Thread.sleep(1000);
} else {
System.out.printf("tag %s 设置成功\n", tag);
num = 200;
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.printf("tag %s , num=%s\n", tag, num);
} public static void main(String[] args) { final MutiThread m1 = new MutiThread();
final MutiThread m2 = new MutiThread();
new Thread(new Runnable() {
public void run() {
m1.printNum("a");
}
}).start();
new Thread(new Runnable() {
public void run() {
m1.printNum("b");
}
}).start();
}
}

执行结果:

//run()方法用synchronized修辞
tag b 设置成功
tag b , num=200
tag a 设置成功
tag a , num=100 //run()方法用static synchronized修辞
tag a 设置成功
tag a , num=100
tag b 设置成功
tag b , num=200

由此可见:

  1. static synchronized 修辞的方法,属于类级别的锁,多个对象共享同一把锁。

  2. synchronized 修辞的方法,属于对象锁,一个对象一把锁。

三、脏读

对于对象的同步和异步的方法,一定要考虑问题的整体性,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)

public class DirtyRead {

    private String username;
private String password; public /*synchronized*/ void setValue(String username, String password) {
this.username = username; try {
Thread.sleep(2000);
} catch (InterruptedException e) {
//e.printStackTrace();
}
this.password = password;
System.out.printf("username=%s; password=%s\n", username, password);
} public /*synchronized*/ void getValue() {
System.out.printf("username=%s; password=%s\n", username, password);
} public static void main(String[] args) throws InterruptedException {
final DirtyRead dirtyRead = new DirtyRead();
new Thread(new Runnable() {
public void run() {
dirtyRead.setValue("admin", "admin");
}
}).start();
Thread.sleep(1000);
dirtyRead.getValue();
//username=admin; password=null 不加锁,产生脏读
//username=admin; password=admin 加锁
}
}

执行结果:

  1. setValue() 执行要 2s,而主程序 1s 时调用 getValue() ,这时username 已经赋值,而 password 仍未赋值,产生了脏读,username=admin; password=null

  2. setValue() 和 getValue() 都对 username 和 password 操作,所以要避免产生脏读,需要对这两个方法都加锁 synchronized。


每天用心记录一点点。内容也许不重要,但习惯很重要!

Java Thread系列(三)线程安全的更多相关文章

  1. Java Thread系列(四)线程通信

    Java Thread系列(四)线程通信 一.传统通信 public static void main(String[] args) { //volatile实现两个线程间数据可见性 private ...

  2. Java Thread系列(二)线程状态

    Java Thread系列(二)线程状态 一.线程的五种状态 新建状态(New):新创建了一个线程对象,尚未启动. 就绪状态(Runnable):也叫可运行状态.线程对象创建后,其他线程调用了该对象的 ...

  3. Java Thread系列(一)线程创建

    Java Thread系列(一)线程创建 Java 中创建线程主要有三种方式:继承 Thread.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实 ...

  4. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  5. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  6. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  7. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  8. Java Thread系列(十)Future 模式

    Java Thread系列(十)Future 模式 Future 模式适合在处理很耗时的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量. 一.Future 模式核心思想 如下的请求 ...

  9. java: Thread 和 runnable线程类

    java: Thread 和 runnable线程类 Java有2种实现线程的方法:Thread类,Runnable接口.(其实Thread本身就是Runnable的子类) Thread类,默认有ru ...

随机推荐

  1. .NET移动开发环境搭建

    开发工具:Xamarin Studio 社区版 下载地址 http://www.monodevelop.com/download/ 操作系统要求:Windows7及以上..NET Framework4 ...

  2. HDU4261 Estimation

    题意 Estimation Time Limit: 40000/15000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others ...

  3. phpstorm搜索匹配正则表达式

    data-position=".................................................................." 点是匹配任意一 ...

  4. 【转】linux sed命令详解

    原文网址:http://www.iteye.com/topic/587673 1. Sed简介sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”( ...

  5. Android 查看Android版本的方法

    1.通过源码查看 Android 版本 路径:build/core/version_defaults.mk PLATFORM_VERSION := 2.通过编译时终端输出查看 ============ ...

  6. mysql 统计一个列不同值的数量

    SELECT count(status = 0 OR NULL) AS a, count(status = 1 OR NULL) AS b, count(status = 2 OR NULL) AS ...

  7. [转]FluentData

    本文来自:http://fluentdata.codeplex.com/wikipage?title=Fluency&referringTitle=Home Documentation  Fl ...

  8. Hive 安装操作

    本篇为安装篇较简单: 前提:1: 安装了hadoop-1.0.4(1.0.3也可以)正常运行2:安装了hbase-0.94.3, 正常运行 接下来,安装Hive,基于已经安装好的hadoop,步骤如下 ...

  9. SpringBoot入门篇--使用IDEA创建一个SpringBoot项目

    随着技术的更新对于开发速度的追求,我们越来越不能忍受的是Spring框架对于集成开发以后大量的配置问题.所以SprigBoot应运而生,SpringBoot框架其实就是在Spring框架的外边包裹上了 ...

  10. Hive常见问题汇总

    参考资料: Hive常见问题汇总 啟動hive出錯,提示沒有權限 2015年04月02日 09:58:49 阅读数:31769 这里小编汇集,使用Hive时遇到的常见问题. 1,执行#hive命令进入 ...