当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题,Java中提供了**同步机制(synchronized)**来解决。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。那么怎么去使用呢?有三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制

同步代码块

  • 同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){需要同步操作的代码
}
  • 同步锁:
    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
    锁对象 可以是任意类型。
    多个线程对象,要使用同一把锁。
    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

同步方法

  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:

public synchronized void method(){可能会产生线程安全问题的代码
}

同步方法的锁对象:
(1)静态方法:当前类的Class对象
(2)非静态方法:this

Lock锁(JUC中的锁机制)

  • java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
  • Lock锁也称同步锁,加锁与释放锁方法,如下:
    • public void lock() :加同步锁。
    • public void unlock() :释放同步锁。

接着我们通过八个问题以及八个实例来理解一下多线程锁synchronized的作用范围。

锁的8个问题

首先模拟手机发短信和发邮件,创建Phone类,类中声明两个同步方法,其中一个同步方法发送短信,另一个同步方法发送邮件。

class Phone{  //发送短信的同步方法public synchronized void sendSMS() throws Exception{     System.out.println("------sendSMS");  }//发送邮件的同步方法public synchronized void sendEmail() throws Exception{             System.out.println("------sendEmail");    }
}
public class Lock_8 {public static void main(String[] args) throws Exception{Phone phone = new Phone();Phone phone1 = new Phone();//发送短信的线程new Thread(()->{try {phone.sendSMS();}catch(Exception e){e.printStackTrace();}},"AA").start();        //让主线程sleep0.2秒已区分上下两个线程的执行顺序        Thread.sleep(200);        //发送email的线程        new Thread(()->{try {phone.sendEmail();//phone.getHello();//phone1.sendEmail();}catch(Exception e){e.printStackTrace();}},"BB").start();}
}

① 标准访问,先打印短信还是邮件

测试结果:先输出sendSMS 后输出sendEmail

分析原因:

由于Phone类中无论发短信还是发邮件两个方法均为synchronized非静态同步方法,则此时的锁对象就是this即主方法中new的Phone对象,因此在主方法中无论有多少个线程由于竞争的都为同一把锁每次都只能由先抢到锁并获取到cpu资源的线程进入Phone类中执行相应非静态同步方法,而其他线程无论多久都得先在Phone类外等待。本例中通过Thread.sleep(200); 让主线程休眠0.2秒使“AA”线程先于“BB”线程获取到cpu资源,因此“AA”线程先获取锁并执行相应方法,执行完毕后将锁释放,此时“BB”线程才能进入Phone类中执行相应非静态方法。

② 停4秒在短信方法内,先打印短信还是邮件

修改Phone类:

测试:隔4秒后先输出了sendSMS 后输出sendEmail

分析原因:

在发短信同步方法中使其sleep四秒钟,由于sleep并不会释放锁以及cpu资源,因此并不影响获取锁以及线程执行的先后顺序,过程和①一致。

③ 普通的hello方法,是先打短信还是hello

修改Phone类:

修改main:

测试结果:先输出Hello world!隔4秒后输出sendSMS

分析原因:

synchronized同步机制只对标有它的同步方法或者同步代码块有效,对于普通的getHello方法并没有同步锁的限制,因此“AA”线程获取同步锁并得到CPU资源进入Phone资源类中的同步方法休眠4秒,而与此同时主线程sleep0.2秒后“BB”线程执行没有synchronized同步机制限制的getHello方法,0.2秒后先输出hello world!“AA”线程休眠4秒后输出sendSMS并释放资源。

④ 现在有两部手机,先打印短信还是邮件

修改main:

测试结果:先输出sendEmail 隔4秒后输出sendSMS

原因分析:

首先明确,资源类Phone中发送短信和发送邮件均为非静态同步方法,锁对象为this即当前对象,而主方法中new了两部手机phone和phone1,因此此时对于线程“AA”与线程“BB”来说,它们竞争的已经不是同一把锁,所以对于锁的争抢互不影响,“AA”获取phone当前对象锁,而“BB”则获取phone1对象锁,因此“AA”在优于”BB“先获取到CPU资源后携带着phone锁进入到资源类中执行相应方法且休眠4秒,而”BB“线程在0.2秒后也获取到CPU资源,携带着phone1锁进入到资源类执行sendEmail方法,因此执行顺序为先输出sendEmail 隔4秒后输出sendSMS。

⑤ 两个静态同步方法,1部手机,先打印短信还是邮件

修改Phone类:

修改main:

测试结果:隔4秒后先输出了sendSMS 后输出sendEmail

原因分析:

首先明确资源类中两个同步方法由非静态变为了静态同步方法,因此此时的锁对象已经由原来的this当前实例对象变为了当前类的Class对象。因此此时的锁对象与new出的phone无关,而是造出phone的工厂Class Phone这个Class的类对象。所以整体流程和①的流程相似,因为让主线程休眠0.2秒的原因,线程“AA”会先获取到CPU资源并携带着锁进入资源类Phone执行相应静态同步方法4秒后输出sendSMS,随后释放锁和CPU资源,“BB”线程再获取锁以及CPU资源结束在资源类Phone外的等待,进入执行sendEmail方法。

⑥ 两个静态同步方法,2部手机,先打印短信还是邮件

修改main:

测试结果:隔4秒后先输出了sendSMS 后输出sendEmail

分析原因:

与⑤原因相同,由于静态同步方法,锁对象已经由当前实例对象this变为生产实例对象的工厂Class Phone,即当前Phone类的Class对象,线程“AA”和线程“BB”都要竞争同一把Class对象锁,而与new出的实例无关,因此不管有几部手机,不管new出几个phone都不影响输出结果,因为phone不是锁对象。

⑦ 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件

修改Phone类:

修改main:

测试结果:先输出sendEmail 隔4秒后输出sendSMS

原因分析:

首先明确资源类中sendSMS依旧为静态同步方法,锁对象为当前类的Class对象。而sendEmail方法由静态同步方法变为了非静态同步方法,因此锁对象为当前实例对象this。“AA”线程需要的锁对象为Class对象,而“BB”线程需要的锁对象为当前实例对象phone,因此两个线程之间不存在锁的竞争,只存在CPU资源获取的先后顺序,而由于主线程休眠的0.2秒,"AA线程"先获取到CPU资源,携带着Class对象锁进入到资源类中执行senSMS静态同步方法并休眠4秒,而“BB”线程在0.2秒后获取到CPU资源,携带着当前phone对象锁进入到资源类中执行sendEmail方法,因此先输出sendEmail 隔4秒后输出sendSMS。

⑧ 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

修改main:

测试结果:先输出sendEmail 隔4秒后输出sendSMS

原因分析:

首先明确,静态同步方法与非静态同步方法与⑦相同,没有改变,因此线程“AA”与线程“BB“竞争的锁资源并非同一个,所以有多个实例phone、phone1由于锁的不同并不会影响两个线程进入资源类执行相应同步方法的先后顺序。因此流程依旧和⑦相同。

总结:

1.synchronized实现同步的基础:Java中的每一个对象都可以作为锁,具体表现为以下3种形式:

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象

2.一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法。

3.当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁。可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

4.所有的非静态同步方法用的是同一把锁——当前实例对象(this),所有的静态同步方法用的也是同一把锁——类对象本身,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。

5.普通方法与同步锁无关,不影响线程的执行。
综上所述,在上述8个问题中,想要判断程序输出的先后顺序,一定要先搞清楚当前线程所竞争的锁资源是什么,以及线程所竞争的锁资源是不是同一个,搞清楚这一点后,问题就迎刃而解了,而对于锁的理解,就可以将其理解为非静态同步方法中this当前实例对象或者是静态同步方法中的当前类的Class对象。

此时再去理解同步代码块中同步锁的概念就简单的多了:

synchronized(同步锁){需要同步操作的代码
}
  • 同步锁:
    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

