多线程同步

模拟卖票系统


public class Ticket implements Runnable
{
	//当前拥有的票数
	private  int num = 100;
	public void run()
	{
		while(true)
		{
				if(num>0)
				{
					try{Thread.sleep(10);}catch (InterruptedException e){}
					//输出卖票信息
					System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
				}
		}
	}
}

public class TicketDemo {
	
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();//创建一个线程任务对象。
		
		//创建4个线程同时卖票
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		//启动线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

运行结果:
多线程同步
从运行结果,我们就可以看出我们4个售票窗口同时卖出了1号票,这显然是不合逻辑的,其实这个问题就是线程同步问题。不同的线程都对同一个数据进了操作这就容易导致数据错乱的问题,也就是线程不同步。

解决思路:将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

解决线程同步的两种典型法案

1.通过锁(Lock)对象的方式解决线程安全问题

在java中锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但有的锁可以允许多个线程并发访问共享资源,比如读写锁,后面我们会分析)。在Lock接口出现之前,java程序是靠synchronized关键字实现锁功能的,而JAVA SE5.0之后并发包中新增了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁,缺点就是缺少像synchronized那样隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class Ticket implements Runnable {
    //创建锁对象
    private Lock ticketLock = new ReentrantLock();
    //当前拥有的票数
    private int num = 100;
 
    public void run() {
        while (true) {
            try {
                ticketLock.lock();//获取锁
                if (num > 0) {
                    Thread.sleep(10);//输出卖票信息System.out.println(Thread.currentThread().getName()+".....sale...."+num--); }
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();//出现异常就中断
            } finally {
                ticketLock.unlock();//释放锁
            }
        }
    }
}

2.通过synchronized关键字的方式解决线程安全问题

从JAVA SE1.0开始,java中的每一个对象都有一个内部锁,如果一个方法使用synchronized关键字进行声明,那么这个对象将保护整个方法,也就是说调用该方法线程必须获得内部的对象锁。

class Ticket implements Runnable
{
	private  int num = 100;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(num>0)
				{
					try{Thread.sleep(10);}catch (InterruptedException e){}
					
					System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
				}
			}
		}
	}
}