一、一个简单的Demo引发的血案

关于线程同步问题我们从一个简单的Demo现象说起。Demo特别简单就是开启两个线程打印字符串信息。

OutPutStr类源码:

1 public class OutPutStr {

2

3 public void out(String str) {

4 for (int i = 0; i < str.length(); i++) {

5 System.out.print(str.charAt(i));

6 }

7 System.out.println();

8 }

9 }

很简单吧,就是一个方法供外界调用,调用的时候传进来一个字符串,方法逐个取出字符串的字符并打印到控制台。

接下来,我们看main方法中逻辑:

1 public static void main(String[] args) {

2 //

3 final OutPutStr o = new OutPutStr();

4 new Thread(new Runnable() {

5

6 @Override

7 public void run() {

8 //

9 while(true){

10 o.out("111111111111");

11 }

12 }

13 }).start();

14 new Thread(new Runnable() {

15

16 @Override

17 public void run() {

18 //

19 while(true){

20 o.out("222222222222");

21 }

22 }

23 }).start();

24 }

也很简单,就是开启两个线程分别调用OutPutStr中out方法不停打印字符串信息,运行程序打印信息如下:

1 222222222222

2 222222222222

3 22222222222111111111

4 2

5 111111111111

6 111111111111

7 1111222222222211111111

8 111111111111

咦?和我们想的不一样啊,怎么还会打印出22222222222111111111这样子的信息,这是怎么回事呢?

二、原因解析

我们知道线程的执行是CPU随机调度的,比如我们开启10个线程,这10个线程并不是同时执行的,而是CPU快速的在这10个线程之间切换执行,由于切换速度极快使我们感觉同时执行罢了。发生上面问题的本质就是CPU对线程执行的随机调度,比如A线程此时正在打印信息还没打印完毕此时CPU切换到B线程执行了,B线程执行完了又切换回A线程执行就会导致上面现象发生。

线程同步问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是CPU对线程的随机调度,CPU无法保证一个线程执行完其逻辑才去调用另一个线程执行。

三、同步方法解决上述问题

既然知道了问题发生的原因,记下来我们就要想办法解决问题啊,解决的思路就是保证一个线程在调用out方法的时候如果没执行完那么另一个不能执行此方法,换句话说就是只能等待别的线程执行完毕才能执行。

针对线程同步问题java早就有解决方法了,最简单的就是给方法加上synchronized关键字,如下:

1 public synchronized void out(String str) {

2 for (int i = 0; i < str.length(); i++) {

3 System.out.print(str.charAt(i));

4 }

5 System.out.println();

6 }

这是什么意思呢?加上synchronized关键字后,比如A线程执行out方法就相当于拿到了一把锁,只有获取这个锁才能执行此方法,如果在A线程执行out方法过程中B线程也想插一脚进来执行out方法,对不起此时这是不能够的,因为此时锁在A线程手里,B线程无权拿到这把锁,只有等到A线程执行完后放弃锁,B线程才能拿到锁执行out方法。

为out方法加上synchronized后其就变成了同步方法,普通同步方法的锁是this,也就是当前对象,比如demo中,外部要想调用out方法就必须创建OutPutStr类实例对象o,此时out同步方法的锁就是这个o。

四、同步代码块解决上述问题

我们也可以利用同步代码块解决上述问题,修改out方法如下:

1 public void out(String str) {

2 synchronized (this) {

3 for (int i = 0; i < str.length(); i++) {

4 System.out.print(str.charAt(i));

5 }

6 System.out.println();

7 }

8 }

同步代码块写法:synchronized(obj){},其中obj为锁对象,此处我们传入this,同样方法的锁也为当前对象,如果此处我们传入str,那么这里的锁就是str对象了。

为了说明不同锁带来的影响我们修改OutPutStr代码如下:

1 public class OutPutStr {

2

3 public synchronized void out(String str) {

4 for (int i = 0; i < str.length(); i++) {

5 System.out.print(str.charAt(i));

6 }

7 System.out.println();

8 }

9

10 public void out1(String str) {

11

12 synchronized (str) {

13 for (int i = 0; i < str.length(); i++) {

14 System.out.print(str.charAt(i));

15 }

16 System.out.println();

17 }

18 }

19 }

很简单我们就是加入了一个out1方法,out方法用同步函数保证同步,out1用同步代码块保证代码块,但是锁我们用的是str。

main代码:

1 public static void main(String[] args) {

2 //

3 final OutPutStr o = new OutPutStr();

4 new Thread(new Runnable() {

5

6 @Override

7 public void run() {

8 //

9 while(true){

10 o.out("111111111111");

11 }

12 }

13 }).start();

14 new Thread(new Runnable() {

15

16 @Override

17 public void run() {

18 //

19 while(true){

20 o.out1("222222222222");

21 }

22 }

23 }).start();

24 }

