【java】多线程-2

java多线程-1
在这里插入图片描述

1.线程安全问题的提出

当多个线程同时访问一个数据时,容易出现线程安全问题。
下面通过这个例子看如何一步步解决线程安全问题:银行取钱问题
(1)用户输入账户、密码,系统判断用户的账户、密码是否匹配。
(2)用户输入取款金额。
(3)系统判断账户余额是否大于取款金额。
(4)如果余额大于取款金额,则取款成功;如果余额小于取款金额,则取款失败。
模拟两个人使用同一个账户并发取钱的问题

1.1 第一步:封装一个银行账户

人去银行取钱,首先要有一个银行卡,也就是银行账户,包括银行卡号,银行卡余额
将这些封装为一个类,符合面向对象思想

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;
	}

	//根据accountNo重写hashCode()方法
	public int hashCode()
	{
		return accountNO.hashCode();
	}
	//根据accountNo重写equals()方法
	public boolean equals(Object obj)
	{
		//如果比较的是同一个对象,直接结束方法
		if(this==obj)
		{
			return true;
		}
		//equals对象可以传入任意类型的对象,但是两个对象要属于同一个类
		//public final Class getClass()表示此对象运行时类的 Class对象。
		//obj.getClass()== Account.class即为比较两个对象是否属于同一个类
		//相当于obj instancef Account
		if(obj != null&&obj.getClass()== Account.class)
		{
			//obj中没有getAccountNO()方法,想要使用子类的特有属性,向下转型
			Account target = (Account)obj;
			//通过getAccountNO()方法获取账户编号
			//调用String类的equals()方法,判断两个账户编号是否相同
			return target.getAccountNO().equals(accountNO);			
		}
		return false;
	}
}

1.2 第二步:提供一个取钱的线程类

创建一个线程类执行根据账户,取钱数量完成取钱任务
取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出钞票,余额减少。

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
			{
				//线程在这执行了切换,在这1ms内CPU会去执行另一个线程,
				//此时还没有修改账户余额,另一个线程在执行if语句时会判断成功
				Thread.sleep(1);
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}			
			//取出钱后,修改余额
			account.setBalance(account.getBalance()-drawAmount);
			System.out.println("\t余额为:"+account.getBalance());
		}
		else
		{
			System.out.println(getName()+"取钱失败,余额不足!");
		}
	}
}

1.3 第三步:启动两个线程对同一个账户进行取钱

主程序就是创建一个账户,并启动两个线程对同一个账户进行取钱

public class DrawTest
{
	public static void main(String[] args)
	{
		//创建一个账户
		Account acc = new Account("1234567",1000);
		//模拟两个线程对同一个账户取钱
		new DrawThread("甲",acc,800).start();
		new DrawThread("乙",acc,800).start();		
	}
}

使用Thread类方法创建线程对象时,多个线程之间无法共享线程类的实例变量,这句话在这里并不冲突,因为每创建一个线程对象就会创建一个DrawThread对象,这是两个不同的对象,所以无法共享线程类的实例变量。
但是传入的Account账户是同一个账户,也就是账户Account和线程对象并没有绑定在一起。但是两个人是对这同一个账户进行取钱。

1.4 产生的结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
分析原因:
之所以出现问题是因为run()方法的方法体不具有同步安全性——程序中有两个并发线程在修改Account对象;而且系统恰好在粗体字代码处执行线程切换,切换给另一个修改Account对象的线程,所以就出现了问题。
在这里插入图片描述

2 同步代码块

  1. Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。
        synchronized(obj)
        {
            ...
            //此处的代码就是同步代码块
        }
  1. obj就是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
  2. 任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。
  3. Java程序允许使用任何对象作为同步监视器,但想一下同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

代码示例:

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作为同步监视器,任何线程进入下面同步代码块之前
		//必须先获得account账户的锁定----其他线程无法访问它,也就无法修改它
		//这种做法符合:“加锁-->修改-->释放锁”的逻辑
		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 同步方法

Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身。

