以下是本文的目录大纲:

  一.什么时候会出现线程安全问题?

  二.如何解决线程安全问题?

  三.synchronized同步方法或者同步块 

  转载原文链接:http://www.cnblogs.com/dolphin0520/p/3923737.html

一.什么时候会出现线程安全问题?

  在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:

  由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。

  举个简单的例子:

  现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据。

  那么必然在插入数据的过程中存在两个操作:

  1)检查数据库中是否存在该条数据;

  2)如果存在,则不插入;如果不存在,则插入到数据库中。

  假如两个线程分别用thread-1和thread-2表示,某一时刻,thread-1和thread-2都读取到了数据X,那么可能会发生这种情况:

  thread-1去检查数据库中是否存在数据X,然后thread-2也接着去检查数据库中是否存在数据X。

  结果两个线程检查的结果都是数据库中不存在数据X,那么两个线程都分别将数据X插入数据库表当中。

  这个就是线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。

  这里面,这个资源被称为:临界资源(也有称为共享资源)。

  也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。

  不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。

二.如何解决线程安全问题?

  那么一般来说,是如何解决线程安全问题的呢?

  基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。

  通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

  在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。

  本文主要讲述synchronized的使用方法,Lock的使用方法在下一篇博文中讲述。

三.synchronized同步方法或者同步块

  在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁。

  举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。

  在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

  在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

  下面通过几个简单的例子来说明synchronized关键字的使用:

  1.synchronized方法

  下面这段代码中两个线程分别调用insertData对象插入数据:

public class Test {

    public static void main(String[] args)  {
final InsertData insertData = new InsertData(); new Thread() {
public void run() {
insertData.insert(Thread.currentThread());
};
}.start(); new Thread() {
public void run() {
insertData.insert(Thread.currentThread());
};
}.start();
}
} class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}

此时程序的输出结果为:

  

  说明两个线程在同时执行insert方法。

  而如果在insert方法前面加上关键字synchronized的话,运行结果为:

class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public synchronized void insert(Thread thread){
for(int i=0;i<5;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}

  从上输出结果说明,Thread-1插入数据是等Thread-0插入完数据之后才进行的。说明Thread-0和Thread-1是顺序执行insert方法的。

  这就是synchronized方法。

  不过有几点需要注意:

  1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

  2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,

  3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

  2.synchronized代码块

  synchronized代码块类似于以下这种形式:

synchronized(synObject) {

}

当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块。

  synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。

  比如上面的insert方法可以改成以下两种形式:

class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){
synchronized (this) {
for(int i=0;i<100;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}
}
class InsertData {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
private Object object = new Object(); public void insert(Thread thread){
synchronized (object) {
for(int i=0;i<100;i++){
System.out.println(thread.getName()+"在插入数据"+i);
arrayList.add(i);
}
}
}
}

从上面可以看出,synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。

  另外,每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。

  并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

看下面这段代码就明白了:

public class Test {

    public static void main(String[] args)  {
final InsertData insertData = new InsertData();
new Thread(){
@Override
public void run() {
insertData.insert();
}
}.start();
new Thread(){
@Override
public void run() {
insertData.insert1();
}
}.start();
}
} class InsertData {
public synchronized void insert(){
System.out.println("执行insert");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行insert完毕");
} public synchronized static void insert1() {
System.out.println("执行insert1");
System.out.println("执行insert1完毕");
}
}

执行结果;

  

  第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。

  下面我们看一下synchronized关键字到底做了什么事情,我们来反编译它的字节码看一下,下面这段代码反编译后的字节码为:

public class InsertData {
private Object object = new Object(); public void insert(Thread thread){
synchronized (object) { }
} public synchronized void insert1(Thread thread){ } public void insert2(Thread thread){ }
}

  从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

  有一点要注意:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

  参考资料:

  《Java编程思想》

  http://ifeve.com/synchronized-blocks/

  http://ifeve.com/java-synchronized/

  http://blog.csdn.net/ns_code/article/details/17199201

Java并发--synchronized的更多相关文章

  1. java 并发——synchronized

    java 并发--synchronized 介绍 在平常我们开发的过程中可能会遇到线程安全性的问题,为了保证线程之间操作数据的正确性,我们第一想到的可能就是使用 synchronized 并且 syn ...

  2. Java并发——synchronized关键字

    前言: 只要涉及到Java并发那么我们就会考虑线程安全,实际上能够实现线程安全的方法很多,今天先介绍一下synchronized关键字,主要从使用,原理介绍 一.synchronized的使用方法 1 ...

