一 同步代码块

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

synchronized(obj){

//同步代码块

}

其中obj就是同步监视器,它的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。虽然java程序允许使用任何对象作为同步监视器,但 是同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

2.小例子

Account.java

public classAccount {privateString accountNo;private doublebalance;public Account(String accountNo,doublebalance){this.accountNo=accountNo;this.balance=balance;

}public doublegetBalance() {returnbalance;

}public void setBalance(doublebalance) {this.balance =balance;

}publicString getAccountNo() {returnaccountNo;

}public voidsetAccountNo(String accountNo) {this.accountNo =accountNo;

}

@Overridepublic booleanequals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;

Account account=(Account) o;returnaccountNo.equals(account.accountNo);

}

@Overridepublic inthashCode() {returnaccountNo.hashCode();

}

}

DrawThread.java

public class DrawThread extendsThread {privateAccount account;private doubledrawAmount;public DrawThread(String name, Account account, doubledrawAmount) {super(name);this.account =account;this.drawAmount =drawAmount;

}public voidrun(){synchronized(account){if(account.getBalance()>=drawAmount){

System.out.println(getName()+ "取钱成功,吐出钞票: " +drawAmount);try{

Thread.sleep(1);

}catch(InterruptedException ex){

ex.getStackTrace();

}

account.setBalance(account.getBalance()-drawAmount);

System.out.println("\t余额为:"+account.getBalance());

}else{

System.out.println(getName()+"取钱失败,余额不足");

}

}

}

}

DrawTest.java

public classDrawTest {public static voidmain(String[] args){

Account acct=new Account("1234567",1000);new DrawThread("甲",acct,800).start();new DrawThread("乙",acct,800).start();

}

}

甲取钱成功,吐出钞票: 800.0

余额为:200.0

乙取钱失败,余额不足

3.如果将DrawThread的同步去掉:

public class DrawThread extendsThread {privateAccount account;private doubledrawAmount;public DrawThread(String name, Account account, doubledrawAmount) {super(name);this.account =account;this.drawAmount =drawAmount;

}public voidrun(){//synchronized (account){

if(account.getBalance()>=drawAmount){

System.out.println(getName()+ "取钱成功,吐出钞票: " +drawAmount);try{

Thread.sleep(1);

}catch(InterruptedException ex){

ex.getStackTrace();

}

account.setBalance(account.getBalance()-drawAmount);

System.out.println("\t余额为:"+account.getBalance());

}else{

System.out.println(getName()+"取钱失败,余额不足");

}//}

}

}

会出现这些情况的结果:

乙取钱成功,吐出钞票: 800.0

甲取钱成功,吐出钞票: 800.0

余额为:200.0

余额为:-600.0

甲取钱成功,吐出钞票: 800.0

乙取钱成功,吐出钞票: 800.0

余额为:200.0

余额为:200.0

程序使用synchronized将run()方法里的方法体修改成同步代码块,同步监视器就是account对象,这样的做法符合“加锁-修改-释放锁”的逻辑,这样就可以保证并发线程在任一时刻只有一个线程进入修改共享资源的代码区。多次运行,结果只有一个。

二 同步方法

1.同步方法就是使用synchronized关键字修饰某个方法,这个方法就是同步方法。这个同步方法(非static方法)无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。通过同步方法可以非常方便的实现线程安全的类,线程安全的类有如下特征:

该类的对象可以方便的被多个线程安全的访问;

每个线程调用该对象的任意方法之后都能得到正确的结果;

每个线程调用该对象的任意方法之后,该对象状态依然能保持合理状态。

2.不可变类总是线程安全的,因为它的对象状态不可改变可变类需要额外的方法来保证其线程安全,在Account类中我们只需要把balance的方法变成同步方法即可。

Account.java

