一、介绍

当多个线程涉及到共享数据的时候,就会设计到线程安全的问题。非线程安全其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”。发生脏读,就是取到的数据已经被其他的线程改过了。什么是线程安全呢?用并发编程实战里面的一段话解释说:

  当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额
外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的

这里需要注意的是多个线程,如果一个线程肯定是线程安全的,而且这里的共享数据是指成员变量,不是局部变量,局部变量是

方法私有的,而方法运行时,对应的虚拟机方法栈是线程私有的,所以局部变量一定是方法安全的。

为了保证线程的安全,就要用到同步了。同步可以这么理解,只有等一个线程执行完这么一段需要同步的代码,其他的线程才能执行。而异步就是这段代码代码可以交替执行。

二、synchronized同步方法

synchronized同步方法的用法是:

synchronized  修饰符  返回值 方法名(){
}

1.synchronized取得的锁都是对象锁,而不是把一段代码或函数当做锁

synchronized方法相当于给这个方法上了一把锁,锁就是拥有这个方法的实例对象,当多个线程访问一个类的同一个实例对象时,这个锁也就是这个实例对象,先获得这把锁的线程就可以执行同步方法里面的内容,其他线程只有等第一个线程执行结束自动释放锁或者程序抛出异常或者使用wait()等方法释放锁的情况下才能获得锁。

当多个线程访问一个类的多个实例对象时,jvm就创建了多把锁,多个线程获取到的锁不一样。这时候同步方法还是异步执行的。

2.synchronized方法锁重入

锁重入的意思是,一个线程已经拥有了这个对象的锁,再次请求该对象锁时,还是会保证成功,也就是说,在synchronized方法里面,再调用本类中的其他的synchronized方法,是永远可以得到锁的。否则,会造成死锁。

3.出现异常锁会自动释放

4.同步不具有继承性

也就是说父类中方法是同步的,子类继承父类的方法,这个方法就不是同步的了,需要再加上synchroized变成同步方法

5.如果多个线程持有一把锁,也就是只有一个实例对象,那么该对象里面的所有synchroized方法都具有同步性,也就是,当一个线程调用其中一个sycnhroized方法时,其他线程调用这个对象里面的其他synchroized也会处于阻塞状态。

使用synchroized方法有什么弊端呢?从运行时间来看,当一个线程取得锁以后,其他线程只有等待它释放锁以后才能执行方法里面的代码,从运行时间来看,这样会浪费很长的时间,怎么改变呢?就要用到同步语句块。

三、同步代码块

同步代码块如何解决上面问题呢?那就是只将需要同步的方法用

synchroized(this|任意对象|class){
}

括起来。括号里面的内容是一个监视器

只有代码块里面的代码是同步的,其余的代码还是异步的。

一、

1.当括号里面用this时,锁定的也是当前对象。

这时候其实和使用synchroized方法一样。

2.当括号里面是任意对象时。

当多个线程持有的对象监视器为同一个的前提下,如上。

但是当多个线程持有对象监视器为多个时,由于对象监视器不同,所以运行结果就是异步的。同步代码块放在非同步synchronized方法中进行生命,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无需的,虽然在同步块中执行的顺序是同步的,这样极其容易出现脏读。

所以最好保证对象监视器是同一个对象。如果使用同步代码块锁非this对象,则同步代码块中的程序与同步方法时异步的,不予其他锁this同步方法争抢this锁,大大提高运行效率。

二、synchroized(任意对象)的三个结论

1.多个线程同时执行时呈同步效果

2.当其他线程执行任意对象中synchronized同步方法时呈同步效果

3.当其他线程执行任意对象里面的synchronized(this)代码块时呈同步效果

同步方法比同步代码块更高效,但是它们的功能是一样的(有人研究,从虚拟机测试)。

四、同步代码块和同步方法区别

synchronized用于解决同步问题,当有多条线程同时访问共享数据时,如果不进行同步,就会发生错误,java提供的解决方案是:只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行可以。解决这个问题。这里在用synchronized时会有两种方式,一种是上面的同步方法,即用synchronized来修饰方法,另一种是提供的同步代码块。

import javax.print.DocFlavor;

public class SynchronsizeObj{

    StringBuilder stringBuilder = new StringBuilder();
public synchronized void synMethod ()
{
try {
Thread.sleep(100);
for(int i = 0;i < 10; i++)
{
stringBuilder.append("synmethod:" + String.valueOf(i));
}
stringBuilder.append("\n");
System.out.println(stringBuilder.toString()); }
catch (InterruptedException ex)
{
ex.printStackTrace();
} } public void synThis()
{
synchronized (this)
{
for(int i = 0;i < 10; i++)
{
stringBuilder.append("synthis:" + String.valueOf(i));
}
stringBuilder.append("\n");
System.out.println(stringBuilder.toString());
} } public void synParam()
{
synchronized (stringBuilder)
{
try {
Thread.sleep(0);
for(int i = 0;i < 10; i++)
{
stringBuilder.append("synparam:" + String.valueOf(i));
}
stringBuilder.append("\n");
System.out.println(stringBuilder.toString());
}
catch (InterruptedException ex)
{
ex.printStackTrace();
} } }
}

上面的代码全部是加了同步锁,则此时输出结果为:

上面是同步锁类

测试代码为:

