多线程同步中的门道

多线程同步中的门道(一)

  在涉及到多线程的开发时,线程同步的考虑是不可缺少的,否则很可能会造成各种超出预料的错误结果。以自己的学习经历来说,对于刚开始接触线程同 步的人可能会感觉非常简单,在多线程操作可能会造成数据混乱的地方同步一下不就行了嘛,加个synchronized关键字,多简单!可是随着开发的深 入,会渐渐的发现仅仅是一个synchronized关键字也不是那么简单,里面的门道和考虑到的情况还是不少。本系列就着循序渐进的程序和大家探讨一下 synchronized关键字使用中的各种情形和会造成的各种意料之外和意料之中的结果,欢迎各位大神轻拍。

  synchronized涉及到同步方法、同步代码块、同步类、同步对象、静态方法等,本系列来挨个探讨。

  注:因为考虑到文章篇幅和为了突出我们要分析的关键代码,所以下面程序有可能不会是最优写法。

  未作线程同步

  我们先来看看,在多线程运行下,未作线程同步的程序。

  [测试程序1]

  /**

  * Test case 1.

  * There is no thread synchronization.

  */

  public class Test {

  public static void main(String[] args) {

  final TestCase test = new TestCase();

  Thread thread1 = new Thread() {

  @Override

  public void run() {

  test.function();

  }

  };

  Thread thread2 = new Thread() {

  @Override

  public void run() {

  test.function();

  }

  };

  thread1.start();

  thread2.start();

  }

  }

  class TestCase {

  public void function() {

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread()。getName() + " executed result: " + i);

  }

  }

  }

  上面的测试程序很简单,定义了一个测试用例类,类中有一个循环输出5次"线程名+输出次数"的方法。然后设置了两个线程,启动这两个线程跑这个测试用例对象的方法,查看会有什么样的输出结果。后面的测试程序基本都是在此程序上修改变化而出,用来测试不同情况。

  运行程序,某次运行的结果可能如下:

  Thread-0 executed result: 0

  Thread-1 executed result: 0

  Thread-1 executed result: 1

  Thread-0 executed result: 1

  Thread-1 executed result: 2

  Thread-1 executed result: 3

  Thread-1 executed result: 4

  Thread-0 executed result: 2

  Thread-0 executed result: 3

  Thread-0 executed result: 4

  从程序输出结果可以看出,Thread-0和Thread-1是无规则交叉输出的,也就意味着在未作线程同步的情况下,两个线程同时执行着TestCase的function方法,这种是属于线程不安全的。

  同步方法

  我们对上面的程序进行一下修改,加一个synchronized关键字用来同步方法。

  [测试程序2.1]

  /**

  * Test case 2.1. synchronized method.

  */

  public class Test {

  public static void main(String[] args) {

  final TestCase test = new TestCase();

  Thread thread1 = new Thread() {

  @Override

  public void run() {

  test.function();

  }

  };

Thread thread2 = new Thread() {

  @Override

  public void run() {

  test.function();

  }

  };

  thread1.start();

  thread2.start();

  }

  }

  class TestCase {

  public synchronized void function() {// add synchronized keyword.

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread()。getName() + " executed result: " + i);

  }

  }

  }

  运行程序,得到的输出结果总是如下:

  Thread-0 executed result: 0

  Thread-0 executed result: 1

  Thread-0 executed result: 2

  Thread-0 executed result: 3

  Thread-0 executed result: 4

  Thread-1 executed result: 0

  Thread-1 executed result: 1

  Thread-1 executed result: 2

  Thread-1 executed result: 3

  Thread-1 executed result: 4

  从输出结果可以看出,同步了方法之后,两个线程顺序输出,说明线程Thread-1进入function方法后,线程Thread-2在方法外等待,等Thread-1执行完后释放锁,Thread-2才进入方法执行。

  但是我们对上面的代码稍作修改,看看同步方法,对于不同的对象情况下是否都有线程同步的效果。

  [测试程序2.2]

  /**

  * Test case 2.2. synchronized method but different objects.

  */

  public class Test {

  public static void main(String[] args) {

  // There are two objects.

  final TestCase test1 = new TestCase();

  final TestCase test2 = new TestCase();

  Thread thread1 = new Thread() {

  @Override

  public void run() {

  test1.function();

  }

  };

  Thread thread2 = new Thread() {

  @Override

  public void run() {

  test2.function();

  }

  };

  thread1.start();

  thread2.start();

  }

  }

  class TestCase {

  public synchronized void function() {// add synchronized keyword.

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread()。getName() + " executed result: " + i);

  }

  }

  }

  执行程序,某次的运行结果如下:

  Thread-0 executed result: 0

  Thread-0 executed result: 1

  Thread-1 executed result: 0

  Thread-1 executed result: 1

  Thread-0 executed result: 2

  Thread-0 executed result: 3

  Thread-0 executed result: 4

  Thread-1 executed result: 2

  Thread-1 executed result: 3

  Thread-1 executed result: 4

  从以上结果可以看出,同步方法时,当一个线程进入一个对象的这个同步方法时,另一个线程可以进入这个类的别的对象的同一个同步方法。

  同步方法小结

  在多线程中,同步方法时:

  同步方法,属于对象锁,只是对一个对象上锁;

  一个线程进入这个对象的同步方法,其他线程则进不去这个对象所有被同步的方法,可以进入这个对象未被同步的其他方法;

  一个线程进入这个对象的同步方法,其他线程可以进入同一个类的其他对象的所有方法(包括被同步的方法)。

  同步方法只对单个对象有用。

  对静态方法的同步

  上面是对普通的方法进行同步,发现只能锁对象。那么这次我们试着同步静态方法看会有什么结果,与上面的[测试程序2.2]来进行对比。

  [测试程序3.1]

  /**

  * Test case 3.1. synchronized static method.

  */

  public class Test {

  public static void main(String[] args) {

  // There are two objects.

  final TestCase test1 = new TestCase();

  final TestCase test2 = new TestCase();

  Thread thread1 = new Thread() {

  @Override

  public void run() {

  test1.function();

  }

  };

Thread thread2 = new Thread() {

  @Override

  public void run() {

  test2.function();

  }

  };

  thread1.start();

  thread2.start();

  }

  }

  class TestCase {

  public synchronized static void function() {// add static keyword.

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread()。getName() + " executed result: " + i);

  }

  }

  }

  执行程序,运行结果总是如下:

  Thread-0 executed result: 0

  Thread-0 executed result: 1

  Thread-0 executed result: 2

  Thread-0 executed result: 3

  Thread-0 executed result: 4

  Thread-1 executed result: 0

  Thread-1 executed result: 1

  Thread-1 executed result: 2

  Thread-1 executed result: 3

  Thread-1 executed result: 4

  从结果可以看出,两个线程顺序执行。而上面的[测试程序2.2]两个线程是交叉输出的。为什么同步静态方法会起到两个对象也能同步的了呢?

  因为被static修饰的方法是静态方法,也被称为类方法,表明这个方法是属于类的。与普通方法的区别是,在类被加载进内存时,就分配了方法的入口地址,而普通方法要实例化对象后才分配方法的入口地址。

  所以对静态方法进行同步,相当于是锁住了类,当一个线程进入这个静态方法时,其他线程无论使用这个类的哪个对象都无法进入这个静态方法,直到上一个线程执行完毕释放锁。

  同样的当一个线程进入这个静态方法时,其他线程也进不去这个类的其他被同步的静态方法,因为只要是静态方法都是属于类的嘛。但是可以进入其他未同步的方法(包括静态方法)。这些可以自己来测试,就不上例子了。

  但是这种方式,与完全的同步类又有些区别,我们可以继续看下面的程序。

  [测试程序3.2]

  /**

  * Test case 3.2. synchronized static method.

  */

  public class Test {

  public static void main(String[] args) {

  // There are two objects.

  final TestCase test = new TestCase();

  Thread thread1 = new Thread() {

  @Override

  public void run() {

  test.function1();

  }

  };

  Thread thread2 = new Thread() {

  @Override

  public void run() {

  test.function2();

  }

  };

  thread1.start();

  thread2.start();

  }

  }

  class TestCase {

  public synchronized static void function1() {// add static keyword.

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread()。getName() + " executed result: " + i);

  }

  }

  public synchronized void function2() {// no static keyword.

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread()。getName() + " executed result: " + i);

  }

  }

  }

  执行程序,某次的运行结果如下:

  Thread-0 executed result: 0

  Thread-1 executed result: 0

  Thread-1 executed result: 1

  Thread-1 executed result: 2

  Thread-0 executed result: 1

  Thread-0 executed result: 2

  Thread-1 executed result: 3

  Thread-1 executed result: 4

  Thread-0 executed result: 3

  Thread-0 executed result: 4

  从以上结果可以看出,虽然静态方法和普通方法都被同步,虽然是对同一个对象,但是两个线程仍然交叉执行。说明当一个线程进入了类的静态同步方法,其他线程可以进入这个类的非静态的同步方法。

  同步静态方法小结

  在多线程中,同步静态方法时:

  同步静态方法时,相当于对类所有的类方法上锁,但并不是完全的类同步;

  一个线程进入这个类的静态同步方法时,其他线程无法进入这个类的其他静态同步方法;

  但是其他线程可以进入这个类的非静态方法(无论是否同步)

  至于未同步的方法,无论是否是静态方法,都是想进就进啦。

