java线程同步的演示
Posted 写不完作业还要玩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程同步的演示相关的知识,希望对你有一定的参考价值。
线程同步
1.问题引出:
对于同一个银行账户在多个地点同一时间的取钱模拟
具体代码我们看下面写的:
account账户类:
package thread;
public class Account
{
//一个账户名和一个余额
private String acNo;
private int balance;
Account(String ac,int bal)
{
acNo=ac;
balance=bal;
}
//set方法和get方法
public void setAccountNo(String no)
{
acNo=no;
}
public void setBalance(int bal)
{
balance=bal;
}
public String getAccoutNo()
{
return acNo;
}
public int getBalance()
{
return balance;
}
//两个使用覆盖方法
@Override
public int hashCode() {
return acNo.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj==this)
return true;
if(obj!=null&&obj.getClass()==Account.class)
{
Account target=(Account)obj;
return target.equals(acNo);
}
return false;
}
}
取钱类:
package thread;
public class DrawAccount extends Thread
{
// 用户的账户
private Account acn;
private double drawMoney;
public DrawAccount(String name,Account ac,double reduce)
{
super(name);
acn=ac;
drawMoney=reduce;
}
public void run()
{
if(acn.getBalance()>drawMoney)
{
//取出钞票
System.out.println(this.getName()+" 取钱成功,取出了: "+drawMoney);
//增加错误率
/*try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//减去钞票
acn.setBalance((int) (acn.getBalance()-drawMoney));
//打印余额
System.out.println("余额为: "+acn.getBalance());
}
else {
System.out.println("取钱失败,余额是 "+acn.getBalance());
}
}
}
ps:值得注意的是那段注释的代码,这段代码可以增加失误率
测试类:
package thread;
public class DrawTest
{
public static void main(String[]__)
{
Account he=new Account("123", 1600);
new DrawAccount("取钱机器1", he, 1000).start();
new DrawAccount("取钱机器2", he, 1000).start();
}
}
多次运行之后会看到下面 情况:
我们这里稍微做一下解释:
- 如果我们之前了解过线程同步的问题,应该会对这两个概念比较明确:原子性和可见性,对于这里的取钱操作,判断,扣费这两个动作不是原子操作,因此存在当一个线程已经判断成功之后,CPU的使用权被另外一个线程夺取,进行判断,这个时候账户还没有扣费,所以就造成了出现账户余额为负数的情况。
2.同步做法
为了解决上述问题,java的多线程使用了同步监视器来解决,同步代码块的语法格式:
synchronized(obj)
{
//代码块
}
括号中的obj就是同步监视器,上面代码的含义就是在线程开始执行之前,必须现获得同步监视器的锁定,任何一个线程在运行同步代码块前一定要先获得锁,流程:"加锁"->"修改锁"->"释放锁"
obj这个锁一般使用共享的变量来当锁,当程序运行到synchronize的时候,会先判断里面的obj锁是否已经被别人获取。
另外还有同步方法
public synchronized void run
{
//代码块
}
这个时候相当于使用this作为obj来充当锁
可变类的线程安全是通过降低效率来获得的,为了减少负面影响:
- 不要对所有的资源进行同步,只需要对于竞争资源(共享资源加锁)
- 可变类有两种运行环境:单线程和多线程的时候,应该提供两种版本的可变类,单线程效率性和多线程安全性。
3.关于锁的释放
下面这几种情况下,会释放锁:
- 当前线程的同步方法,同步代码块执行结束,当前线程释放锁
- 遇到break,return语句释放锁
- 遇到了未处理的ERROR和EXCEPTION就会释放锁
- 线程执行同步代码块的时候,程序执行了同步监视器对象的wait()方法,则当前的线程暂停,并释放同步监视器(ps:wait()方法是继承object类的方法)
下面这几种情况,不会释放锁,可能造成死锁:
- 程序调用sleep()方法和yield()方法不会释放锁
- 线程调用这个线程的suspend()方法将该线程挂起,这个线程不会释放同步监视器
补充昨天学了就忘记了join()方法:阻塞调用线程的线程,等待被调用的线程执行结束之后,调用线程的线程才会继续执行,底层使用了wait()方法?
java多线程(线程安全,线程同步)
一.线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
通过电影院卖票,演示线程的安全问题:
模拟电影院的卖票过程。本次电影的座位共100个(本场电影只能卖100张票)。模拟电影院的售票窗口,实现多个窗口同时卖这场电影票(多个窗口一起卖这100张票)
需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
//测试类 public class ThreadDemo { public static void main(String[] args) { //创建票对象 Ticket ticket = new Ticket(); //创建3个窗口 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); t1.start(); t2.start(); t3.start(); } }
//模拟票 public class Ticket implements Runnable { //共100票 int ticket = 100; public void run() { //模拟卖票 while(true){ if (ticket > 0) { //模拟选坐的操作 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } }
运行结果发现:
①票出现了重复的票
②错误的票 0、-1
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
二.线程同步(线程安全处理Synchronized)
java中提供了线程同步机制,它能够解决上述的线程安全问题。
线程同步的方式有两种:
①同步代码块
②同步方法
1.同步代码块
//同步代码块: 在代码块声明上 加上synchronized synchronized (锁对象) { 可能会产生线程安全问题的代码 }
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
//使用同步代码块,对电影院卖票案例中Ticket类进行如下代码修改: public class Ticket implements Runnable { //共100票 int ticket = 100; //定义锁对象 Object lock = new Object(); @Override public void run() { //模拟卖票 while(true){ //同步代码块 synchronized (lock){ if (ticket > 0) { //模拟电影选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } } } }
2.同步方法
//同步方法:在方法声明上加上synchronized public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步方法中的锁对象是 this
//使用同步方法,对电影院卖票案例中Ticket类进行如下代码修改: public class Ticket implements Runnable { //共100票 int ticket = 100; //定义锁对象 Object lock = new Object(); @Override public void run() { //模拟卖票 while(true){ //同步方法 method(); } } //同步方法,锁对象this public synchronized void method(){ if (ticket > 0) { //模拟选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--); } } }
//静态同步方法: 在方法声明上加上static synchronized public static synchronized void method(){ 可能会产生线程安全问题的代码 }
静态同步方法中的锁对象是 类名.class
以上是关于java线程同步的演示的主要内容,如果未能解决你的问题,请参考以下文章