也没什么,就是其中一个线程调用out方法,另一个调用out1方法,运行程序:

111111111111222

222222222222

111111111111222222222222

222222222222

看到了吧,打印信息又出问题了,就是因为out与out1方法的锁不一样导致的,线程A调用out方法拿到this这把锁,线程B调用out1拿到str这把锁,二者互不影响,解决办法也很简单,修改out1方法如下即可:

1 public void out1(String str) {

2

3 synchronized (this) {

4 for (int i = 0; i < str.length(); i++) {

5 System.out.print(str.charAt(i));

6 }

7 System.out.println();

8 }

9 }

五、静态函数的同步问题

我们继续修改OutPutStr类,加入out2方法:

1 public class OutPutStr {

2

3 public synchronized void out(String str) {

4 for (int i = 0; i < str.length(); i++) {

5 System.out.print(str.charAt(i));

6 }

7 System.out.println();

8 }

9

10 public void out1(String str) {

11

12 synchronized (this) {

13 for (int i = 0; i < str.length(); i++) {

14 System.out.print(str.charAt(i));

15 }

16 System.out.println();

17 }

18 }

19

20 public synchronized static void out2(String str) {

21

22 for (int i = 0; i < str.length(); i++) {

23 System.out.print(str.charAt(i));

24 }

25 System.out.println();

26 }

27 }

main中两个子线程分别调用out1,ou2打印信息,运行程序打印信息如下;

1 222222222222

2 222222222222

3 222222222111111111111

4 111111111111

咦?又出错了,out2与out方法唯一不同就是out2就是静态方法啊,不是说同步方法锁是this吗,是啊,没错,但是静态方法没有对应类的实例对象依然可以调用,那其锁是谁呢?显然静态方法锁不是this,这里就直说了,是类的字节码对象,类的字节码对象是优先于类实例对象存在的。

将ou1方法改为如下:

1 public void out1(String str) {

2

3 synchronized (OutPutStr.class) {

4 for (int i = 0; i < str.length(); i++) {

5 System.out.print(str.charAt(i));

6 }

7 System.out.println();

8 }

9 }

再次运行程序,就会发现信息能正常打印了。

六、synchronized同步方式总结

到此我们就该小小的总结一下了,普通同步函数的锁是this,当前类实例对象,同步代码块锁可以自己定义,静态同步函数的锁是类的字节码文件。总结完毕,就是这么简单。说了一大堆理解这一句就够了。

七、JDK1.5中Lock锁机制解决线程同步

大家是不是觉得上面说的锁这个玩意咋这么抽象,看不见,摸不着的。从JDK1.5起我们就可以根据需要显性的获取锁以及释放锁了,这样也更加符合面向对象原则。

Lock接口的实现子类之一ReentrantLock,翻译过来就是重入锁,就是支持重新进入的锁,该锁能够支持一个线程对资源的重复加锁,也就是说在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞,同时还支持获取锁的公平性和非公平性,所谓公平性就是多个线程发起lock()请求,先发起的线程优先获取执行权,非公平性就是获取锁与是否优先发起lock()操作无关。默认情况下是不公平的锁,为什么要这样设计呢?现实生活中我们都希望公平的啊?我们想一下,现实生活中要保证公平就必须额外开销,比如地铁站保证有序公平进站就必须配备额外人员维持秩序,程序中也是一样保证公平就必须需要额外开销,这样性能就下降了,所以公平与性能是有一定矛盾的,除非公平策略对你的程序很重要,比如必须按照顺序执行线程,否则还是使用不公平锁为好。

接下来我们修改OutPutStr类,添加out3方法:

1 //true表示公平锁,false非公平锁

2 private Lock lock = new ReentrantLock();

3

4 public void out3(String str) {

5

6 lock.lock();//如果有其它线程已经获取锁,那么当前线程在此等待直到其它线程释放锁。

7 try {

8 for (int i = 0; i < str.length(); i++) {

9 System.out.print(str.charAt(i));

10 }

11 System.out.println();

12 } finally {

13 lock.unlock();//释放锁资源,之所以加入try{}finally{}代码块,

14 //是为了保证锁资源的释放,如果代码发生异常也可以保证锁资源的释放,

15 //否则其它线程无法拿到锁资源执行业务逻辑,永远处于等待状态。

16 }

17 }

关键注释都在代码中有所体现了,使用起来也很简单。

八、Lock与synchronized同步方式优缺点

Lock 的锁定是通过代码实现的,而 synchronized 是在 JVM 层面上实现的(所有对象都自动含有单一的锁。JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在线程第一次给对象加锁的时候,计数变为1。每当这个相同的线程在此对象上获得锁时,计数会递增。只有首先获得锁的线程才能继续获取该对象上的多个锁。每当线程离开一个synchronized方法,计数递减,当计数为0的时候,锁被完全释放,此时别的线程就可以使用此资源)。