  3. Java并发-Synchronized关键字

    一.多线程下的i++操作的并发问题 package passtra; public class SynchronizedDemo implements Runnable{ private static ...

  4. Java并发synchronized详解

    今天和大家一起学习下并发编程,先举一个简单的生活例子,我们去医院或者银行排队叫号,那每个工作人员之间如何保证不会叫重号呢? public class TicketDemo extends Thread ...

  5. 精通java并发-synchronized关键字和锁

    目前CSDN,博客园,简书同步发表中,更多精彩欢迎访问我的gitee pages synchronized关键字和锁 示例代码 public class MyThreadTest2 { public ...

  6. 深入理解Java并发synchronized同步化的代码块不是this对象时的操作

    本文仅仅是为了说明synchronized关键字同步的是对象不是方法,列子的确有失偏颇. 一.明确一点synchronized同步的是对象不是方法也不是代码块  我有关synchronized同步的是 ...

  7. java 并发synchronized使用

    从版本1.0开始,java中每个对象都有一个内部锁,如果一个方法用synchronized修饰,那么对象的锁将保护整个方法,也就是说要调用该方法,线程必须获得内部的对象锁 换句话说 public sy ...

  8. Java并发——synchronized和ReentrantLock的联系与区别

    0 前言 本文通过使用synchronized以及Lock分别完成"生产消费场景",再引出两种锁机制的关系和区别,以及一些关于锁的知识点. 本文原创,转载请注明出处:http:// ...

  9. Java并发—synchronized关键字

    synchronized关键字的作用是线程同步,而线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. synchronized用法 1. 在需要同步的方法的方法签名中加入synchro ...

随机推荐

  1. hadoop2.6.0的eclipse插件安装

    1.安装插件 下载插件hadoop-eclipse-plugin-2.6.0.jar并将其放到eclips安装目录->plugins(插件)文件夹下.然后启动eclipse. 配置 hadoop ...

  2. 开启Tomcat APR运行模式,优化并发性能

    Tomcat支持三种接收请求的处理方式:BIO.NIO.APR 1>.BIO模式:阻塞式I/O操作,表示Tomcat使用的是传统JavaI/O操作(即Java.io包及其子包).Tomcat7以 ...

  3. Codeforces Round #378 (Div. 2)F - Drivers Dissatisfaction GNU

    http://codeforces.com/contest/733/problem/F 题意:给你一些城市和一些路,每条路有不满意程度和每减少一点不满意程度的花费,给出最大花费,要求找出花费小于s的最 ...

  4. VS2013 VC++的.cpp文件调用CUDA的.cu文件中的函数

    CUDA 8.0在函数的调用中方便的让人感动.以下是从网上学到的VC++的.cpp文件调用CUDA的.cu文件中的函数方法,和一般的VC++函数调用的方法基本没差别. 使用的CUDA版本为CUDA 8 ...

  5. RabbitMQ 如何保证消息不丢失?

    RabbitMQ一般情况很少丢失,但是不能排除意外,为了保证我们自己系统高可用,我们必须作出更好完善措施,保证系统的稳定性. 下面来介绍下,如何保证消息的绝对不丢失的问题,下面分享的绝对干货,都是在知 ...

  6. POJ 2891 中国剩余定理的非互质形式

    中国剩余定理的非互质形式 任意n个表达式一对对处理,故只需处理两个表达式. x = a(mod m) x = b(mod n) km+a = b (mod n) km = (a-b)(mod n) 利 ...

  7. mysql 5.1.71升级到5.6.30

    mysql 5.1.71升级到5.6.30 mysqldump -h主机名 -P端口 -u用户名 -p密码 (–database) 数据库名 > 文件名.sql 备份MySQL数据库的命令 my ...

  8. js鼠标键禁用功能

    页面完全禁用右键 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://ww ...

  9. 内存保护机制及绕过方法——通过伪造SEHOP链绕过SEHOP保护机制

    1.1    SEHOP保护机制 1.1.1    SEHOP工作原理: SEHOP保护机制的核心就是检查SEH链的完整性,其验证代码如下: BOOL RtlIsValidHandler(handle ...

  10. 【前端工具】 git windows下搭建全过程

    1. Git,Windows下的Git,地址:http://msysgit.googlecode.com/files/Git-1.7.9-preview20120201.exe(方便下载) 2 .SS ...