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线程同步的演示的主要内容,如果未能解决你的问题,请参考以下文章

多线程核心点

java多线程(线程安全,线程同步)

多线程 Thread 线程同步 synchronized

线程安全问题-synchronized(方法)火车票卖票代码演示

java并发线程锁技术的使用

java基础入门-多线程同步浅析-以银行转账为样例