博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java中synchronized同步的理解
阅读量:5342 次
发布时间:2019-06-15

本文共 5321 字,大约阅读时间需要 17 分钟。

在并发访问的问题上,Java引入了同步监视器来应对,主要是通过关键字synchronized实现。关于synchronized,它有两种形式,一种是同步代码块:synchronized(obj){},另一种是同步方法:public synchronized void method1(){},前者比较灵活,可以自己控制同步的范围,而后者同步的是整个方法。

同步代码块

synchronized(obj){}

在上述代码中,对象obj即是同步监视器,代码表示,如果要执行{}中的代码,必须先获得对obj的锁定。该对象可以是任何对象,但是一般情况下推荐使用那个要被并发访问的对象,因为同步监视器的作用就是防止对某资源进行并发访问。比如

1 package com.hm.thread.test; 2  3 public class Thread111 implements Runnable { 4     public Object o = new Object(); 5     public void run() { 6         synchronized (o) { 7             for (int i = 0; i < 5; i++) { 8                 System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); 9             }10         }11     }12 13     public static void main(String[] args) {14         Thread111 t1 = new Thread111();15         Thread ta = new Thread(t1, "A");16         Thread tb = new Thread(t1, "B");17         ta.start();18         tb.start();19     }20 }

如果不加synchronized (o) {},则输出的A,B是交错的,因为两个线程A,B并发执行,所以打印的结果就是交替输出。而使用了synchronized (o) {}之后,两个线程都要去获取o的锁,那么就变成了同步执行了,A和B分别输出:

A synchronized loop 0

A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
 同步方法

public synchronized void method1(){}

对于同步方法来说,没有显式的指定同步监视器,实际的同步监视器是this,即调用该方法的对象本身。

这里有一点需要注意:是否是同一个对象在多线程的访问同步方法。

这里借用一些例子说明,比如:

1 package com.hm.thread.account; 2  3 class Account { 4     String name; 5     float amount; 6  7     public Account(String name, float amount) { 8         this.name = name; 9         this.amount = amount;10     }11 12     public synchronized void deposit(float amt) {13         float tmp = amount;14         tmp += amt;15         try {16             Thread.sleep(1);// 模拟其它处理所需要的时间,比如刷新数据库等17         } catch (InterruptedException e) { }18         amount = tmp;19     }20 21     public synchronized void withdraw(float amt) {22         float tmp = amount;23         tmp -= amt;24         try {25             Thread.sleep(114);// 模拟其它处理所需要的时间,比如刷新数据库等26         } catch (InterruptedException e) { }27         amount = tmp;28     }29 30     public float getBalance() {31         return amount;32     }33 }34 35 public class AccountTest {36     private static int NUM_OF_THREAD = 200;37     static Thread[] threads = new Thread[NUM_OF_THREAD];38 39     public static void main(String[] args) {40         final Account acc = new Account("John", 1000.0f);41         for (int i = 0; i < NUM_OF_THREAD; i++) {42             threads[i] = new Thread(new Runnable() {43                 public void run() {44                     acc.deposit(100.0f);45                     acc.withdraw(100.0f);46                 }47             });48             threads[i].start();49         }50 51         for (int i = 0; i < NUM_OF_THREAD; i++) {52             try {53                 threads[i].join(); // 等待所有线程运行结束54             } catch (InterruptedException e) {55             }56         }57         System.out.println("Finally, John's balance is:" + acc.getBalance());58     }59 60 }

在上面的例子中,模拟账户中有1000元,存取款各100元,各200次,理论上余额应该还是1000,但是对于deposite()和 withdraw()方法,如果去掉synchronized修饰,则结果是不确定的。加上synchronized,则对于同一个account对象,在多个线程中并发访问deposite()和withdraw()方法,这两个方法都变成同步的了,即多个线程被同步了。

那么换个例子

1 package com.hm.thread.test; 2  3 public class ClassSync_Test { 4     public static void main(String[] args) { 5         Foo f1 = new Foo(1); 6         Foo f2 = new Foo(3); 7         f1.start(); 8         f2.start(); 9     }10 }11 12 class Foo extends Thread {13     private int val;14     public Foo(int val){15         this.val = val;16     }17     18     public synchronized void print(int v){19             int n = 100;20             int i = 0;21             while(i < n){22                 System.out.println(v);23                 i++;24                 try {25                     Thread.currentThread().sleep(1);26                 } catch (InterruptedException e) {27                     e.printStackTrace();28                 }29             }30     }31     public void run(){32         print(val);33     }34 }

在这个例子中,print()也是同步方法,但是在main方法中new出了两个Foo对象,分别启动线程执行,可以看到1和3交替出现,说明这个方法同时被两个线程执行了,而并不是同步的,原因在哪?

在Java中,类本身只有一个实例,但是类产生的对象却可以多个。对于Foo,它存在两个实例对象,启动它们的线程,彼此并不干扰,所以它们各自执行自己的同步方法。换句话说,synchronized虽然锁住了这个方法,但是只是针对同一个对象来说的,不同的对象会获取了各自的锁,彼此没有影响。所以,如果要实现同步,则需要让不同的对象在访问这个方法的时仍然去获取同一把锁,而不是各自获取各自的。即让synchronized去同步一个类级别的对象。

可以这么修改print():

1 public void print(int v){ 2         synchronized(Foo.class){ 3             int n = 100; 4             int i = 0; 5             while(i < n){ 6                 System.out.println(v); 7                 i++; 8                 try { 9                     Thread.currentThread().sleep(1);10                 } catch (InterruptedException e) {11                     e.printStackTrace();12                 }13             }14         }15     }

当锁住Foo.class之后,该类对应的对象在访问print方法时都会去获取Foo.class身上的锁--同一把锁。所以实现了同步。或者可以在该类中创建一个全局的静态变量,然后替换掉Foo.class也是可以的,因为静态的变量和类一样,都只有一份(要注意锁住一个class,范围较大,性能不好)。

另外一方面,对于wait(),notify(),notifyAll()三个方法,它们属于Object类,并且必须由同步监视器调用。所以在同步代码块中,使用obj去调用,obj.wait()。而对于同步方法来说,由于它没有显示指定同步监视器,并且监视器就是this,所以可以直接调用:this.wait()或者wait()。

引用:

http://www.shangxueba.com/jingyan/90315.html

http://www.cnblogs.com/devinzhang/archive/2011/12/14/2287675.html#top

 

转载于:https://www.cnblogs.com/seguzhizi/p/5145169.html

你可能感兴趣的文章
边框圆角方法
查看>>
asp.net WebApi自定义权限验证消息返回
查看>>
php中eval函数的危害与正确禁用方法
查看>>
20172315 2017-2018-2 《程序设计与数据结构》第十一周学习总结
查看>>
MySQL添加、修改、撤销用户数据库操作权限的一些记录
查看>>
关于谷歌浏览器Chrome正在处理请求的问题解决
查看>>
Git核心技术:在Ubuntu下部署Gitolite服务端
查看>>
平面波展开法总结
查看>>
建造者模式
查看>>
ArraySort--冒泡排序、选择排序、插入排序工具类demo
查看>>
composer 安装laravel
查看>>
8-EasyNetQ之Send & Receive
查看>>
Android反编译教程
查看>>
List<string> 去重复 并且出现次数最多的排前面
查看>>
js日志管理-log4javascript学习小结
查看>>
Android之布局androidmanifest.xml 资源清单 概述
查看>>
How to Find Research Problems
查看>>
Linux用户管理
查看>>
数据库第1,2,3范式学习
查看>>
《Linux内核设计与实现》第四章学习笔记
查看>>