博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发编程基础四--同步代码块,同步方法,lock,死锁
阅读量:6310 次
发布时间:2019-06-22

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

hot3.png

老生常谈的一个问题,就bank的那个例子,同一张卡同时取钱,并发2个,每个取800,但卡里只有1000,不能让2个人同时操作.这里就需要线程同步.比如下面的DEMO.

首先定义一个账户类:

package org.credo.thread.tongbu;public class Account {	private String account;	private double money;		public Account(String account,double money){		this.account=account;		this.money=money;	}		//根据account重写hashcode和equals方法.	public int hasCode(){		return account.hashCode();	}	public boolean equals(Object obj){		if(this==obj){			return true;		}		if(obj!=null && obj.getClass()==Account.class){			Account target=(Account)obj;			return target.getAccount().equals(account);		}		return false;	}		public String getAccount() {		return account;	}	public void setAccount(String account) {		this.account = account;	}	public double getMoney() {		return money;	}	public void setMoney(double money) {		this.money = money;	}}
再就是业务类:
package org.credo.thread.tongbu;public class Draw extends Thread{		private Account account;	private double hopeGetMoney;		public Draw(String name,Account account,double hopeGetMoney){		super(name);		this.account=account;		this.hopeGetMoney=hopeGetMoney;	}		//当多个线程修改同一个共享数据的时候,将会引发数据问题.	public void run(){		if(account.getMoney() >= hopeGetMoney){			System.out.println(account.getAccount()+"---"+getName()+"取钱成功,取出金额为:"+hopeGetMoney);			try {				Thread.sleep(1);			} catch (Exception e) {				e.printStackTrace();			}			//修改金额			account.setMoney(account.getMoney()-hopeGetMoney);			System.out.println("余额为:"+account.getMoney());		}else{			System.out.println("余额不足,取款失败!");		}	}}
测试类:
package org.credo.thread.tongbu;public class NoSynchronizedTest {	public static void main(String[] args) {		//create a new account		Account account=new Account("印度人", 1000);		new Draw("印度人A", account, 800).start();		new Draw("印度人B", account, 800).start();	}}
结果是:

印度人---印度人B取钱成功,取出金额为:800.0

印度人---印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额为:-600.0

自然是错的,毕竟这不是信用卡.

在这种场景下,account这个账户必然要是一定时间内只能一个人操作的,是需要线程的同步的.

1.同步代码块

如下code:在业务类的run方法总加入synchronized代码块.

public void run(){		//使用account作为同步监视器,任何线程进入下面同步代码块之前必须先获得对account账户的锁定.		//其他线程无法获得锁,也就无法修改		//这种做法符合,  加锁---修改---释放锁   的逻辑.		synchronized (account) {			if(account.getMoney() >= hopeGetMoney){				System.out.println(account.getAccount()+"---"+getName()+"取钱成功,取出金额为:"+hopeGetMoney);				try {					Thread.sleep(1);				} catch (Exception e) {					e.printStackTrace();				}				//修改金额				account.setMoney(account.getMoney()-hopeGetMoney);				System.out.println("余额为:"+account.getMoney());			}else{				System.out.println("余额不足,取款失败!");			}		}		//结束,释放同步锁.	}
印度人---印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额不足,取款失败!
为了解决问题,java的处理方法就出现了"同步监视器",使用同步监视器的方法就是上面的同步代码块.语法嘛,就同上.

synchronized(obj){

    //此处代码就是同步代码块
}

括号里的obj就是同步监视器,上面的含义就是,线程开始执行同步代码前,必须先获得对同步监视器的锁定.当完成同步代码块后会释放锁定.
Java允许任何对象充当同步监视器,但实际上,我们必须使用可能被并发访问的贡献资源充当同步监视器.如上面代码的 account.

2.同步方法

与同步代码块对应的,java的多线程的安全支持还提供了同步方法,同步方法就是使用synchronized关键字去修饰某个方法.对于同步方法而言,其同步监视器就是this,就是该对象本身.

用过使用同步方法可以很方便的实现线程安全的类,线程安全的类具有如下特征:

  • 该类的对象可以被多个线程安全的访问
  • 每个线程调用该对象的任意方法都会得到正确的结果.
  • 每个线程调用该对象任意方法后,该对象状态依然保持合理状态.

synchronized关键字可以修饰方法,代码块,但不能修改构造器,属性等.

在Account类中加入下面代码:

public synchronized void process(double hopeGetMoney){		if(money >= hopeGetMoney){			System.out.println(Thread.currentThread().getName()+"取钱成功,取出金额为:"+hopeGetMoney);			try {				Thread.sleep(1);			} catch (Exception e) {				e.printStackTrace();			}			//修改金额			this.money=money-hopeGetMoney;			System.out.println("余额为:"+money);		}else{			System.out.println("余额不足,取款失败!");		}	}

修改业务类中的run()方法为:

public void run(){		account.process(hopeGetMoney);	}
印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额不足,取款失败!
可变类的线程安全是以降低程序的运行效率为代价的.因此如果严格考虑性能的话,可以提供 单线程环境和多线程环境,提供2个版本,,一个就是线程不安全,一个线程安全,分别在单线程和多线程中使用.

比如StringBuffer是多线程安全的,但性能不好.StringBuilder有好的性能,但线程是不安全的.

3.释放同步监视器的锁定

释放:

  • 1.当前线程的同步方法,同步代码块正常运行结束.
  • 2.当前线程在同步代码块,同步方法中遇到break,return什么的,
  • 3发生异常,错误.
  • 4.程序执行了同步监视器对象的wait()方法,线程暂停并释放同步监视器.

不释放:

  • 1.在期内执行了Thread.sleep,thread.yield方法.不会放开.
  • 2.用了suspend挂起,当然不应该用suspend和resume方法.

从JAVA5开始.有了Lock.

先贴代码,上一章最后的代码,不变,只修改account代码如下:

public class Account {	private String account;	private double money;	private final ReentrantLock lock=new ReentrantLock();		public Account(String account,double money){		this.account=account;		this.money=money;	}		public void process(double hopeGetMoney){		try {			lock.lock();		if(money >= hopeGetMoney){			System.out.println(Thread.currentThread().getName()+"取钱成功,取出金额为:"+hopeGetMoney);			try {				Thread.sleep(1);			} catch (Exception e) {				e.printStackTrace();			}			//修改金额			this.money=money-hopeGetMoney;			System.out.println("余额为:"+money);		}else{			System.out.println("余额不足,取款失败!");		}		} finally {			lock.unlock();		}	}		//根据account重写hashcode和equals方法.
使用的是lock.输出为:

印度人A取钱成功,取出金额为:800.0

余额为:200.0
余额不足,取款失败!

Lock是控制多个线程对共享资源进行访问的工具.通常,锁提供了对共享资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应获得Lock对象.

同步锁里面需要理解的东西也很多,这里........尼玛快过年了,实在没心情搞了.

===============死锁==================

死锁就是两个线程互相等待对方释放同步监视器时就回发生死锁.Java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时要注意这个情况.发生死锁的时候,整个程序没错误没异常,没提示,只是说有线程都处于阻塞状态,无法继续.

转载于:https://my.oschina.net/zhaoqian/blog/108306

你可能感兴趣的文章
C#中让WebBrowser运行Javascript脚本
查看>>
SDWebImage图片缓存清理以及缓存大小计算
查看>>
常用数据验证正则表达式释义(附:正则表达式常用符号)
查看>>
AC日记——[SCOI2009]游戏 bzoj 1025
查看>>
Windows Azure 即将更名
查看>>
微信开发-微信接入
查看>>
JVM(二)JVM内存布局
查看>>
MySQL count 优化
查看>>
Linux系统性能和使用活动监控工具---- sysstat
查看>>
大型网站架构系列:负载均衡详解(2)(转)
查看>>
db2用户密码不合法
查看>>
java求几个数字的和输出详细步骤
查看>>
Chrome控制台使用
查看>>
清北学堂培训2019.4.29
查看>>
【洛谷P1036 选数】
查看>>
ASCII CODE
查看>>
Resource View Window of Visual Studio
查看>>
数论篇 卷三 各种定理和公式
查看>>
Vijos1459 车展 (数学)
查看>>
Bzoj4423 [AMPPZ2013]Bytehattan
查看>>