public classAccount {privateString accountNo;private doublebalance;public Account(String accountNo,doublebalance){this.accountNo=accountNo;this.balance=balance;

}//因为账户余额不可以随便更改,所以只为balance提供getter方法

public doublegetBalance() {returnbalance;

}publicString getAccountNo() {returnaccountNo;

}public voidsetAccountNo(String accountNo) {this.accountNo =accountNo;

}

@Overridepublic booleanequals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;

Account account=(Account) o;returnaccountNo.equals(account.accountNo);

}

@Overridepublic inthashCode() {returnaccountNo.hashCode();

}//提供一个线程安全的draw()方法来完成取钱操作

public synchronized void draw(doubledrawAmount){if(balance>=drawAmount){

System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);try{

Thread.sleep(1);

}catch(InterruptedException ex){

ex.printStackTrace();

}

balance-=drawAmount;

System.out.println("\t余额为:"+balance);

}else{

System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");

}

}

}

DrawThread.java

public class DrawThread extendsThread {privateAccount account;private doubledrawAmount;public DrawThread(String name, Account account, doubledrawAmount) {super(name);this.account =account;this.drawAmount =drawAmount;

}public voidrun(){

account.draw(drawAmount);

}

}

DrawTest.java

public classDrawTest {public static voidmain(String[] args){

Account acct=new Account("1234567",1000);new DrawThread("甲",acct,800).start();new DrawThread("乙",acct,800).start();

}

}

注意,synchronized可以修饰方法,修饰代码块,但是不能修饰构造器、成员变量等。在Account类中定义draw()方法,而不是直接在 run()方法中实现取钱逻辑,这种做法更符合面向对象规则。DDD设计方式,即Domain Driven Design(领域驱动设计),认为每个类都应该是完备的领域对象,Account代表用户账户,就应该提供用户账户的相关方法。通过draw()方法来执行取钱操作,而不是直接将setBalance()方法暴露出来任人操作。

但是,可变类的线程安全是以降低程序的运行效率为代价的,不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(共享资源)的方法进行同步。同时,可变类有两种运行环境:单线程环境和多线程环境, 则应该为可变类提供两种版本,即线程安全版本和线程不安全版本。如JDK提供的StringBuilder在单线程环境下保证更好的性能,StringBuffer可以保证多线程安全。

三 释放同步监视器的锁定

1.任何线程进入同步代码块,同步方法之前,必须先获得对同步监视器的锁定,那么如何释放对同步监视器的锁定呢,线程会在一下几种情况下释放同步监视器:

当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器;

当前线程在同步代码块、同步方法中遇到break,return终止了该代码块、方法的继续执行;

当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、方法的异常结束;

当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器;

2.以下几种情况,线程不会释放同步监视器:

线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器,当然,程序应尽量避免使用suspend()和resume()方法来控制线程。

四 同步锁:

1.Java5开始,Java提供了一种功能更加强大的线程同步机制——通过显式定义同步锁对象来实现同步,这里的同步锁由Lock对象充当。

Lock 对象提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock是控制多个线程对共享资源进行访问的工具。通常, 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。

某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock,ReadWriteLock是Java5提供的两个根接口,并为 Lock提供了ReentrantLock实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。在 Java8中提供了新型的StampLock类,在大多数场景下它可以替代传统的ReentrantReadWriteLock。 ReentrantReadWriteLock为读写操作提供了三种锁模式:Writing,ReadingOptimistic,Reading。

2.在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。主要的代码格式如下:

classX{//定义锁对象

private final ReentrantLock lock=newReentrantLock();//定义需要保证线程安全的方法

public voidm(){//加锁

lock.lock();try{//...method body

}//使用finally块来保证释放锁

finally{

lock.unlock();

}

}

}

将Account.java修改为:

public classAccount {private final ReentrantLock lock=newReentrantLock();privateString accountNo;private doublebalance;public Account(String accountNo,doublebalance){this.accountNo=accountNo;this.balance=balance;

}//因为账户余额不可以随便更改,所以只为balance提供getter方法

public doublegetBalance() {returnbalance;

}publicString getAccountNo() {returnaccountNo;

}public voidsetAccountNo(String accountNo) {this.accountNo =accountNo;

}

@Overridepublic booleanequals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;

Account account=(Account) o;returnaccountNo.equals(account.accountNo);

}