synchronized 在锁定时如果方法块抛出异常,JVM 会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。但是 Lock 的话就享受不到 JVM 带来自动的功能,出现异常时必须在 finally 将锁释放掉,否则将会引起死锁。

在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好。在资源竞争激烈情况下,Lock同步机制性能会更好一些。

关于线程同步问题到这里就结束了,java多线程文章只是本人工作以来的一次梳理,都比较基础,但是却很重要的,最近招人面试的最大体会就是都喜欢那些所谓时髦的技术一问基础说的乱七八糟,浪费彼此的时间。好啦,吐槽了几句,本文到此为止,很基础的玩意,希望对你有用。

版权声明:本文出自汪磊的博客,转载请务必注明出处。

Java同步问题_Java多线程同步问题相关推荐

  1. java同步通信方式_java多线程同步与通信示例(synchronized方式)

    java多线程同步示例,来自<疯狂java讲义>.通过synchronized,wait(),notify(),notifyAll()实现多线程同步与通信.假设现在系统中有两个线程,这两个 ...

  2. java 多线程同步问题_Java多线程同步问题:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.一个简单的Demo引发的血案 关于线程同步问题我们从一个 ...

  3. java synchronized块_Java多线程同步代码块Synchronized

    Java多线程同步代码块Synchronized Java中的每个对象都有一个与之关联的内部锁(Intrinsic lock). 这种锁也称为监视器(Monitor), 这种内部锁是一种排他锁,可以保 ...

  4. java 同步锁_Java多线程:synchronized同步锁的使用和实现原理

    作用和用法 在多线程对共享资源进行并发访问方面,JDK提供了synchronized关键字来进行线程同步,实现多线程并发访问的线程安全.synchronized的作用主要体现在三个方面:(1)确保线程 ...

  5. java多线程同步异步_003JAVA多线程同步与异步方法

    003JAVA多线程同步与异步方法 发布时间:2020-04-03 09:51:20 来源:51CTO 阅读:314 作者:zjy1002261870 package com.skcc.mthread ...

  6. java线程钥匙_Java多线程并发编程/锁的理解

    一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...

  7. 线程同步锁 java_java多线程同步之重入锁,详细解析

    上次已经为大家介绍过java多线程同步,Volatile详解的主要内容了.今天再来为大家介绍一些相关的内容,也就是java多线程同步之重入锁,一起来了解一下吧. 使用重入锁实现线程同步 在JavaSE ...

  8. java线程栅栏_Java 多线程基础 - CyclicBarrier

    我的博客 转载请注明原创出处. 序 java.util.concurrent包里有几个能帮助人们管理相互合作的线程集的类,为多线程常见的应用场景预置了抽象好的类库.在遇到这些应用场景时应该直接重用合适 ...

  9. java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...

    线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...

  10. python 多线程同步_Python利用多线程同步锁实现多窗口订票系统(推荐)

    利用Python实现多窗口订票系统,利用 threading.Lock() 避免出现一票多卖,无票也卖的情况,并规范化输出情况. 代码: import threading import time ti ...

最新文章

  1. Python组合数据类型之字典类型
  2. Python Selenium + phantomJS 模拟登陆教务管理系统 “抢课”
  3. AOJ 491.扑克牌
  4. mysql安全权限的讲解
  5. html 右侧滚动,HTMLCSS实现左侧固定宽度右侧内容可滚动
  6. codeforces 808d
  7. Java 8的8个新功能
  8. 将数据库表导入到solr索引
  9. linux 判断文件上传轨迹,linux各种常用命令
  10. echarts柱状图x轴 label一行超过设置的字数换行
  11. poj3264Balanced Lineup(RMQ)
  12. html判断用户名的合法性,javascript简单判断输入内容是否合法的方法
  13. WebMvc中MultipartFile文件上传
  14. 数据集中趋势度量:众数、平均数、中位数、几何平均数
  15. python实验报告代写_programs作业代写、代做data课程作业、代写Python实验作业、Python编程设计作业调试...
  16. 雷军需要讲好新故事,小米需要新风口
  17. 未来,Siri将能读心!
  18. 微博html5版什么手机,搜狐微博推出全新手机HTML5触屏版
  19. 关于Mysql的驱动(org.gjt.mm.mysql.Driver)问题
  20. ijkplayer源码---FFPlayer结构体4 SDL_Aout

热门文章

  1. python:修改图片的尺寸
  2. 笔记本电脑频繁自动重启_如何解决电脑频繁自动重启
  3. windows设置路径Path
  4. CCF 201712-3 Crontab
  5. Python之统计英文字符的个数
  6. php里用钢笔画曲线,PS如何使用钢笔工具进行抠图
  7. c语言常量(c语言常量定义规则)
  8. 【每日一P】利用通道抠图更换天空
  9. PhotoShop CS6 抠图教程
  10. 全国各省会城市经纬度(包含港澳台)