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. 在create-react-app创建的项目下允许函数绑定运算符

    前话 React的函数绑定一致是个问题,主要有下面几种方式: 事件处理器动态绑定 export default class Com extends React.Component { render() ...

  2. 7-31 The World's Richest(25 分)

    Forbes magazine publishes every year its list of billionaires based on the annual ranking of the wor ...

  3. 折半查找(C语言)

    一.二分查找 在C和C++里,二分查找是针对有序数组所用的一种快速查找元素的方法. 二.二分查找的条件以及优缺点 条件:针对有序数组(元素从小到大或从大到小) 优点:查询速度较快,时间复杂度为O(n) ...

  4. 【传输协议】发送https请求,由于客户端jdk版本过高,服务端版本低。导致异常:javax.net.ssl.SSLHandshakeException: Server chose SSLv3, but that protocol version is not enabled or not supported by the client.

    本地环境jdk为1.8,服务器使用jdk版本未知.但发送https请求,抛出如下异常,解决方案. 一:发送异常内容如下 javax.net.ssl.SSLHandshakeException: Ser ...

  5. mysql 源代码目录及安装目录介绍

    1.源代码目录介绍: 1.BUILD    BUILD目录是编译.安装脚本目录,绝大部分以compile-开头,其中的SETUP.sh脚本为C和C++编译器设置了优化选项.2.client    cl ...

  6. gradle windows上面安装配置

    本文转载自: http://blog.csdn.net/u011546806/article/details/44806513 前提条件 安装jvm,并配置好了java环境变量 安装步骤 1.下载gr ...

  7. Hive的基本介绍

    Hive最初是应Facebook每天产生的海量新兴社会网络数据进行管理和机器学习的需求而产生和发展的.那么,到底什么是Hive,我们先看看Hive官网Wiki是如何介绍Hive的(https://cw ...

  8. MMO技能系统的同步机制分析

    转自:http://www.gameres.com/729629.html 此篇文章基于之前文章介绍的技能系统,主要介绍了如何实现MMO中的技能系统的同步.阅读此文章之前,推荐首先阅读前一篇文章:一个 ...

  9. 九jQuery源码解析之.each()

    jQuery中存在两个each方法都是用于遍历的, 一个是对jQuery对象集合使用.each(),用于遍历这个集合; .each(function(index,element)) 还有一个是扩展jQ ...

  10. 《Linux内核精髓:精通Linux内核必会的75个绝技》一HACK #13 使用Block I/O控制器设置I/O优先级

    HACK #13 使用Block I/O控制器设置I/O优先级 本节介绍使用Block I/O控制器的功能设置I/O优先级的方法.Block I/O控制器可以将任意进程分组,并对该分组设置I/O的优先 ...