代码示例:
同步代码块中共享资源是账户Account,Account类中的属性accountNO和balance都是可变的,当多个线程同时修改Account对象的balance属性时,程序就会出现异常。
下面我们将Account类对balance的访问设置成线程安全的,那么只要把balance的方法修改成同步方法即可。

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;
	}

	//因为账户余额不允许随便修改,所以只提供getter方法
	public double getBalance()
	{
		return balance;
	}
	//提供一个线程安全的draw()方法来完成取钱操作
	public synchronized void draw(double drawAmount)
	{
		//账户余额大于取钱数目
		if(balance >= drawAmount)
		{
			//吐出钞票
			System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票"+drawAmount);			
			
			try
			{
				Thread.sleep(1);
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}	
				
			//取出钱后,修改余额
			balance = drawAmount - balance;
			System.out.println("\t余额为:"+balance);
		}
		else
		{
			System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
		}
	}
	
	//根据accountNo重写hashCode()方法
	public int hashCode()
	{
		return accountNO.hashCode();
	}
	//根据accountNo重写equals()方法
	public boolean equals(Object obj)
	{
		if(this==obj)
		{
			return true;
		}
		if(obj != null&&obj.getClass()== Account.class)
		{
			
			Account target = (Account)obj;
			return target.getAccountNO().equals(accountNO);			
		}
		return false;
	}
}

draw()方法为同步方法,同步方法的同步监视器问this,哪个对象调用该方法,哪个对象就是同步监视器,任何时刻只能有一个线程获得对Account对象的锁定。

public class DrawThread extends Thread
{
	public class DrawThread extends Thread
{
	//模拟用户的账户
	private Account account;
	//当前取钱线程希望取的钱数
	private double drawAmount;
	public DrawThread(String name,Account account,double drawAmount)
	{
		//形参account是引用类型的变量
		//将创建的Account对象acc作为实参传递给形参account
		//再将局部变量account赋值给成员变量account
		//所以Account的对象为account
		super(name);
		this.account= account;
		this.drawAmount=drawAmount;
	}
	public void run()
	{
		//直接调用account对象的draw()方法来执行取钱操作
		//同步方法的同步监视器时this,this代表调用draw方法的对象
		//线程进入draw方法之前,必须对account对象锁定
		account.draw(drawAmount);					
	}
}

在线程执行体中调用draw()方法完成取钱操作

public class DrawTest
{
	public static void main(String[] args)
	{
		//创建一个账户
		Account acc = new Account("1234567",1000);
		//模拟两个线程对同一个账户取钱
		new DrawThread("甲",acc,800).start();
		new DrawThread("乙",acc,800).start();		
	}
}

调用draw()方法的对象是account,因此多个线程并发访问同一份account前,必须先对account对象加锁。,这也符合了加锁----修改-----释放锁。

4 Lock锁

  1. java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁使用Lock对象充当。
  2. 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  3. Lock锁的语法格式如下:
class X
        {
            // 定义锁对象
            private final ReentrantLock lock = new ReentrantLock();
            // ...
            // 定义需要保证线程安全的方法
            public void m()
            {
                  // 加锁
                  lock.lock();
                  try
                  {
                        // 需要保证线程安全的代码
                        // ... method body
                  }
                  // 使用finally块来保证释放锁
                  finally
                  {
                        lock.unlock();
                  }
            }
        }

使用ReentrantLock对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。

代码示例:

import java.util.concurrent.locks.ReentrantLock;
public class Account
{
	//定义锁对象
	private final ReentrantLock lock = new ReentrantLock();
	//封装账户编号和账户余额
	private String  accountNO;
	private double balance;
	public Account(String name,String Account,double balace)
	{
		this.accountNO = accountNO;
		this.balance = balance;
	}
	public String getAccountNO()
	{
		return accountNO;
	}
	public void setAccountNO(String accountNO)
	{
		this.accountNO = accountNO;
	}
	public double getBalance()
	{
		return balance;
	}
	//提供一个线程安全的draw()方法
	public void draw(double drawAmount)
	{
		//加锁
		lock.lock();
		try
		{
			//账户余额大于取钱数目
			if(balance >= drawAmount)
			{
				//吐出钞票
				System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票"+drawAmount);			
				
				try
				{
					Thread.sleep(1);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}	
					
				//取出钱后,修改余额
				balance = drawAmount - balance;
				System.out.println("\t余额为:"+balance);
			}
			else
			{
				System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
			}
		}
		finally
		{
			lock.unlock();
		}
		
	}
	 	//根据accountNo重写hashCode()方法
		public int hashCode()
		{
			return accountNO.hashCode();
		}
		//根据accountNo重写equals()方法
		public boolean equals(Object obj)
		{
			if(this==obj)
			{
				return true;
			}
			if(obj != null&&obj.getClass()== Account.class)
			{
				
				Account target = (Account)obj;
				return target.getAccountNO().equals(accountNO);			
			}
			return false;
		}
}

程序定义了一个ReentrantLock对象,程序中实现额draw方法,进入方法开始执行后立即请求对ReentrantLock对象进行加锁,当执行完draw方法的取钱逻辑后,使用finally块确保释放锁。

5 死锁

