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

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

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

  举个简单的例子:

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

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

  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. @jsonProperty 实现返回自定义属性名字

    实现场景: 比如说前端需要返回userPic 这个字段,但是我们数据库定义的是pic字段. 可以用@jsonProperty 来实现 public class User{ @JsonProperty( ...

  2. Ado.NET基础必备

    一.SqlConnection对象 第一次需要连接数据库时要和服务器握手,解析连接字符串,授权,约束的检查等等操作,而物理连接建立后,这些操作就不会去做了(默认使用了连接池技术). SqlConnec ...

  3. 解决Navicat 出错:1130-host . is not allowed to connect to this MySql server,MySQL

    1. 改表法. 可能是你的帐号不允许从远程登陆,只能在localhost.这个时候只要在localhost的那台电脑,登入MySQL后,更改 "mysql" 数据库里的 " ...

  4. (转)windows 下安装配置 Nginx 详解

    windows 下安装配置 Nginx 详解 本文转自https://blog.csdn.net/kingscoming/article/details/79042874 nginx功能之一可以启动一 ...

  5. a标签与js的冲突

    如上图,需要做一个页面,点击左边的标题,右边就显示左边标题下的子标题的集合, html代码如下: <div id="newleft"> <ul> <l ...

  6. 【1】【leetcode-79】 单词搜索

    (典型dfs,知道思想写错) 给定一个二维网格和一个单词,找出该单词是否存在于网格中. 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格.同一个单 ...

  7. Idea构建maven项目

    Idea构建maven项目: 步骤一: 步骤二: 自动导入Maven项目: 步骤三:增加web 二:搭建spring项目结构: 结构图: 网上都是一大堆的:自己也可以去搜:ssm  pom.xml  ...

  8. windows server 2008 r2 负载平衡 找不到主机 解决方案

    在C:\Windows\System32\drivers\etc文件夹中的host文件里手工将主机名WIN-********解析至IP 即可.

  9. npm与nrm

    npm npm是Node.js 平台的默认包(模块依赖)管理工具 Node Package Manager nrm 一个npm的源管理器(管理工具) 允许快速的在 npm 源间切换 两者关系 npm是 ...

  10. Struts2的JSON插件

    扎心了,老铁~这依然是一个注册. 1.reg.jsp <%@page contentType="text/html; charset=utf-8"%> <!DOC ...