@Overridepublic inthashCode() {returnaccountNo.hashCode();

}//提供一个线程安全的draw()方法来完成取钱操作

public void draw(doubledrawAmount){//加锁

lock.lock();try{if (balance >=drawAmount) {

System.out.println(Thread.currentThread().getName()+ "取钱成功!吐出钞票:" +drawAmount);try{

Thread.sleep(1);

}catch(InterruptedException ex) {

ex.printStackTrace();

}

balance-=drawAmount;

System.out.println("\t余额为:" +balance);

}else{

System.out.println(Thread.currentThread().getName()+ "取钱失败,余额不足");

}

}finally{

lock.unlock();

}

}

}

使用Lock与使用同步代码有点相似,只是使用Lock时可以显式使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器。使用 Lock时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象,同一个时刻只能有一个线程进入临界区。Lock提供 了同步方法和同步代码块所没有的其他功能,包括使用非块结构的tryLock()方法,以及试图获取可中断锁的lockInterruptibly()方法,还有获取超时失效锁的tryLock(long,TimeUnit)方法。

ReentrantLock可重入锁的意思是,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

五 死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。

如DeadLock.java

classA{public synchronized voidfoo(B b){

System.out.println("当前线程名为:"+Thread.currentThread().getName()+"进入了A实例的foo()方法");try{

Thread.sleep(200);

}catch(InterruptedException ex){

ex.printStackTrace();

}

System.out.println("当前线程名为:"+Thread.currentThread().getName()+"试图调用B实例的last()方法");

b.last();

}public synchronized voidlast(){

System.out.println("进入了A类的last()方法内部");

}

}classB{public synchronized voidbar(A a){

System.out.println("当前线程名为:"+Thread.currentThread().getName()+"进入了B实例的bar()方法");try{

Thread.sleep(200);

}catch(InterruptedException ex){

ex.printStackTrace();

}

System.out.println("当前线程名为:"+Thread.currentThread().getName()+"试图调用A实例的last()方法");

a.last();

}public synchronized voidlast(){

System.out.println("进入了B类的last()方法内部");

}

}public class DeadLock implementsRunnable{

A a=newA();

B b=newB();public voidinit(){

Thread.currentThread().setName("主线程");

a.foo(b);

System.out.println("进入了主线程之后...");

}public voidrun(){

Thread.currentThread().setName("副线程");

b.bar(a);

System.out.println("进入了副线程之后...");

}public static voidmain(String[] args){

DeadLock d1=newDeadLock();newThread(d1).start();

d1.init();

}

}

结果:

当前线程名为:主线程进入了A实例的foo()方法

当前线程名为:副线程进入了B实例的bar()方法

当前线程名为:主线程试图调用B实例的last()方法

当前线程名为:副线程试图调用A实例的last()方法