  1. 当两个线程相互等待对方释放同步监视器时就会发生死锁
  2. 一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
  3. 死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下。

代码示例:

class A
{
	//这个方法的形参是一个引用变量即对象
	//需要创建引用变量的对象作为实参传给形参b
	//A对象调用foo()方法,进入foo()方法之前,线程对A对象加锁
	public synchronized void foo(B b) 
	{
		System.out.println(Thread.currentThread().getName()+"进入了A实例的foo()方法");
		try
		{
			//线程执行到这儿,暂停200ms,CPU切换到另一个线程
			//让B对象执行bar()方法
			Thread.sleep(200);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"企图调用B实例的last()方法");	
		//执行该方法之前,必须先对B对象加锁
		//但是此时副线程正保持着B对象的锁,所以主线程阻塞
		b.last();
	}
	public synchronized void last()
	{
		System.out.println("进入A类的last()方法内部");
	}
}
class B
{
	//这个方法的形参是一个引用变量对象
	//创建这个类的对象作为实参传给形参a
	//进入Bar()方法之前,该线程对B对象加锁
	public synchronized void bar(A a)   
	{
		System.out.println(Thread.currentThread().getName()+"进入B实例的bar()方法");
		try
		{
			//副线程暂停200ms,主线程会醒过来,继续执行
			Thread.sleep(200);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"企图调用A实例的last()方法");
		//副线程醒过来以后,向下执行到这
		//执行该方法之前,必须先对A对象加锁,
		//但是此时主线程没有释放A对象的锁
		a.last();		
	}
	public synchronized void last()
	{
		System.out.println("进入B类的last()方法内部");
	}
}
//线程执行体
public class DeadLock implements Runnable
{
		A a = new A();
		B b = new B();
		//主线程执行体
		public void init() 
		{
			Thread.currentThread().setName("主线程");
			//A对象调用foo()方法
			//A类中有foo()方法,可以创建A对象调用foo()方法
			a.foo(b);
			System.out.println("进入主线程之后");
		}
		//副线程执行体
		public void run()
		{
			Thread.currentThread().setName("副线程");	
			//B对象调用bar()方法
			b.bar(a);			
			System.out.println("进入副线程之后");		
		}
		public static void main(String[] args) 
		{
			DeadLock dl = new DeadLock();
			//以d1为target启动新线程
			//java线程体是由线程类的run()方法进行定义的,也是从该方法开始执行	
			//启动线程只会执行线程执行体run()方法
			new Thread(dl).start();
			dl.init();
		}
}

在这里插入图片描述
明确:

  1. 程序中设置了A类和B类,A类中有一个加了锁的foo()方法,B类中有一个加了锁的bar()方法
  2. A类的形参是一个引用变量,即B类的对象,B类的形参也是一个引用变量即A类的对象。
  3. 线程类中有两个方法,run()方法和init()方法,其中run()方法是副线程的执行体。init()方法是主线程的执行体(因为在主线程中调用了init()方法)
  4. 线程类是一个普通的类,这个类中一定要有run()方法,但是也可以有普通方法,线程的执行体是run()方法,调用start()方法时只会执行run()方法,run()方法执完毕,线程结束。至于其他的普通方法,启动线程的时候不会执行,只有其他类调用的时候才会执行。也就是说,无论这个方法在run()方法的上面还是下面都不会执行。
  5. 需要创建A类和B类的对象

分析执行过程:

