同步synchronized

同步方法

synchronized可用来给方法或者代码块加锁,当它修饰一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。这就意味着,当两个并发线程同时访问synchronized代码块时,两个线程只能是排队做串行处理,另一个线程要等待前一个线程执行完该代码块后,才能再次执行synchronized代码块。

使用synchronized修饰某个方法,该方法便成为一个同步方法,在同一时刻只能有一个线程执行该方法。可是,synchronized的锁机制太重量级,不但整个同步方法的代码都加锁,就连该方法用到的所有类变量也一并加锁。因此,同步方法覆盖的代码越多,加锁操作对效率的影响就越严重。

显式指纹(同步代码块)

为缩小同步方法的影响方法,我们可让synchronized只修饰某个代码块,而不必修饰整个方法,synchronized修饰后的代码块叫做同步代码块。同步代码块要先指定该代码块的密钥对象,这个对象可以是任意相关类的实例,它相当于一个指纹,每个线程执行同步代码块时都要先验证指纹,指纹相同的线程进入同一个队列依次排队,若指纹不同则进入另外的执行队列。

下面是同步方法和同步代码块的代码示例:

public class TestCode {public static void main(String[] args) {TestCode1 t1 = new TestCode1();TestCode2 t2 = new TestCode2();TestCode3 t3 = new TestCode3();Thread ta = new Thread(t1, "A");Thread tb = new Thread(t2, "B");Thread tc = new Thread(t3, "C");ta.start();tb.start();tc.start();}private static TestCode test = new TestCode();public static class TestCode1 implements Runnable {public synchronized void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);}}}public static class TestCode2 implements Runnable {public void run() {synchronized (test) {  //这里如果使用this则表示新实例,就不与其他代码块搞在一起for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);}}}}public static class TestCode3 implements Runnable {public void run() {synchronized (test) {  //这里如果使用this则表示新实例,就不与其他代码块搞在一起for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" synchronized loop "+i);}}}}}

下面是该示例代码的执行结果:

B synchronized loop 0
A synchronized loop 0
A synchronized loop 1
B synchronized loop 1
B synchronized loop 2
A synchronized loop 2
B synchronized loop 3
A synchronized loop 3
A synchronized loop 4
B synchronized loop 4
C synchronized loop 0
C synchronized loop 1
C synchronized loop 2
C synchronized loop 3
C synchronized loop 4

从输出结果可以看出,同步代码块如果加锁的是同一个对象实例,那么这两个同步代码块也被看作是互相排他的,同一时刻也只能有两个代码块的其中之一被执行,因此日志显示:线程B的同步代码块都执行完了,才开始执行线程C的同步代码块,即使两个代码块是在不同的地方。

隐式指纹

前面说到,synchronized会对同步代码内部的类变量加锁,这样一来,如果两个同步代码都使用了某个类变量,那也会产生排队等待的情况。因为某线程执行第一个同步代码时,会给类变量上锁;然后另一线程执行第二个同步代码,也准备给类变量上锁,结果发现类变量已经上锁无法再次上锁;所以后面的线程只好等待前面的线程执行完成。这种情况下,两个同步代码使用同一个类变量,我们可将其当作隐式指纹;而同步代码块事先指定密钥对象,可称作是显式指纹。

下面是隐式指纹的代码示例:

public class TestSynchronized {private int count = 0;public synchronized void plus() {System.out.println("begin plus count="+count);count++;try {Thread.sleep(2000);System.out.println("end plus count="+count);} catch (Exception e) {e.printStackTrace();}}public synchronized void minus() {System.out.println("begin minus count="+count);count--;System.out.println("end minus count="+count);}public void print() {try {Thread.sleep(1000);System.out.println("print count="+count);} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {final TestSynchronized ts = new TestSynchronized();Thread thread_plus = new Thread("thread_plus") {public void run() {System.out.println("Thread:" + super.getName());ts.plus();}};final Thread thread_minus = new Thread("thread_minus") {public void run() {System.out.println("Thread:" + super.getName());//改变ts.sub和ts.print的执行顺序,看看是什么情况ts.minus();ts.print();// ts.minus();}};thread_plus.start();Thread.sleep(1000);thread_minus.start();}}

下面是该示例代码的执行结果:

Thread:thread_plus
begin plus count=0
Thread:thread_minus
end plus count=1
begin minus count=1
end minus count=0
print count=0

从输出结果可以看出,minus线程总是在plus线程结束之后才开始执行,虽然两个线程的同步方法并没有指定加锁的对象,但两个方法内部都使用了类变量count,因此这两个方法成为了拥有同一个隐式指纹的排他方法。

加锁Lock

因为synchronized是重量级的加锁,对程序效率影响大,而且容易偏离预期,所以从jdk1.5开始,java引入了Lock接口,用来实现轻量级的加锁操作。Lock接口主要有两个派生类,分别是普通的重入锁ReentrantLock,以及读写锁ReentrantReadWriteLock。

重入锁ReentrantLock

ReentrantLock是不区分类型的普通锁,在lock与unlock之间的代码就是被锁保护的代码块。

ReentrantLock的常用方法如下:
lock : 加锁。该操作不允许中断,重复加锁时会一直等待。
unlock : 解锁。
lockInterruptibly : 允许中断的加锁。允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException
tryLock : 尝试锁。若之前未锁则加锁,并返回true;若之前已被当前线程锁,也返回true;若之前已被其他线程锁,则返回false。
getHoldCount : 获取当前线程的加锁次数。
isLocked : 判断是否加锁。
getQueueLength : 获取等待队列的长度。

读写锁ReentrantReadWriteLock

ReentrantReadWriteLock是区分了读锁和写锁的混合锁,读锁是共享锁,写锁是排它锁。

ReentrantReadWriteLock的常用方法如下:
readLock : 获得读锁对象ReentrantReadWriteLock.ReadLock
writeLock : 获得写锁对象ReentrantReadWriteLock.WriteLock
isWriteLocked : 判断是否加了写锁。
getReadHoldCount : 获取加读锁的次数。
getWriteHoldCount : 获取加写锁的次数。
getQueueLength : 获取等待队列的长度。

下面是ReentrantReadWriteLock.ReadLock的常用方法:
lock : 加读锁。
lockInterruptibly : 允许中断的加锁。
tryLock : 尝试加读锁。
unlock : 解除读锁。

下面是ReentrantReadWriteLock.WriteLock的常用方法:
lock : 加写锁。
lockInterruptibly : 允许中断的加锁。
tryLock : 尝试加写锁。
unlock : 解除写锁。

匿名内部类的加锁

匿名内部类使用synchronized要小心,虽然看起来同步代码只有一个,但是匿名内部类每次使用都是创建新类并实例化,所以多次使用匿名内部类其实是调用不同的类,不同类的内部同步方法,自然是互不影响的了。这就是匿名内部类时常令人迷惑的一个地方,遇到这种情况,建议采用Lock加锁,而不要用synchronized加锁。匿名内部类的说明参见《 Android开发笔记(八十六)几个特殊的类》。

下面是同时采用两种加锁方式的示例代码:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;public class TestAnonymous {public static String getNowDateTime() {SimpleDateFormat s_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date d_date = new Date();String s_date = "";s_date = s_format.format(d_date);return s_date;}private static void runSync() {for (int i = 0; i < 5; i++) {final int pos = i;Thread t = new Thread() {@Overridepublic synchronized void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getNowDateTime() + " runSync pos=" + pos);}};t.start();}}private final static ReentrantLock lock = new ReentrantLock();private static void runLock() {for (int i = 0; i < 5; i++) {final int pos = i;Thread t = new Thread() {@Overridepublic void run() {try {lock.lock();Thread.sleep(1000);System.out.println(getNowDateTime() + " runLock pos=" + pos);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}};t.start();}}public static void main(String[] args) {runSync();runLock();}}

下面是该示例代码的执行结果:

2016-04-20 11:25:36 runSync pos=1
2016-04-20 11:25:36 runSync pos=4
2016-04-20 11:25:36 runSync pos=2
2016-04-20 11:25:36 runSync pos=3
2016-04-20 11:25:36 runSync pos=0
2016-04-20 11:25:36 runLock pos=0
2016-04-20 11:25:37 runLock pos=1
2016-04-20 11:25:38 runLock pos=2
2016-04-20 11:25:39 runLock pos=3
2016-04-20 11:25:40 runLock pos=4

从输出结果可以看出,在匿名内部类中,synchronized的加锁操作不符合预期结果,各线程仍是乱序执行。相比之下,Lock的加锁操作符合预期,各线程按顺序依次执行。

点此查看Android开发笔记的完整目录

Android开发笔记(八十八)同步与加锁相关推荐