/**
* @author :dongbl
* @version :
* @Description:
* @date :19:48 2017/11/14
*/
public class TestSyn { public static void main(String [] args){ final SynchronsizeObj obj = new SynchronsizeObj(); new Thread(new Runnable() {
public void run() {
obj.synMethod();
}
}).start(); new Thread(new Runnable() {
public void run() {
obj.synThis();
}
}).start(); new Thread(new Runnable() {
public void run() {
obj.synParam();
}
}).start(); }
}

运行结果为:

synparam:0synparam:1synparam:2synparam:3synparam:4synparam:5synparam:6synparam:7synparam:8synparam:9
synmethod:0synmethod:1synmethod:2synmethod:3synmethod:4synmethod:5synmethod:6synmethod:7synmethod:8synmethod:9
synthis:0synthis:1synthis:2synthis:3synthis:4synthis:5synthis:6synthis:7synthis:8synthis:9

在启动线程1调用方法synMethod后,接着会让线程1休眠100豪秒钟,这时会调用方法synparam,注意到方法C这里用synchronized进行加锁,这里锁的对象是str这个字符串对象。但是方法B则不同,是用当前对象this进行加锁。显然,这两个方法用的是一把锁

这就是同步对象和同步变量的区别。

同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好*。

2.同步方法

synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放。

此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

3.同步方法this

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

对synchronized(this)的一些理解
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。 
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

四、参考文献

1、java中的synchronized(同步代码块和同步方法的区别)

2、Java 多线程详解(四)------生产者和消费者

文中对生产者和消费者模式进行详细的介绍,关键是对于wait()、notify()、notifyALL(),进行详细的介绍。

java中多线程详解-synchronized的更多相关文章

  1. 【转】 java中HashMap详解

    原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...

  2. java中HashMap详解(转)

    java中HashMap详解 博客分类: JavaSE Java算法JDK编程生活       HashMap 和 HashSet 是 Java Collection Framework 的两个重要成 ...

  3. java集合(2)- java中HashMap详解

    java中HashMap详解 基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 H ...

  4. Java 中HashMap 详解

    本篇重点: 1.HashMap的存储结构 2.HashMap的put和get操作过程 3.HashMap的扩容 4.关于transient关键字 HashMap的存储结构 1. HashMap 总体是 ...

  5. Java中List详解

    List是Java中比较常用的集合类,关于List接口有很多实现类,本文就来简单介绍下其中几个重点的实现ArrayList.LinkedList和Vector之间的关系和区别. List List 是 ...

  6. 【Java基础】JAVA中优先队列详解

    总体介绍 优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素).这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序( ...

  7. Java中PriorityQueue详解

    Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示.本文从Queue接口函数出发,结合生动的图解,深入浅出地分析PriorityQueue每个操作的具体过程和时间复杂度, ...

  8. Java中CAS详解

    在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. (2 ...

  9. Java中Volatile详解

    当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写.这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的 ...

随机推荐

  1. Android 音乐(音效)播放方式总结

    一.音效的分类 音效按照作用的不同,可以将音效分为即时音效和背景音乐.两种音效在Android中的实现技术是不同的. 主要的实现方式为:SoundPool.MediaPlayer. 区别在于,Medi ...

  2. 软件包管理之rpm与yum

    软件包的安装和卸载时很平常的事,但在Linux上面却不简单..Linux的其中一个哲学就是一个程序只做一件事,并且做好.组合小程序来完成复杂的任务,这样做有很多好处,但是各个小程序之间往往会存在着复杂 ...

  3. 从零搭建java后台管理系统(二)mysql和redis安装

    接上篇开始安装mysql和redis 注意了,如果用阿里云服务器,外网访问的端口必须在安全组开启,否则外网访问不通 三.服务器安装redis和mysql 本次环境搭建将所有第三方服务会安装在阿里云服务 ...

  4. Spring Boot自动配置原理、实战

    Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...

  5. Win10手记-为应用集成SQLite(一)

    SQLite是什么?熟悉移动端开发的朋友都会经常接触,无论是iOS的CoreData还是安卓的内置数据库,他们都是采用了SQLite这个轻量高效数据库,微信也是如此.可以说SQLite是目前移动端最为 ...

  6. AlarmManager的使用和七牛云android SDK上传图片

    一学期的课程设计又开始了,虽然以后不搞安卓,但是课设还是想好好完成的,因为之前做过地图开发,所以选了一个跟 这个相关的题目,其实有一个校车系统也可以选,但是之前做过一个相似度接近80%的东西,不想混混 ...

  7. oracle查看当前用户,数据库实例

    #sysdba用户登录[oracle@oracle ~]$ sqlplus / as sysdba #查看当前用户sql>show user; #查看当前数据库实例sql>show par ...

  8. 基于Docker+Prometheus+Grafana监控SpringBoot健康信息

    在微服务体系当中,监控是必不可少的.当系统环境超过指定的阀值以后,需要提醒指定的运维人员或开发人员进行有效的防范,从而降低系统宕机的风险.在CNCF云计算平台中,Prometheus+Grafana是 ...

  9. linux pidstat 命令详解

    pidstat 概述 pidstat是sysstat工具的一个命令,用于监控全部或指定进程的cpu.内存.线程.设备IO等系统资源的占用情况.pidstat首次运行时显示自系统启动开始的各项统计信息, ...

  10. Android快速实现二维码扫描--Zbar

    Android中二维码扫描的最常用库是zxing和zbar,上一篇<Android快速实现二维码扫描–Zxing>介绍了Zxing.这次说Zbar,Zbar速度极快,我就比较常用,项目地址 ...