java同步锁实例_Java同步锁全息详解相关推荐

  1. Java 初始化 代码块_Java中初始化块详解及实例代码

    Java中初始化块详解 在Java中,有两种初始化块:静态初始化块和非静态初始化块. 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量, ...

  2. java file 实例_Java File类的详解及简单实例

    Java File类的详解及简单实例 1. File():构造函数,一般是依据文件所在的指定位置来创建文件对象. CanWrite():返回文件是否可写. CanRead():返回文件是否可读. Co ...

  3. java单例设计模式_Java设计模式之单例模式详解

    在Java开发过程中,很多场景下都会碰到或要用到单例模式,在设计模式里也是经常作为指导学习的热门模式之一,相信每位开发同事都用到过.我们总是沿着前辈的足迹去做设定好的思路,往往没去探究为何这么做,所以 ...

  4. java connection 单例_Java设计模式之单例模式详解

    Java设计模式之单例模式详解 什么是设计模式 设计模式是在大量的实践中总结和理论之后优选的代码结构,编程风格,以及解决问题的思考方式.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可 ...

  5. java的static类_java中staticclass静态类详解

    一般情况下是不可以用static修饰类的.如果一定要用static修饰类的话,通常static修饰的是匿名内部类. 在一个类中创建另外一个类,叫做成员内部类.这个成员内部类可以静态的(利用static ...

  6. java方法怎么写_java方法定义格式详解,java方法怎么写?

    对于java方法你了解多少呢?你知道java方法应该如何写吗?下面要给大家介绍的就是和java方法相关的内容,一起来了解一下这个概念吧. 在学习运算符的时候,都为每个运算符单独的创建一个新的类和mai ...

  7. java指数表示法_Java指数计数法详解

    Java指数计数法详解 时间:2017-10-16     来源:华清远见Java培训中心 Java指数计数法并不是一个很难的运算,关键是你要理解应用,很多朋友不理解Java指数计数法,所以也无从运用 ...

  8. java解析json数据_java解析JSON数据详解

    JSON是目前最流行的轻量级数据交换语言(没有之一).尽管他是javaScript的一个子集.但由于其是独立与语言的文本格式,它几乎可以被所有编程语言所支持. 以下是对java语言中解析json数据的 ...

  9. java 乐观锁 实例_JAVA乐观锁实现-CAS(示例代码)

    是什么 全称compare and swap,一个CPU原子指令,在硬件层面实现的机制,体现了乐观锁的思想. JVM用C语言封装了汇编调用.Java的基础库中有很多类就是基于JNI调用C接口实现了多线 ...

  10. java 异或 排序_Java的位运算符详解实例——与()、非(~)、或(|)、异或(^)...

    位运算符主要针对二进制,它包括了:"与"."非"."或"."异或".从表面上看似乎有点像逻辑运算符,但逻辑运算符是针对两 ...

最新文章

  1. ewebeditor 上传大内容文本注意点
  2. 音效摸鱼还不够爽?试试IDE里打几盘魂斗罗?
  3. python 仪表盘-跟小白学Python数据分析——绘制仪表盘
  4. php和openresty效率,openresty say_hello 性能测试
  5. 2年质保期刚过就翘辫子,这是什么骚设计?
  6. 解密Arm Neoverse V1 和 Neoverse N2 平台 为下一代基础设施带来计算变革
  7. Java实现的时钟置换算法_时钟页面置换算法
  8. 小学教师计算机说课,浙江温州小学计算机教师资格认证说课稿
  9. 布局管理——复习示例
  10. 链表查找java_Java 实例 – 链表元素查找
  11. localhost 拒绝了我们的连接请求。_Zipkin请求链路日志聚合
  12. 资源 | 近500页python深度学习实践应用pdf
  13. oppo鸿蒙系统刷机包下载,oppo A11N原版系统rom固件刷机包下载20200716版卡刷包
  14. 重要不紧急紧急不重要
  15. Droid razr xt910 tegra2 地牢猎人2 dungeon hunter2
  16. A - Linearization of the kernel functions in SVM (模拟)
  17. openfoam前处理:并行计算decomposeParDict和setFieldsDict
  18. linux floppy 虚拟,Floppylinux
  19. 苹果怎么取消微信订阅服务器,教程:取消微信免密支付授权功能设置
  20. MySQL ODBC驱动安装和配置数据源

热门文章

  1. 使用Ajax时常用的转码方法encodeURI,escape,encodeURI
  2. 19.看板方法---变异性的根源
  3. 22.分布式系统基础设施
  4. 1. crontab 简介
  5. wex5 导入mysql_wex5 sqllite本地数据库的运用
  6. 应用安全-CMF/CMS漏洞整理
  7. 在手动安装 Kubernetes 的基础上搭建微服务
  8. Python自动化中的元素定位xpath(二)
  9. Activity传递数据
  10. Linux下pppd拨号脚本配置(转载)