    锁对象 可以是任意类型。(this或者是Phone.Class

​ 多个线程对象,要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

线程同步机制synchronized中锁的判断以及锁的作用范围相关推荐

  1. Java线程同步机制synchronized关键字的理解

    由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 需要明确的几个问题: ...

  2. 学习java的第四十天,线程的优先级、守护线程、线程同步机制、死锁

    一.线程的优先级(priority) Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行. 线程的优先级用数字表示,范围1~10 Thr ...

  3. Python线程同步机制: Locks, RLocks, Semaphores, Condition

    为什么80%的码农都做不了架构师?>>>    翻译自Laurent Luce的博客 原文名称:Python threads synchronization: Locks, RLoc ...

  4. Java多线程之线程同步机制(锁,线程池等等)

    Java多线程之线程同步机制 一.概念 1.并发 2.起因 3.缺点 二.三大不安全案例 1.样例一(模拟买票场景) 2.样例二(模拟取钱场景) 3.样例三(模拟集合) 三.同步方法及同步块 1.同步 ...

  5. c++ linux 线程等待与唤醒_C++ Linux线程同步机制:POSIX信号量,互斥锁,条件变量...

    线程同步机制:POSIX 信号量,互斥量,条件变量 POSIX 信号量 常用的POSIX 信号量函数为如下5个: sem_init sem_destroy sem_wait sem_trywait s ...

  6. Linux中的线程同步机制-futex

    Linux中的线程同步机制(一) -- Futex 引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这 ...

  7. 解析JVM线程同步机制

    http://blog.csdn.net/thl789/article/details/566494 对多线程的支持一般是在OS级的,而Java将其做在了语言级别,这其中最吸引人的莫过于Java对线程 ...

  8. Java多线程的同步机制(synchronized)

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...

  9. Java高级-线程同步机制实现

    2019独角兽企业重金招聘Python工程师标准>>> 前言 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Threa ...

最新文章

  1. CVPR禁令出台:审稿期间禁止主动在社交媒体宣传论文!LeCun:阻碍科学交流,简直疯了...
  2. python真的那么火吗-Python语言为什么这么火?
  3. 树莓派 安装中文字体、中文输入法fcitx和googlepinyin输入法
  4. Python 中,字符串连接效率最高的方式是?一定出乎你的意料
  5. android酷狗界面,酷狗音乐Android 2.2版系统优化版界面曝光
  6. Linuxs查看进程,杀死进程
  7. 网络游戏同步问题综述
  8. 微信花呗真的来了吗?
  9. java Object类的公共方法
  10. 用tar给linux .rar解压,Linux 下解压 rar 文件的方法
  11. Apache Tomcat JServ漏洞验证测试(CNVD-2020-10487 CVE-2020-1938)
  12. Python eval()和exec()函数详解
  13. python实现键盘自动输入_人生苦短我用Python——模拟鼠标点击和键盘输入的操作...
  14. git reflog 恢复已删除分支
  15. Gvim开发环境配置笔记--Windows篇(转)
  16. 如何制作自己的网页html,如何制作自己的网页
  17. 计算机老师的英语怎么写,英语感谢信感谢我的计算机老师
  18. ap导入 ebs oracle_Oracle EBS Interface/API(1)--AP Invoice费用报表类型导入
  19. MFC在对话框中绘制图像
  20. MySQL事务与锁详解,并发读异常与隔离策略

热门文章

  1. knn算法实例python_kNN算法及其Python实例
  2. 智能吸顶灯Homekit控制
  3. android引导页自动轮播,Android使用ViewPager实现自动轮播
  4. 做财务,别让太多的机械工作埋没了你1%的涨薪机会
  5. css3悬停按钮发光遮罩动画js特效
  6. 数字IC必学之《Skill入门教程》
  7. Photoshop 渐变介绍
  8. 数字驾驶舱智慧金融|设计一等奖作品:古风金融产品体验运营大屏
  9. PHP 获取 IP 地址所在的地理位置信息/城市
  10. mybatis一对多查询的两种方式