转载至:https://www.cnblogs.com/dolphin0520/p/3923737.html

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1.synchronized方法

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

  1. public class Test {
  2.  
  3. public static void main(String[] args) {
  4. final InsertData insertData = new InsertData();
  5.  
  6. new Thread() {
  7. public void run() {
  8. insertData.insert(Thread.currentThread());
  9. };
  10. }.start();
  11.  
  12. new Thread() {
  13. public void run() {
  14. insertData.insert(Thread.currentThread());
  15. };
  16. }.start();
  17. }
  18. }
  19.  
  20. class InsertData {
  21. private ArrayList<Integer> arrayList = new ArrayList<Integer>();
  22.  
  23. public void insert(Thread thread){
  24. for(int i=0;i<5;i++){
  25. System.out.println(thread.getName()+"在插入数据"+i);
  26. arrayList.add(i);
  27. }
  28. }
  29. }

此时程序的输出结果为:

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

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

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

从上输出结果说明,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代码块

  1. synchronized(synObject) { }

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

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

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

  1. class InsertData {
  2. private ArrayList<Integer> arrayList = new ArrayList<Integer>();
  3.  
  4. public void insert(Thread thread){
  5. synchronized (this) {
  6. for(int i=0;i<100;i++){
  7. System.out.println(thread.getName()+"在插入数据"+i);
  8. arrayList.add(i);
  9. }
  10. }
  11. }
  12. }
  1. class InsertData {
  2. private ArrayList<Integer> arrayList = new ArrayList<Integer>();
  3. private Object object = new Object();
  4.  
  5. public void insert(Thread thread){
  6. synchronized (object) {
  7. for(int i=0;i<100;i++){
  8. System.out.println(thread.getName()+"在插入数据"+i);
  9. arrayList.add(i);
  10. }
  11. }
  12. }
  13. }

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

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

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

  1. public class Test {
  2.  
  3. public static void main(String[] args) {
  4. final InsertData insertData = new InsertData();
  5. new Thread(){
  6. @Override
  7. public void run() {
  8. insertData.insert();
  9. }
  10. }.start();
  11. new Thread(){
  12. @Override
  13. public void run() {
  14. insertData.insert1();
  15. }
  16. }.start();
  17. }
  18. }
  19.  
  20. class InsertData {
  21. public synchronized void insert(){
  22. System.out.println("执行insert");
  23. try {
  24. Thread.sleep(5000);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println("执行insert完毕");
  29. }
  30.  
  31. public synchronized static void insert1() {
  32. System.out.println("执行insert1");
  33. System.out.println("执行insert1完毕");
  34. }
  35. }

执行结果;

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

同步(synchronized)的更多相关文章

  1. Java多线程同步 synchronized 关键字的使用

    代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...

  2. 同步synchronized用法

    今天在高人的指导下,对同步synchronized用法有了更高一层的理解,非常感谢他的无私奉献.在此把代码贴出来方便日后查阅. publicclass SfServlet { privatestati ...

  3. java同步synchronized

    java同步synchronized volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性. 看下面的这段代码: /** * * @author InJavaWeTrust * */ ...

  4. 2.2.9静态同步synchronized方法与synchronized(class)代码块

    关键字synchronized还可以应用在static静态方法上,这样写那是对当前的*.java文件对应的class类进行持锁, 测试如下 package com.cky.bean; /** * Cr ...

  5. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. 线程同步synchronized和ReentrantLock

    一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...

  7. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  8. Java自学-多线程 同步synchronized

    Java 多线程同步 synchronized 多线程的同步问题指的是多个线程同时修改一个数据的时候,可能导致的问题 多线程的问题,又叫Concurrency 问题 步骤 1 : 演示同步问题 假设盖 ...

  9. java 多线程:Thread 并发线程: 方法同步synchronized关键字,与static的结合

    1.方法内的变量是安全的 方法内定义的变量,每个变量对应单独的内存变量地址,多个线程之间相互不影响.多个线程之间的变量根本没有一毛钱关系 public class ThreadFuncVarSafe ...

  10. Java同步synchronized与死锁

    多个线程要操作同一资源时就有可能出现资源的同步问题. 同步就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行. 解决资源共享的同步操作,可以使用同步代码块和同 ...

随机推荐

  1. 日志采集工具Flume的安装与使用方法

    安装Flume,参考厦门大学林子雨教程:http://dblab.xmu.edu.cn/blog/1102/ 并完成案例1 1.案例1:Avro source Avro可以发送一个给定的文件给Flum ...

  2. 算法基础~链表~排序链表的合并(k条)

    算法基础~链表~排序链表的合并(k条) 1,题意:已知k个已排序链表头结点指针,将这k个链表合并,合并后仍然为有序的,返回合并后的头结点. 2,方法之间时间复杂度的比较: 方法1(借助工具vector ...

  3. browse下载插件DownThemAll!

    DownThemAll!是一个不错的下载插件,它安装在各类browse上.

  4. 什么是TCP?什么是TCP协议?

    一.什么是TCP >>>TCP是一种传输控制协议,是面向连接的.可靠的.基于字节流之间的传输层通信协议 >>>在因特网协议族里面,TCP层是在IP层上面,应用层下面 ...

  5. P2490 [SDOI2011]黑白棋

    P2490 [SDOI2011]黑白棋 题意 一个 \(1*n\) 的棋盘上,A 可以移动白色棋子,B 可以移动黑色的棋子,其中白色不能往左,黑色不能往右.他们每次操作可以移动 1 到 \(d\) 个 ...

  6. 最大流最小割——bzoj1001狼抓兔子,洛谷P2598

    前置知识 平面图 平面图就是平面上任意边都不相交的图.(自己瞎画的不算XD) 对偶图 比如说这个图,我们发现平面图肯定会把平面分成不同的区域(感觉像拓扑图),并把这些区域当做每个点(不被包围的区域独自 ...

  7. shell脚本(11)-流程控制case

    一.case介绍 生产环境下,遇到要根据不同的状况执行不同的预案的情况,首先根据可能出现的情况写出对应预案,根据出现的情况来加载不同的预案 特点:根据给予的不同的代码块 二.case语法 case 变 ...

  8. odoo源生打印【web report】

    https://www.odoo.com/documentation/12.0/reference/reports.html     具体的看官方文档 一.纸张格式设置: <record id= ...

  9. 第十四篇 -- QMainWindow与QAction(清空-全选-撤销-重做-关闭-语言选择)

    效果图: 这次添加了关闭-撤销-重做-全选-清空等功能,并添加了字体和字体大小选择.基本方法跟前面几篇类似. ui_mainWindow.py # -*- coding: utf-8 -*- # Fo ...

  10. Python脚本:删除文件夹下的重复图片,实现图片去重

    近期在整理相册的时候,发现相册中有许多重复图片,人工一张张筛查删除太枯燥,便写下这个脚本,用于删除文件夹下重复的图片. 第一部分:判断两张图片是否相同 要查找重复的图片,必然绕不开判断两张图片是否相同 ...