  1. 由运行结果可以看出,init()方法先执行,在init()方法中A对象调用A实例的foo()方法
  2. 由于foo()方法是一个同步方法,所以在进入foo()方法之前,线程需要对A对象加锁
  3. 进入到foo()方法以后,执行到sleep()方法,主线程暂停200ms,CPU切换到执行另一个线程,即run()方法开始执行,在run()方法中B对象调用B实例的bar()方法
  4. 由于bar()方法是一个同步方法,在进入bar()方法之前,线程需要对B对象加锁
  5. 进入bar()方法之后,执行到sleep()方法,副线程暂停200ms
  6. 主线程醒过来继续向下执行,执行到b.last时,由于last()方法是一个同步方法,所以需要对B对象加锁,但是此时副线程正保持着B对象的锁,所以主线程阻塞
  7. 下面副线程也醒过来了,继续向下执行,执行到a.last()时,由于last()方法是一个同步方法,所以需要对A对象加锁,但是此时主线程没有释放A对象的锁。

结论:
出现了主线程保持着A对象的锁,等待对B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁,两个线程互相等待对方先释放,所以就出现了死锁。

6线程通信

多个线程都在处理同一个资源,但是处理的任务不一样

6.1 传统的线程通信

下面通过一个示例来解释编程通信问题:
假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者——系统要求存款者和取钱者不断地重复存款、取钱的动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。