  1. Android开发笔记(十八)书籍翻页动画PageAnimation

    前面几节的动画都算简单,本文就介绍一个复杂点的动画--书籍翻页动画.Android有自带的翻页动画ViewPager,不过ViewPager只实现了平移效果.即便使用补间组合动画或者属性动画,也只是把 ...

  2. Android开发笔记(九十八)往图片添加部件

    添加圆角 添加圆角的功能,要用到Canvas类的drawRoundRect方法,即把画布裁剪成指定的圆角矩形. 下面是给图片添加圆角的效果截图: 下面是给图片添加圆角的代码片段: public sta ...

  3. Android开发笔记(一百八十七)利用估值器实现弹幕动画

    如今上网看电影电视越发流行了,追剧的时候经常看到视频上方数行评论向左边飘去,犹如子弹那样飞快掠过,这些评论文字因此得名"弹幕".弹幕评论由正在观看的网友们即兴发表,故而连绵不绝从画 ...

  4. Android开发笔记(十五)淡入淡出动画TransitionDrawable

    说到淡入淡出动画,可能大家会想到补间动画里面的AlphaAnimation,不过这个深浅动画只能对透明度做渐变效果,也就是只能对一个图形做深浅的颜色变换.如果我们想要从A图片逐渐变为B图片,也就是要实 ...