原文发布于微信公众号 - java一日一条(mjx_java)

原文发表时间:2015-12-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xx_Cc的学习总结专栏

C - 基础总结

374110
来自专栏JAVA高级架构

Java面试题合集

1.抽象类与接口的区别是什么? 一个类可以实现多个接口,但是只能继承以及抽象类。类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声...

390100
来自专栏阿凯的Excel

Python读书笔记17(while与列表、字典)

今天分享利用while函数处理列表和字典,顺便温习一下历史知识 一、论如何将一个列表折腾至另外一个列表!(两个列表是独立的) 论折腾列表有几种方法! 先分...

37450
来自专栏liulun

Nim教程【十五】【完结】

模版 模版是Nim语言中的抽象语法树,它是一种简单的替换机制,在编译期被处理 这个特性使Nim语言可以和C语言很好的运行在一起 像调用一个方法一样调用一个模版 ...

24980
来自专栏程序员的SOD蜜

条件表达式的短路求值与函数的延迟求值

延迟求值是 .NET的一个很重要的特性,在LISP语言,这个特性是依靠宏来完成的,在C,C++,可以通过函数指针来完成,而在.NET,它是靠委托来完成的。如果不...

21960
来自专栏Spark学习技巧

Java动态代理原理及解析

代理:设计模式 代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个真实对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及...

48250
来自专栏Jerry的SAP技术分享

使用javap深入理解Java整型常量和整型变量的区别

下面我们就用javap将.class文件反编译出来然后深入研究Java里整型变量和整型常量的区别。

16230
来自专栏开源优测

python selenium2 - webelement操作常用方法

完整路径 C:\Python27\Lib\site-packages\selenium\webdriver\remote\webelement...

32950
来自专栏机器学习AI算法工程

Python一些基础面试题目总结

1 Python是如何进行内存管理的? 答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制 一、对象的引用计数机制 pytho...

41060
来自专栏逆向技术

C++反汇编第二讲,不同作用域下的构造和析构的识别

               C++反汇编第二讲,不同作用域下的构造和析构的识别 目录大纲:   1.全局(静态)对象的识别,(全局静态全局一样的,都是编译期间...

212100

扫码关注云+社区

领取腾讯云代金券