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

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

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

  举个简单的例子:

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

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

  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对象插入数据:

 package com.meng.javalanguage.thread.test;

 import java.util.ArrayList;

 public class MySynchronizedTest {

     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的锁,从而使得其它线程无法同时访问该代码块。

  比如上面的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方法占用的是对象锁

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

 package com.meng.javalanguage.thread.test;

 import java.util.ArrayList;

 public class MySynchronizedTest {

     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.currentThread().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代码块实际上多了monitorentermonitorexit两条指令。monitorenter指令执行时,会让对象的锁计数加1,而monitorexit指令执行时,会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的method_info结构是否有ACC_SYNCHRONIZED标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

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

转载自《Java并发编程:synchronized

【转】Java并发编程:synchronized的更多相关文章

  1. Java并发编程-synchronized指南

    在多线程程序中,同步修饰符用来控制对临界区代码的访问.其中一种方式是用synchronized关键字来保证代码的线程安全性.在Java中,synchronized修饰的代码块或方法不会被多个线程并发访 ...

  2. Java并发编程-synchronized

    多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题.同步机制可以使用synchronized关键字实现.synchronized关 ...

  3. java并发编程--Synchronized的理解

    synchronized实现锁的基础:Java中每一个对象都可以作为锁,具体表现为3种形式. (1)普通同步方法,锁是当前实例对象 (2)静态同步方法,锁是当前类的Class对象 (3)同步方法块,锁 ...

  4. Java并发编程 | Synchronized原理与使用

    Java提供了多种机制实现多线程之间有需要同步执行的场景需求.其中最基本的是Synchronized ,实现上使用对象监视器( Monitor ). Java中的每个对象都是与线程可以锁定或解锁的对象 ...

  5. Java并发编程:synchronized

    Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...

  6. Java并发编程:Synchronized及其实现原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  7. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  8. Java并发编程原理与实战九:synchronized的原理与使用

    一.理论层面 内置锁与互斥锁 修饰普通方法.修饰静态方法.修饰代码块 package com.roocon.thread.t3; public class Sequence { private sta ...

  9. 4、Java并发编程:synchronized

    Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...

随机推荐

  1. java io系列09之 FileDescriptor总结

    本章对FileDescriptor进行介绍 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_09.html FileDescriptor 介绍 Fil ...

  2. 1.单件模式(Singleton Pattern)

    创建型模式---单件模式(Singleton Pattern)动机(Motivation):    在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性. ...

  3. input:checked + label用法

    input:checked ~ label   :相邻同胞选择器,选择被选中的input标签后 所有的label标签[input  和 label标签有共同的父元素]: input:checked + ...

  4. [leetcode-108,109] 将有序数组转换为二叉搜索树

    109. 有序链表转换二叉搜索树 Given a singly linked list where elements are sorted in ascending order, convert it ...

  5. 在java1.8下使用jetty报错java.lang.CharSequence cannot be resolved

    环境: JDK: 1.8Jetty: jetty6,jetty7(在eclipse中使用run-jetty-run插件) 在JSP页面中使用StringBuilder或者StringBuffer,示例 ...

  6. 浅谈style.,currentStyle,getComputedStyle,getAttribute

    xxx为属性. ele为元素. 1.style.是针对于样式 在前面的一篇博客中我也有说到,ele.style.xxx; 通常用于赋值,赋值也是针对于行内样式,用它来取值的话,它只能取到内联样式. 今 ...

  7. IDApython教程(五)

    我们继续IDAPython让生活更美好序列,这一部分我们解决逆向工程师日常遇到的问题:提取执行的内嵌代码. 恶意软件会用各种方式存储内嵌可执行代码,有些恶意软件将内嵌代码加到文件附加段,包括PE资源区 ...

  8. springboot10-springcloud-eureka 服务注册与发现,负载均衡客户端(ribbon,feign)调用

    创建5个项目: 1.服务注册中心 2.服务提供者1 3.服务提供者2(与服务提供者1的代码实现一样,这是是为了模拟负载均衡) 4.ribbon客户端项目 5.feign客户端项目 如图: 一.注册中心 ...

  9. ubuntu完全卸载mysql

    可以先用 dpkg --list|grep mysql 查看自己的mysql有哪些依赖 一.先卸载 mysql-common sudo apt-get remove mysql-common 二.然后 ...

  10. Element-ui 中dialog的使用方法

    <template> <div> <el-button type="text" @click="dialogFormVisible = tr ...