一、在没有锁的情况下,举例下面的例子
(1)Account类,账户类,包括账户号码和余额
package cn.test.synchronization.one;
public class Account {
private String accountNo;
private double balance;
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj != null && obj.getClass() == Account.class){
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
(2)DrawThread 取钱类,包括账户实体和取钱金额
package cn.test.synchronization.one;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name, Account account,
double drawAmount){
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
if(account.getBalance() >= drawAmount){
System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("\t 余额为:"+account.getBalance());
}else{
System.out.println(getName() + "取钱失败!余额不足");
}
}
}
注意这个类中的run()方法实现了取钱的过程,按道理这不是面向对象的思想在编程,在面向对象中有一个设计模式叫领域驱动模式,强调把类的功能应该放在类里面。
(3)测试类:
package cn.test.synchronization.one;
public class TestDraw {
public static void main(String[] args) {
//创建一个账户
Account account = new Account("12345", 1000);
new DrawThread("甲", account, 800).start();
new DrawThread("乙", account, 800).start();
}
}
执行出现的结果:
乙取钱成功!吐出钞票:800.0
甲取钱成功!吐出钞票:800.0
余额为:200.0
余额为:-600.0
明显,由于run方法线程不安全的问题出现了以上异常的结果。
由此我们引出了线程锁的问题。关于锁的问题,有三种方式:
1、snychronize(){} :括号里面是对竞争资源的锁,这种方式是对属性加锁,而属性就是竞争资源
2、public snychronize void 方法名(){} : 这种是对this进行加锁
3、前面两种锁不能显示地加锁和显示地解锁,而这种方式就能完美的解决。同步锁(Lock),在实现多线程的安全控制中,通常喜欢用ReentrantLock(可重入锁)。使用该所进行显示加锁、显示释放锁。
二、snychronize(){}
(1)Account类,同上
(2)DrawThread,取钱类
package cn.test.synchronization.two;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name, Account account,
double drawAmount){
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
//synchronized可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等等
synchronized (account) {
if(account.getBalance() >= drawAmount){
System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("\t 余额为:"+account.getBalance());
}else{
System.out.println(getName() + "取钱失败!余额不足");
}
}
}
}
(3)测试类,同上
执行的结果是:
甲取钱成功!吐出钞票:800.0
余额为:200.0
乙取钱失败!余额不足
显然,没有出现异常。
三、public snychronize void 方法名(){}
(1)Account类,账户类
package cn.test.synchronization.three;
public class Account {
private String accountNo;
private double balance;
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj != null && obj.getClass() == Account.class){
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
//将draw()方法放在Account类中更符合面向对象的思想,在面向对象有一种设计模式叫
//领域驱动模式
//凡事具有两面性,加了synchronized方法使得可变类更加的安全,但是这种安全是有代价的,
//它是以降低程序的运行效率作为代价,所以我们有以下两点建议:
//1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不
// 进行同步,因为这会影响运行效率
//2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本
/// 这样可以保证性能
public synchronized void draw(double drawAmount){
if(this.getBalance() >= drawAmount){
System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额
this.balance = this.balance - drawAmount;
System.out.println("\t 余额为:"+this.getBalance());
}else{
System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足");
}
}
}
(2)DrawThread,取钱类
package cn.test.synchronization.three;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name, Account account,
double drawAmount){
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
account.draw(drawAmount);
}
}
(3)测试类,同上
运行结果与二相同。
=================================
可变类与不可变类讲解
凡事具有两面性,加了synchronized使得可变类更加的安全,但是这种安全是有代价的,它是以降低程序的运行效率作为代价,所以我们有以下两点建议:
1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不进行线程同步,因为这会影响运行效率
2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本,这样可以保证性能
前面两种线程同步的方式都无法显示地释放同步监视器,那么什么时候才会自动地释放同步监视器呢?
1、以下情况会自动释放同步监视器:
(1)同步方法或同步代码块已经执行完毕。包括(同步方法或同步代码块执行完最后一行、遇到break跳出同步方法或同步代码块、遇到return终止执行同步方法或同步代码块)
(2)当线程在同步方法或同步代码块遇到未处理的Error或Exception,导致异常结束时会释放同步监视器
(3)同步监视器监视的对象执行wait()方法,释放同步监视器
2、以下情况不会自动释放同步监视器:
(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()(线程让步)方法来暂停当前线程的执行、当前线程不会释放同步监视器
(2)线程执行同步代码块或同步方法时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器
=================================
三、同步锁
(1)Account类,账户类
package cn.test.synchronization.four;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
//定义锁对象,可重入锁
private final ReentrantLock lock = new ReentrantLock();
private String accountNo;
private double balance;
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object obj){
if(obj != null && obj.getClass() == Account.class){
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
//将draw()方法放在Account类中更符合面向对象的思想,在面向对象有一种设计模式叫
//领域驱动模式
//凡事具有两面性,加了synchronized方法使得可变类更加的安全,但是这种安全是有代价的,
//它是以降低程序的运行效率作为代价,所以我们有以下两点建议:
//1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不
// 进行同步,因为这会影响运行效率
//2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本
/// 这样可以保证性能
public void draw(double drawAmount){
//对同步锁进行加锁
lock.lock();
try{
if(this.getBalance() >= drawAmount){
System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改余额
this.balance = this.balance - drawAmount;
System.out.println("\t 余额为:"+this.getBalance());
}else{
System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足");
}
}finally {
lock.unlock();
}
}
}
(2)DrawThread类,取钱类
package cn.test.synchronization.four;
public class DrawThread extends Thread{
private Account account;
private double drawAmount;
public DrawThread(String name, Account account,
double drawAmount){
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
public void run(){
account.draw(drawAmount);
}
}
(3)测试类,同上
===============
避免死锁的几个方法:
·避免一个线程同时获取多个锁。
·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。