  1. 为了实现这种功能,可以借助于Object类提供的wait()、notify()和notifyAll() 3个方法,这3个方法并不属于Thread类,而是属于Object类
  2. 但这3个方法必须由同步监视器对象来调用,这可分成以下两种情况:
    对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这3个方法。
    对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这3个方法。
  3. 三个方法的说明:
方法说明
wait()方法导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。
notify()方法唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程,选择时任意性的
notifyAll()方法唤醒在此同步监视器上等待的所有线程。

代码示例:

public class Account
{
	//封装账户编号账户余额
	private String accountNO;
	private double balance;
	//标识账户中是否已有存款的旗标
	//当flag=false时表明账户中没有存款,为true时表明有存款
	private boolean flag=false;
	public Account(){}
	public Account(String accoutNO,double balance)
	{
		this.accountNO = accountNO;
		this.balance = balance;
	}
	public String getAccountNO()
	{
		return accountNO;
	}
	public void setAccountNO(String accountNO)
	{
		this.accountNO = accountNO;
	}
	//因为账户余额不允许完结随便修改,所以只提供getter方法
	public double getBalance()
	{
		return balance;
	}
	/*
	 * 对取款者线程而言,当程序执行到draw()方法后
	 * 如果flag为假,说明账户中没有钱,程序就会盗用wait()方法阻塞线程
	 *否则会继续向下执行取钱操作
	 *当取钱操作完成以后就会把flag改为false
	 *程序调用notifyAll()方法来唤醒其他被阻塞的线程
	 *如果线程池中有取款者线程,那么取款者线程也会被唤醒
	 *但是执行到draw()方法的if判断语句时,该线程再次进入阻塞
	 */
	public synchronized void draw(double drawAmount)
	{
		
			//如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
			if(!flag)
			{
				try
				{
					wait();
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
			else
			{
				//执行取钱操作
				System.out.println(Thread.currentThread().getName()+"取钱:"+drawAmount);
				balance= balance-drawAmount;
				System.out.println("账户余额:"+balance);
				//将标识账户是否已有存款设置为false;
				flag = false;
				//唤所有线程
				notifyAll();				
			}								
	}
	/*
	 *对存款者线程而言,当程序进入deposit()方法后
	 * 如果flag为true,就说明账户中已将有钱,程序调用wait()方法阻塞,
	 * 否则程序就会继续向下执行存款操作
	 * 当存款操作执行完以后就会把flag改为true
	 * 然后调用notifyAll()方法来唤醒其他被阻塞的线程
	 * 如果系统中有存款者线程,存款者也会被唤醒
	 * 但是存款者执行到存款方法的if语句时会再次被阻塞
	 * 只有执行了draw()方法的取钱者线程后才可以向下执行
	 */
	
	public synchronized void deposit(double depositAmount)
	{
		//如果flag为true,表明账户中已经有人存钱进去,存钱方法阻塞
		if(flag)
		{
			try
			{
				wait();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		else
		{
			//执行存钱操作
			System.out.println(Thread.currentThread().getName()+"存钱:"+depositAmount);
			balance= balance+depositAmount;
			System.out.println("账户余额为:"+balance);
			//将账户是否已有存款的旗标设为true
			flag = true;
			//唤醒其他线程
			notifyAll();			
		}
	}
	//根据accountNo重写hashCode()方法
	public int hashCode()
	{
		return accountNO.hashCode();
	}
	//根据accountNo重写equals()方法
	public boolean equals(Object obj)
	{
		if(this==obj)
		{
			return true;
		}
		if(obj != null&&obj.getClass()== Account.class)
		{			
			Account target = (Account)obj;
			return target.getAccountNO().equals(accountNO);			
		}
		return false;
	}
}
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;
	}
	//重复100次取钱操作
	public void run()
	{
		for(int i=0;i<100;i++)
		{
			account.draw(drawAmount);
		}
	}
}
public class DepositThread extends Thread
{
	private Account account;
	//当前存款线程希望存的钱数
	private double depositAmount;
	public DepositThread(String name,Account account,double depositAmount)
	{
		super(name);
		this.account = account;
		this.depositAmount=depositAmount;		
	}
	//重复100次执行存钱操作
	public void run()
	{
		for(int i=0;i<100;i++)
		{
			account.deposit(depositAmount);
		}
	}
}
public class DrawTest
{
	public static void main(String[] args)
	{
		Account acc = new Account("123",0);
		new DrawThread("取钱者",acc,800).start();;
		new DepositThread("存钱者甲",acc,800).start();
		new DepositThread("存钱者乙",acc,800).start();
		new DepositThread("存钱者丙",acc,800).start();		
	}
}

在这里插入图片描述

  1. 只有当取钱者取钱后,存款者才可以存款;同理,只有等存款者存款后,取钱者线程才可以取钱。
  2. 程序最后被阻塞无法继续向下执行,这是因为3个存款者线程共有300次存款操作,但1个取钱者线程只有100次取钱操作,所以程序最后被阻塞!
  3. 阻塞并不是死锁,对于这种情况,取钱者线程已经执行结束,而存款者线程只是在等待其他线程来取钱而已,并不是等待其他线程释放同步监视器。

6.2使用condition控制线程通信

  1. 如果程序不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用wait()、notify()、notifyAll()方法进行线程通信了。(因为没有监视器对象)
  2. Condition将同步监视器方法(wait()、notify()和notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock对象组合使用
  3. Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能。(同步监事器在同步代码块中 为共享资源或者任意对象,在同步方法中为this,在Lock锁中为Condition)
  4. Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。
  5. Condition类中的三个方法:
方法说明
await()类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。
signal()唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择是任意性的。
signalAll()唤醒在此Lock对象上等待的所有线程。

代码示例:


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Account
{
	//封装账户编号账户余额
	private String accountNO;
	private double balance;
	private boolean flag=false;
	//显示定义Lock对象
	private final ReentrantLock lock = new ReentrantLock();
	//获得指定Lock对象对应的Condition
	private final Condition cond = lock.newCondition();
	public Account(){}
	public Account(String accoutNO,double balance)
	{
		this.accountNO = accountNO;
		this.balance = balance;
	}
	public String getAccountNO()
	{
		return accountNO;
	}
	public void setAccountNO(String accountNO)
	{
		this.accountNO = accountNO;
	}
	//因为账户余额不允许完结随便修改,所以只提供getter方法
	public double getBalance()
	{
		return balance;
	}

	public void draw(double drawAmount)
	{
		lock.lock();
		try
		{
			//如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
			if(!flag)
			{
				try
				{
					cond.await();
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
			else
			{
				//执行取钱操作
				System.out.println(Thread.currentThread().getName()+"取钱:"+drawAmount);
				balance= balance-drawAmount;
				System.out.println("账户余额:"+balance);
				//将标识账户是否已有存款设置为false;
				flag = false;
				//唤所有线程
				cond.signalAll();				
			}	
		}
		finally
		{
			lock.unlock();
		}										
	}
	
	public void deposit(double depositAmount)
	{
		lock.lock();
		try
		{
			//如果flag为true,表明账户中已经有人存钱进去,存钱方法阻塞
			if(flag)
			{
				try
				{
					cond.await();
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}		
			}
			else
			{
				//执行存钱操作
				System.out.println(Thread.currentThread().getName()+"存钱:"+depositAmount);
				balance= balance+depositAmount;
				System.out.println("账户余额为:"+balance);
				//将账户是否已有存款的旗标设为true
				flag = true;
				//唤醒其他线程
				cond.signal();			
			}
		}
		finally
		{
			lock.unlock();
		}
	}	
	//根据accountNo重写hashCode()方法
	public int hashCode()
	{
		return accountNO.hashCode();
	}
	//根据accountNo重写equals()方法
	public boolean equals(Object obj)
	{
		if(this==obj)
		{
			return true;
		}
		if(obj != null&&obj.getClass()== Account.class)
		{
			
			Account target = (Account)obj;
			return target.getAccountNO().equals(accountNO);			
		}
		return false;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我一直在流浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值