  5. Android开发笔记(十九)底部标签栏TabBar

    底部标签页实现思路 现在的APP,大多在页面底部显示标签栏Tabbar,用于切换不同栏目的页面.Tabbar起源于iOS,iOS的Tabbar自动位于页面下方,可是Android搬过来的时候做了改动, ...

  6. Android开发笔记(十六)秋千摇摆动画SwingAnimation

    上节博主介绍了AlphaAnimation和淡入淡出动画的使用,其实AlphaAnimation只是四种补间动画中的一种.那么为了加深对其他补间动画的理解,我想说说旋转动画RotateAnimatio ...

  7. Android开发笔记(十四)圆弧进度动画CircleAnimation

    一个好看的APP,都有不少精致的动画效果.熟练运用各种动画技术,可让我们的APP灼灼生辉.Android在技术上把动画分为了三类,分别是帧动画FrameAnimation.补间动画TweenAnima ...

  8. Android开发笔记(十二)测量尺寸与下拉刷新

    尺寸测量的配置 控件宽和高的设置方式 大家知道,自定义视图的目的就是要在屏幕上显示期望的图案,那在绘制图案之前,我们得先知道这个图案的尺寸(如宽多少高多少). 一般在xml中给控件的宽和高有三种赋值方 ...

  9. Xamarin.Android开发实践(十八)

    Xamarin.Android之SlidingMenu 一.前言 有位网友在评论中希望能够出个在Xamarin.Android下实现SlidingMenu效果的随笔,刚好昨天在观看官网示例项目的时候也 ...

  10. Android学习笔记(十八)——使用意图筛选器和实现浏览网页(附源代码)

    使用意图筛选器 点击下载源代码 1.创建一个Intents项目,给该项目加入一个新类,命名为MyBrowserActivity.在res/layout目录下新增一个browser.xml: 2.在An ...

最新文章

  1. SaaS服务在未来云计算中该如何发展
  2. 计算机虚拟化技术论文,【计算机网络论文】虚拟技术计算机网络论文(共1775字)...
  3. spark集群运行模式理解
  4. redis关键字删除_微信公众号文章防删除
  5. 值得看的hadoop书籍
  6. 算法导论 练习12.3
  7. TestComplete使用关键字测试的数据驱动测试(上)
  8. 【codevs5709】01背包
  9. vscode中前端vue项目详解_web前端Vue项目实战-Music
  10. 免校准的电量计量芯片_应物联网而生:合力为HLW8012系列免校准电能计量芯片-测试测量-与非网...
  11. 方波峰峰值和有效值_峰峰值,峰值,平均值,有效值的关系
  12. 软件需求包括3个不同的层次――业务需求、用户需求和功能需求
  13. c语言求100以内偶数和while,c语言 求1到100以内的偶数之和
  14. 八、python编写IP地址计算第一个可用IP地址、最后一个可用IP地址、网络号、广播地址、主机数
  15. python 制作英文单词_python常用150个英文单词
  16. 分布式框架DSF的搭建
  17. 腾讯云服务器无法通过密钥登录
  18. 鸿蒙系统 美的,美的九阳搭载鸿蒙系统的家电上市了,这手机系统上市真的稳了...
  19. 数码相片转传统相片尺寸表
  20. cmd命令 Java实现cmd命令打包

热门文章

  1. 集成学习——NGBoost论文研读与原理理解
  2. 蓝桥杯 基础练习 阶乘计算
  3. android ffmpeg编译动态库,最简单的android studio调用ffmpeg动态库
  4. bioconductor 安装包_R语言 | 你知道自己的Bioconductor版本么?
  5. linux禁止扫描端口,公网的服务器如何禁止别人扫描端口
  6. oracle10g静默升级,Linux下静默安装,升级和删除Oracle10g客户端
  7. [设计模式-结构型]装饰模式(Decorator)
  8. python什么是调用_Python中包(package)的调用方式
  9. linux运行库,Linux C 静态库 共享库 运行库
  10. ionic3 html调用摄像头,ionic3懒加载中使用自定义组件component