原文:http://blog.csdn.net/luoweifu/article/details/46613015
作者:luoweifu
转载请标名出处


编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程》一文详细讲述了线程、进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础。本文将接着讲一下Java线程同步中的一个重要的概念synchronized.

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。


修饰一个代码块

  1. 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。我们看下面一个例子:

【Demo1】:synchronized的用法

  1. /**
  2. * 同步线程
  3. */
  4. class SyncThread implements Runnable {
  5. private static int count;
  6.  
  7. public SyncThread() {
  8. count = 0;
  9. }
  10.  
  11. public void run() {
  12. synchronized(this) {
  13. for (int i = 0; i < 5; i++) {
  14. try {
  15. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  16. Thread.sleep(100);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }
  23.  
  24. public int getCount() {
  25. return count;
  26. }
  27. }

 

SyncThread的调用:

  1. SyncThread syncThread = new SyncThread();
  2. Thread thread1 = new Thread(syncThread, "SyncThread1");
  3. Thread thread2 = new Thread(syncThread, "SyncThread2");
  4. thread1.start();
  5. thread2.start();

结果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9*

当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

我们再把SyncThread的调用稍微改一下:

  1. Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
  2. Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
  3. thread1.start();
  4. thread2.start();

结果如下:

SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9

不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为什么上面的例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联,而上面的代码等同于下面这段代码:

  1. SyncThread syncThread1 = new SyncThread();
  2. SyncThread syncThread2 = new SyncThread();
  3. Thread thread1 = new Thread(syncThread1, "SyncThread1");
  4. Thread thread2 = new Thread(syncThread2, "SyncThread2");
  5. thread1.start();
  6. thread2.start();
  1.  

这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。


2.当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
【Demo2】:多个线程访问synchronized和非synchronized代码块

  1. class Counter implements Runnable{
  2. private int count;
  3.  
  4. public Counter() {
  5. count = 0;
  6. }
  7.  
  8. public void countAdd() {
  9. synchronized(this) {
  10. for (int i = 0; i < 5; i ++) {
  11. try {
  12. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  13. Thread.sleep(100);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. }
  20.  
  21. //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
  22. public void printCount() {
  23. for (int i = 0; i < 5; i ++) {
  24. try {
  25. System.out.println(Thread.currentThread().getName() + " count:" + count);
  26. Thread.sleep(100);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32.  
  33. public void run() {
  34. String threadName = Thread.currentThread().getName();
  35. if (threadName.equals("A")) {
  36. countAdd();
  37. } else if (threadName.equals("B")) {
  38. printCount();
  39. }
  40. }
  41. }

调用代码:

  1. Counter counter = new Counter();
  2. Thread thread1 = new Thread(counter, "A");
  3. Thread thread2 = new Thread(counter, "B");
  4. thread1.start();
  5. thread2.start();

结果如下:

A:0
B count:1
A:1
B count:2
A:2
B count:3
A:3
B count:4
A:4
B count:5

上面代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。


  1. 指定要给某个对象加锁

【Demo3】:指定要给某个对象加锁

  1. /**
  2. * 银行账户类
  3. */
  4. class Account {
  5. String name;
  6. float amount;
  7.  
  8. public Account(String name, float amount) {
  9. this.name = name;
  10. this.amount = amount;
  11. }
  12. //存钱
  13. public void deposit(float amt) {
  14. amount += amt;
  15. try {
  16. Thread.sleep(100);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. //取钱
  22. public void withdraw(float amt) {
  23. amount -= amt;
  24. try {
  25. Thread.sleep(100);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30.  
  31. public float getBalance() {
  32. return amount;
  33. }
  34. }
  35.  
  36. /**
  37. * 账户操作类
  38. */
  39. class AccountOperator implements Runnable{
  40. private Account account;
  41. public AccountOperator(Account account) {
  42. this.account = account;
  43. }
  44.  
  45. public void run() {
  46. synchronized (account) {
  47. account.deposit(500);
  48. account.withdraw(500);
  49. System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
  50. }
  51. }
  52. }

 

调用代码:

  1. Account account = new Account("zhang san", 10000.0f);
  2. AccountOperator accountOperator = new AccountOperator(account);
  3.  
  4. final int THREAD_NUM = 5;
  5. Thread threads[] = new Thread[THREAD_NUM];
  6. for (int i = 0; i < THREAD_NUM; i ++) {
  7. threads[i] = new Thread(accountOperator, "Thread" + i);
  8. threads[i].start();
  9. }

结果如下:

Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0

在AccountOperator 类中的run方法里,我们用synchronized
给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。

当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。

  1. public void method3(SomeObject obj)
  2. {
  3. //obj 锁定的对象
  4. synchronized(obj)
  5. {
  6. // todo
  7. }
  8. }

当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

  1. class Test implements Runnable
  2. {
  3. private byte[] lock = new byte[0]; // 特殊的instance变量
  4. public void method()
  5. {
  6. synchronized(lock) {
  7. // todo 同步代码块
  8. }
  9. }
  10.  
  11. public void run() {
  12.  
  13. }
  14. }

说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

修饰一个方法

Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。如将【Demo1】中的run方法改成如下的方式,实现的效果一样。

*【Demo4】:synchronized修饰一个方法

  1. public synchronized void run() {
  2. for (int i = 0; i < 5; i ++) {
  3. try {
  4. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  5. Thread.sleep(100);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. }

Synchronized作用于整个方法的写法。
写法一:

  1. public synchronized void method()
  2. {
  3. // todo
  4. }

写法二:

  1. public void method()
  2. {
  3. synchronized(this) {
  4. // todo
  5. }
  6. }

写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。

在用synchronized修饰方法时要注意以下几点:
1. synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:

在子类方法中加上synchronized关键字

  1. class Parent {
  2. public synchronized void method() { }
  3. }
  4. class Child extends Parent {
  5. public synchronized void method() { }
  6. }

在子类方法中调用父类的同步方法

  1. class Parent {
  2. public synchronized void method() { }
  3. }
  4. class Child extends Parent {
  5. public void method() { super.method(); }
  6. }

2、在定义接口方法时不能使用synchronized关键字。

3、构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。



修饰一个静态的方法

Synchronized也可修饰一个静态方法,用法如下:

  1. public synchronized static void method() {
  2. // todo
  3. }

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对Demo1进行一些修改如下:

【Demo5】:synchronized修饰静态方法

  1. /**
  2. * 同步线程
  3. */
  4. class SyncThread implements Runnable {
  5. private static int count;
  6.  
  7. public SyncThread() {
  8. count = 0;
  9. }
  10.  
  11. public synchronized static void method() {
  12. for (int i = 0; i < 5; i ++) {
  13. try {
  14. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  15. Thread.sleep(100);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21.  
  22. public synchronized void run() {
  23. method();
  24. }
  25. }

调用代码:

  1. SyncThread syncThread1 = new SyncThread();
  2. SyncThread syncThread2 = new SyncThread();
  3. Thread thread1 = new Thread(syncThread1, "SyncThread1");
  4. Thread thread2 = new Thread(syncThread2, "SyncThread2");
  5. thread1.start();
  6. thread2.start();

结果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这与Demo1是不同的。



修饰一个类

Synchronized还可作用于一个类,用法如下:

  1. class ClassName {
  2. public void method() {
  3. synchronized(ClassName.class) {
  4. // todo
  5. }
  6. }
  7. }

我们把Demo5再作一些修改。
【Demo6】:修饰一个类

  1. /**
  2. * 同步线程
  3. */
  4. class SyncThread implements Runnable {
  5. private static int count;
  6.  
  7. public SyncThread() {
  8. count = 0;
  9. }
  10.  
  11. public static void method() {
  12. synchronized(SyncThread.class) {
  13. for (int i = 0; i < 5; i ++) {
  14. try {
  15. System.out.println(Thread.currentThread().getName() + ":" + (count++));
  16. Thread.sleep(100);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }
  23.  
  24. public synchronized void run() {
  25. method();
  26. }
  27. }

其效果和【Demo5】是一样的,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。



总结:

A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。


参考资料:
http://blog.csdn.net/chenguang79/article/details/677720
http://developer.51cto.com/art/200906/132354.htm



原文:http://blog.csdn.net/luoweifu/article/details/46613015
作者:luoweifu
转载请标名出处

(转载)synchronized代码块的更多相关文章

  1. 【面试普通人VS高手系列】讲一下wait和notify这个为什么要在synchronized代码块中?

    一个工作七年的小伙伴,竟然不知道"wait"和"notify"为什么要在Synchronized代码块里面. 好吧,如果屏幕前的你也不知道,请在评论区打上&qu ...

  2. 多线程学习-基础( 十)一个synchronized(){/*代码块*/}简单案例分析

    一.提出疑惑 上一篇文章中,分析了synchronized关键字的用法.但是好像遗漏了一种情况. 那就是: synchronized(obj){/*同步块代码*/} 一般有以下几种情况: (1)syn ...

  3. synchronized 代码块怎么用

    加不加 synchronized 有什么区别? synchronized 作为悲观锁,锁住了什么? 之前 2 篇文章我们已经知道 synchronized 的使用方法以及锁的内容(实例对象和Class ...

  4. 2016/9/25编写java实验报告时对synchronized(同步代码块)的一些感悟

    通过此次实验,明白了多线程的设置和启动.synchronized代码块的用法.线程的优先级使用方法.知道了那几类资源是线程共享的. 我现在理解的多线程是:实例化一个继承了Thread类或实现了Runn ...

  5. 线程同步 synchronized 同步代码块 同步方法 同步锁

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  6. 彻底理解线程同步与同步代码块synchronized

    public class Demo { public static synchronized void fun1(){ } public synchronized void fun2(){ } pub ...

  7. synchronized锁机制 之 代码块锁(转)

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  8. synchronized同步代码块锁释放

    今天发现自己写的线上程序出现数据库不能同步的问题,查看日志已经停止记录,随后使用jstack查看线程的运行状况,发现有个同步线程锁住了. 以下是jstack -l 637  问题线程的内容. &quo ...

  9. 59、synchronized同步代码块

    synchronized同步方法的问题 有些情况下,在方法上面加synchronized同步,会有性能问题.请看下面代码,来计算下两个线程执行的耗时: package com.sutaoyu.Thre ...

随机推荐

  1. 牛客网 牛可乐发红包脱单ACM赛 A题 生成树

    [题解] 其实就是求两棵树不同的边有多少条.那么我们用一个set来去重即可. #include<cstdio> #include<cstring> #include<se ...

  2. 杭电 1241 Oil Deposits (很好的dfs)

    Description The GeoSurvComp geologic survey company is responsible for detecting underground oil dep ...

  3. 2018 GDCPC 省赛总结

    第二次参加省赛了,对比上年连STL都不会的acm入门者来说, 今年是接触acm的第二年. 首先要说的是今年的省赛比上年人数多了很多, 闭幕式200多支队伍坐满了整个礼堂还要站着不少人,所以今年的竞争其 ...

  4. 基本Sql语句汇总

    关于Sql语句的学习,选择的DBMS为SQL Server,Sql语句随着工作中的应用不断补充,不具备系统性,为个人笔记汇总,网上有很多优秀的资源,故不对每一处应用做过多细致的说明,后期会对部分篇幅较 ...

  5. 一个抓取智联招聘数据并存入表格的python爬虫

    talk is cheap...show you the code..... import requests import lxml,time,os from bs4 import Beautiful ...

  6. NYOJ 203 三国志

    三国志 时间限制:3000 ms  |  内存限制:65535 KB 难度:5   描述 <三国志>是一款很经典的经营策略类游戏.我们的小白同学是这款游戏的忠实玩家.现在他把游戏简化一下, ...

  7. Leetcode 289.生命游戏

    生命游戏 根据百度百科,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在1970年发明的细胞自动机. 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细胞.每个细胞具有一个初始状 ...

  8. Food Delivery (区间DP)

    When we are focusing on solving problems, we usually prefer to stay in front of computers rather tha ...

  9. [luoguP2447] [SDOI2010]外星千足虫(高斯消元 + bitset)

    传送门 用bitset优化,要不然n^3肯定超时 消元过程中有几点需要注意,找到最大元后break,保证题目中所说的K最小 如果有自由元说明解很多,直接返回 #include <bitset&g ...

  10. hdu 3879 最大权闭合图(裸题)

    /* 裸的最大权闭合图 解:参见胡波涛的<最小割模型在信息学竞赛中的应用 #include<stdio.h> #include<string.h> #include< ...