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

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


修饰一个代码块

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

【Demo1】:synchronized的用法

 /**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count; public SyncThread() {
count = 0;
} public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public int getCount() {
return count;
}
}

SyncThread的调用:

 SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
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的调用稍微改一下:

 Thread thread1 = new Thread(new SyncThread(), "SyncThread1");    //两个不同的对象
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");    //两个不同的对象
 thread1.start();  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)与之相关联,而上面的代码等同于下面这段代码:

 SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

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


2.当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。 注意是非同步代码块,不能访问其它同步块,针对能否同时访问两个同步块,答案是否定的,下面会有更详细的就是。
【Demo2】:多个线程访问synchronized和非synchronized代码块

 class Counter implements Runnable{
private int count; public Counter() {
count = 0;
} public void countAdd() {
synchronized(this) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
public void printCount() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + " count:" + count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("A")) {                    //不同的线程对象操作不同的代码块
countAdd();
} else if (threadName.equals("B")) {
printCount();
}
}
}

调用代码:

 Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
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】:指定要给某个对象加锁

 /**
* 银行账户类
*/
class Account {
String name;
float amount; public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
//存钱
public void deposit(float amt) {
amount += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public void withdraw(float amt) {
amount -= amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public float getBalance() {
return amount;
}
} /**
* 账户操作类
*/
class AccountOperator implements Runnable{
private Account account;
public AccountOperator(Account account) {
this.account = account;
} public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
}
}
}

调用代码:

 Account account = new Account("zhang san", 10000.0f);          //创建加锁对象“account”
AccountOperator accountOperator = new AccountOperator(account);    //接口对象继承于“Runnable” final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);      //创建线程
threads[i].start();
}

结果如下:

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

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

 public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}

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

 class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
} public void run() { }
}

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

修饰一个方法

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

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

 public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

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

 public synchronized void method()
{
// todo
}

写法二:

 public void method()
{
synchronized(this) {
// todo
}
}

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

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

 class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public synchronized void method() { }
}

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

 class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public void method() { super.method(); }
}
  1. 在定义接口方法时不能使用synchronized关键字。
  2. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。 


修饰一个静态的方法

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

 public synchronized static void method() {
// todo
}

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

