在Java中,让线程同步的一种方式是使用synchronized关键字,它可以被用来修饰一段代码块,如下:

  1. synchronized(被锁的同步对象) {
  2. // 代码块:业务代码
  3. }

  当synchronized被用来修饰代码块的时候表示,如果有多个线程正在执行这段代码块,那么需要等到其中一个线程执行完毕,第二个线程才会再执行它。但是!如果被锁的同步对象没有被正确选择的话,上面的结论是不正确的哦。

到底什么样的对象能够成为一个锁对象(也叫同步对象)?我们在选择同步对象的时候,应当始终注意以下几点:

第一点,需要锁定的对象在多个线程中是可见的、同一个对象

  “可见的”这是显而易见的,如果对象不可见,就不能被锁定。“同一个对象”,这理解起来也很好理解,如果锁定的不是同一个对象,那又如何来同步两个对象呢?可是,不见得我们在这上面不会犯错误。为了阐述本建议,我们先模拟一个必须使用到锁的场景:火车站卖火车票。一列火车一共有100张票,一共有3个窗口在同时卖票,代码如下:

  1. package com.zuikc.thread;
  2.  
  3. public class SynchronizedSample01 {
  4. public static void main(String[] args) {
  5. // 创建
  6. TicketWindow window1 = new TicketWindow("售票窗口1");
  7. TicketWindow window2 = new TicketWindow("售票窗口2");
  8. TicketWindow window3 = new TicketWindow("售票窗口3");
  9.  
  10. window1.start();
  11. window2.start();
  12. window3.start();
  13. }
  14.  
  15. }
  16.  
  17. class TicketWindow extends Thread {
  18. // 共100个座位
  19. static int ticket = 100;
  20.  
  21. public TicketWindow(String name) {
  22. super(name);
  23. }
  24.  
  25. @Override
  26. public void run() {
  27. // 模拟卖票
  28. while (ticket > 0) {
  29.  
  30. System.out.println(this.getName() + "卖出了座位号:" + ticket);
  31. ticket--;
  32.  
  33. try {
  34. Thread.sleep(10);
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. }
  40. }

  可是,运行之后,我们发现我们的火车票有些座位被卖了多次,比如:

  只要多运行几次,我们就会看到不同的结果。但是几乎每次都会有被座位号被卖多次的现象发生。

  有同学可能会说,简单:加synchronized锁定同步对象,于是我们修改代码:

  1. class TicketWindow extends Thread {
  2. // 共100个座位
  3. static int ticket = 100;
  4.  
  5. // 定义被锁的同步对象
  6. Object obj = new Object();
  7.  
  8. public TicketWindow(String name) {
  9. super(name);
  10. }
  11.  
  12. @Override
  13. public void run() {
  14. // 想要同步的代码块
  15. synchronized (obj) {
  16. // 模拟卖票
  17. while (ticket > 0) {
  18.  
  19. System.out.println(this.getName() + "卖出了座位号:" + ticket);
  20. ticket--;
  21.  
  22. try {
  23. Thread.sleep(10);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }
  30. }

运行之后,我们发现结果没有任何的改变。为什么呐?

因为在3个线程中,我们锁定的不是同一个对象。

我们看到,被锁的是一个实例变量,如下:

Object obj = new Object();

而存在三个线程,就意味着生成了3个obj,每个线程锁定的是这3个不同的obj对象,所以,同步代码块等于没有被同步。

那应该怎么做呢?最简单的方法是,我们可以把实例变量改成成员变量,即静态变量,如下:

Static Object obj = new Object();

然后,再运行售票代码,就发现可以解决这个问题了。不信,试试看。

第二个注意事项:非静态方法中,静态变量不应作为同步对象

  上面刚说完,要修正第一点中的示例,需要将obj变成static。这似乎和本注意事项有矛盾。实际上,第一点中的示例代码仅出于演示的目的,在编写多线程代码时,我们可以遵循这样的一个原则:类型的静态方法应当保证线程安全,非静态方法不需实现线程安全。而如果将syncObject变成static,就相当于让非静态方法具备线程安全性,这带来的一个问题是,如果应用程序中该类型存在多个实例,在遇到这个锁的时候,都会产生同步,而这可能不是我们原先所愿意看到的。

第三点:值类型(基本数据类型)对象不能作为同步对象

  实际上,这样的代码也不会通过编译。

值类型在传递另一个线程的时候,会创建一个副本,这相当于每个线程锁定的也是两个对象。故,值类型对象不能作为同步对象。这一点实际也可以归结到第一点中。

第四点,锁定字符串是完全没有必要,而且相当危险的

  这整个过程看上去和值类型正好相反。字符串在虚拟机中会被暂存到内存里,如果有两个变量被分配了相同内容的字符串,那么这两个引用会被指向同一块内存。所以,如果有两个地方同时使用了synchronized (“abc”),那么它们实际锁定的是同一个对象,导致整个应用程序被阻滞。

第五点:降低同步对象的可见性

  同步对象一般来说,不应该是一个public变量,我们应该始终考虑降低同步对象的可见性,将我们的同步对象藏起来,只开放给自己或自己的子类就够了(需要开放给子类的情况其实也不多见)。

以下是广告时间:最课程(zuikc.com)正在招收Java就业班学员,如果你想学习更多的Java高质量代码编写方面的技巧,请联系我们哦。

Java代码质量改进之:同步对象的选择的更多相关文章

  1. Java代码质量改进之:使用ThreadLocal维护线程内部变量

    在上文中,<Java代码质量改进之:同步对象的选择>,我们提出了一个场景:火车站有3个售票窗口,同时在售一趟列车的100个座位.我们通过锁定一个靠谱的同步对象,完成了上面的功能. 现在,让 ...

  2. JSP中的Java代码和内置对象

    一.JSP中的Java代码 (一)JSP页面中有三种方式嵌入java代码: 1.java的表达式 格式:<%= java表达式 %> 2.java的语句 格式:<% java语句&g ...

  3. 关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

    Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享 ...

  4. Android学习笔记_32_通过WebView实现JS代码与Java代码互相通信

    webview两种实现方法,覆盖onKeyDown()方法 缓存 WebSettings应用注意的几个问题 1.要实现JS代码与Java代码互相通信,需要通过Android的WebView控件,在视图 ...

  5. JSP页面java代码报错:Purgoods cannot be resolved to a type

    错误提示 : Purgoods cannot be resolved to a type Purgoods不能解析为一个类型 原因 : 缺少引入Purgoods类 页面中引入java类,执行java代 ...

  6. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  7. 【转】java代码中实现android背景选择的selector-StateListDrawable的应用

    原文网址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0924/1712.html 下面的代码应该很多人都熟悉: 1 2 3 ...

  8. Oracle03——游标、异常、存储过程、存储函数、触发器和Java代码访问Oracle对象

    作者: kent鹏 转载请注明出处: http://www.cnblogs.com/xieyupeng/p/7476717.html 1.游标(光标)Cursor 在写java程序中有集合的概念,那么 ...

  9. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

随机推荐

  1. OEL的下载

    https://edelivery.oracle.com 注意的是:第一次下载安装的时候需要首先安装installer.exe软件,然后再下载即可.

  2. 写文件的工具类,输出有格式的文件(txt、json/csv)

    import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io. ...

  3. Non-zero exit code (1)

    刚报了这个错Non-zero exit code (1) 经排查执行这个命令就好了  python -m pip install --upgrade pip 一定要多看报错,报错中有提示的

  4. 115、如何构建Android MVVM 应用框架(转载)

    转载:http://android.jobbole.com/85198/

  5. springCloud之配置中心学习

    调试了好久,中与在地址栏输入http://localhost:9004/liyong-test/ms-dev.properties,奇迹出现了,终于可以访问我的配置中心了.这次也是碰运气,并没有觉得会 ...

  6. 把一张图片变成base64

    // image_file可为urlprivate function base64EncodeImage($image_file) { $image_info = getimagesize($imag ...

  7. date格式互转

    +"%Y/%m/%d-%H:%M:%S" date -d "2017/11/21 17:02:09" +%s

  8. IDEA 快捷将创建main函数

    在编写代码的时候直接输入psv就会看到一个psvm的提示,此时点击tab键一个main方法就写好了. psvm 也就是public static void main的首字母. 依次还有在方法体内键入f ...

  9. JS常用各种正则表达式(汇总)

    匹配URL 这个url的正则表达式判断的JavaScript!比较全面的.它验证的情况包括IP,域名(domain),ftp,二级域名,域名中的文件,域名加上端口!用户名等等信息, function ...

  10. 20165311 ch02 课下作业

    补充完成课上测试(不能只有截图,要有分析,问题解决过程,新学到的知识点) 完成教材 p97 2.96 2.97,要有完备的