1.问题的提出
假设我们想要向数据库中的某个表中insert多条记录,insert操作可能是由多个线程并发来完成,那么此数据表中的主键ID是通过我们写的一个程序来完成数字的自增。我们来写一个类,用来取得下一个主键ID的值,并在insert操作中插入此主键ID。代码如下:
{
//取得下一次插入数据表中的主键ID
public static Integer getNextTableId()
{
//取得数据表中当前主键ID
//代码略
//返回下一此插入数据库表中的主键ID
return tableId++;
}
}
我们再写5个线程来模拟一下主键ID生成器,代码如下:
{
private boolean active;
private long millis;
private String myThreadId;
public ThreadSingleton( long ms, String threadId)
{
this .millis = ms;
this .active = true ;
this .myThreadId = threadId;
}
public void run()
{
while ( this .active)
{
Singleton.printOrder();
try
{
super .sleep( this .millis);
}
catch ( InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public class Client
{
public static void main( String [] args) throws InterruptedException
{
long delay = 100 ;
ThreadSingleton mt1 = new ThreadSingleton(delay, "001" );
mt1.start();
ThreadSingleton mt2 = new ThreadSingleton(delay, "002" );
mt2.start();
ThreadSingleton mt3 = new ThreadSingleton(delay, "003" );
mt3.start();
ThreadSingleton mt4 = new ThreadSingleton(delay, "004" );
mt4.start();
ThreadSingleton mt5 = new ThreadSingleton(delay, "005" );
mt5.start();
}
}
于是问题产生了,在开发级测试中,我们通过多线程并发取得需要插入的主键ID,并向此数据表中insert内容,几乎不会有问题,然而在企业级测试中,例如1000个人或更多的人同时对此数据表进行insert操作的时候,就会出现主键ID重复错误和顺序颠倒。模拟运行结果如下:
原因就是我们在通过 getNextTableId()方法取得下一次需要插入的主键ID时,由于1000乃至更多的线程同时并发, getNextTableId()方法取得TableID可能会出现相同的情况,也就是说可能存在某一个时刻,两个进程同时取得的TableID为100,那么此方法返回的结果同时为101,那么我们向数据库中插入的两条数据的主键ID同时为101,结果出错。
2.解决问题
我们想解决上面的问题,首先需要一个对象或方法,无论有多少个线程去访问,在同一个时刻,只能有一个对象或方法被使用。那么在java里面我们可以采用synchronized 关键字来保证一个类只允许存在一个实例,并提供一个可以访问它的全局访问点。
3.类图设计
4.代码编写
public class Singleton
{
//定义一个Singleton类的对象
private static Singleton instance = null ;
//进行主键ID自增模拟
private static int order = 1 ;
//定义一个生成唯一一个Singleton对象的方法
public static synchronized Singleton getInstance()
{
//如果instance为空
if (instance == null )
{
//new一个Singleton对象,保证只生成一个instance对象
instance = new Singleton();
}
//返回instance对象
return instance;
}
//模拟主键自增的测试方法
public static synchronized void printOrder()
{
//向控制台输出主键ID
System .out.println(order++);
}
}
由于给getInstance()方法和 printOrder()方法加上了synchronized 关键字,那么在多线程访问它们时,在同一时刻只会生成同一个Singleton实例。程序的运行结果如下:
运行结果正确,不在出现ID重复和顺序颠倒的错误。
5.总结——趣味见解
我们很多朋友到一家比较小的餐馆去喝酒,这家餐馆只有一个厨师。我们点了很多很多的菜,但是无论我们点多少,只能通过这一个厨师来掌勺。虽然有点慢,但是也没办法。