【Demo5】:synchronized修饰静态方法

 /**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count; public SyncThread() {
count = 0;
} public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public synchronized void run() {
method();
}
}

调用代码:

 SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
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还可作用于一个类,用法如下:

 class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}

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

 /**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count; public SyncThread() {
count = 0;
} public static void method() {
synchronized(SyncThread.class) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public synchronized void run() {
method();
}
}

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

【Demo7】:同一个对象的多个方法加锁

  可以看到“QueueBuffer ”对象的get/put方法都加锁,另外两个线程同时访问get/put方法,我们看到一个方法在被锁定期间,另一个锁方法是不能被访问的。也就是说这期间是这个方法的对象被锁定了,只有某一个线程放开对象资源,其它线程才可以重新获得对象,继而获取对象方法锁。

import java.util.concurrent.*;
import static net.mindview.util.Print.*; class QueueBuffer {
int n;
boolean valueSet = false; synchronized int get() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (n++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return n;
} synchronized void put() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (n++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} class Producer implements Runnable { private QueueBuffer q; Producer(QueueBuffer q) {
this.q = q;
} public void run() {
while (!Thread.interrupted()) {
q.put();              //q对象的put方法锁
} } } class Consumer implements Runnable { private QueueBuffer q; Consumer(QueueBuffer q) {
this.q = q;
} public void run() {
while (true) {
q.get();              //q对象的get方法锁
}
}
} public class wait_test { public static void main(String[] args) throws Exception {
QueueBuffer q = new QueueBuffer(); // 先产生一个锁对象
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Producer(q));
exec.execute(new Consumer(q));
TimeUnit.SECONDS.sleep(2); // Run for a while...
exec.shutdownNow(); // Interrupt all tasks
System.out.println("Press Control-C to stop.");
}
}

输出如下:

pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-2:5
pool-1-thread-2:6
pool-1-thread-2:7
pool-1-thread-2:8
pool-1-thread-2:9
pool-1-thread-2:10

如果没有synchronized修饰,输出如下:

pool-1-thread-1:0
pool-1-thread-2:1
pool-1-thread-1:2
pool-1-thread-2:3
pool-1-thread-1:4
pool-1-thread-2:5
pool-1-thread-1:6
pool-1-thread-2:7
pool-1-thread-1:8
pool-1-thread-2:9
pool-1-thread-1:10

JAVA Synchronized (一)的更多相关文章

  1. Java Synchronized Blocks

    From http://tutorials.jenkov.com/java-concurrency/synchronized.html By Jakob Jenkov   A Java synchro ...

  2. java synchronized(一)

    java synchronized主要用于控制线程同步,中间有很多小的细节,知识,这里我简单的整理一下,做个记录.主要用于方法和代码块的控制 先说说方法控制 模拟银行存款和取款,创建一个Account ...

  3. java synchronized使用

    java synchronized 基本上,所有并发的模式在解决线程冲突问题的时候,都是采用序列化共享资源的方案.这意味着在给定时刻只允许一个任务访问该资源.这个一般通过在代码上加一条锁语句实现,因为 ...

  4. Java synchronized 详解

    Java synchronized 详解 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 1.当两个并发线程访问同一个对象object ...

  5. Java Synchronized 关键字

    本文内容 Synchronized 关键字 示例 Synchronized 方法 内部锁(Intrinsic Locks)和 Synchronization 参考资料 下载 Demo Synchron ...

  6. Java synchronized 关键字详解

    Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...

  7. Java synchronized对象级别与类级别的同步锁

    Java synchronized 关键字 可以将一个代码块或一个方法标记为同步代码块.同步代码块是指同一时间只能有一个线程执行的代码,并且执行该代码的线程持有同步锁.synchronized关键字可 ...

  8. java synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...

  9. Java synchronized

    1. 将synchronized加在方法上, 即可实现对此方法的同步 public synchronized void deposit(float amt) { float tmp = amount; ...

  10. Java synchronized指南

    在多线程程序中,同步修饰符用来控制对临界区代码的访问.其中一种方式是用synchronized关键字来保证代码的线程安全性.在Java中,synchronized修饰的代码块或方法不会被多个线程并发访 ...

随机推荐

  1. 存code

    #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> us ...

  2. 解决Linux系统没有/etc/sysconfig/iptables文件

    Linux系统中,防火墙默认是不开启的,一般也没有配置过任何防火墙的策略,所以不存在/etc/sysconfig/iptables文件. 一.常规解决方法: 1.在控制台使用iptables命令随便写 ...

  3. WIN7实现多用户远程桌面

    版权声明:本文为博主原创文章,未经博主允许不得转载. 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://jonnyqin.blog. ...

  4. Java面试题集(151-180)

    摘要:这部分包括了Spring.Spring MVC以及Spring和其它框架整合以及測试相关的内容,除此之外还包括了大型站点技术架构相关面试内容. 151. Spring中的BeanFactory和 ...

  5. nginx匹配规则

    rewrite ^/(.*)$ /index.php/$1 last; 这句话的意思,是把slash(/)之后的所有内容在前面加上一个/index.php,这里的$1指代前面括号的正则表达式(.*)所 ...

  6. SolidEdge如何修改线型和线宽

    选中一条直线,然后点击如下所示两个按钮,可以分别修改线型和线宽.    

  7. C语言知识结构之二

    C语言的知识结构整理成思维导图,例如以下图所看到的: 这张图的总体思路是: 用C敲代码.该学会什么? 要用C写的更好,改学会什么? 事实上.仅仅要是分层的知识结构,大致的思路是: 首先,研究本层的知识 ...

  8. iphone 消息推送 实现

    IPhone 消息推送实现 参考 资料 http://blog.csdn.net/victormokai/article/details/39501277 对生成pem 的补充 拿到mac 上生成导出 ...

  9. 深入JVM系列(二)之GC机制、收集器与GC调优(转)

    一.回顾JVM内存分配   需要了解更多内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配2.大对象直接进入老年代 3.长期存活的对象 ...

  10. Cocoa root class

    问题来源: 常见面试问题之: NSObject和NSObject protocol有什么区别,为什么要有NSObject protocol, 有没有不继承自NSObject的类? 虽然在iOS开发过程 ...