本文章在:2018年06月08日 11:53:47 ??发表于:CSDN
2018年11月17日 迁移至新博客服务器。?

????????作为一个刚毕业的新人,前段时间在公司开发一个人员定位项目,其中有一部分功能为:判断人员是否在某一区域,如果是则触发警报,并将信息写入数据库。否则判断人员所在位置是否发生改变,是则将新数据写入到数据库中,否则不管。

????????那么这里就有两个问题了:

????? ? 1、警报器使用继电器板控制的,继电器板采用网口连接到系统中。定位系统返回的数据是多线程高并发的,现场测试中发现当多线程同时访问继电器板时,会引起继电器板抛出拒绝连接异常。所以我需要对多线程进行处理;

????? ? 2、插入到数据库当中的人员位置信息上下两行不能有重复值,即上一条数据与下一条数据中的位置信息不能一样。因为需求说明在人员位置没有发生改变时不允许写入新数据。

????? ? 对于问题一,当时脑子里很直观的想法就是:我把操作继电器板的方法用同步块包裹起来,让多线程排排队,一次仅允许一条线程访问不就行了?因此,就用到了synchronized。

public?static?synchronized?void?triggerAlarm(Info?info)?{		//?do?something
	}

????? ? 使用synchronized把触发继电器板的方法锁起来,这样就能保证一次只有一条线程能够执行该方法了。这问题也就迎刃而解了。(这里还是想顺带吐槽一下那个继电器板的质量是真的差啊!!)

????? ? 对于问题二,因为定位产生的数据并发量确实超级高,多条线程同时执行:判断 —— 插入数据库 的操作,因此导致了大量重复数据被写入数据库。所以这里也应该用锁来控制。

????? ? 为了能有直观的认识,这里有一个小demo:

public?class?Test?{????????static?int?num?=?0;	public?static?void?main(String[]?args)?{
		Test?t?=?new?Test();		for?(int?i?=?0;?i?	}

}	private?static?void?add()?{
	num?+=?1;
	System.out.print(num+"?");
}

}

<span class="cke_reset cke_widget_drag_handler_container" style="background: url("https://csdnimg.cn/release/blog_editor_html/release1.3.1/ckeditor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">


????? ? 这是没有对线程做任何限制的写法,期望输出结果是:1 2 3 4 5 6 7 8 9 10,但是测试输出结果千奇百怪。很好理解,当多个线程同时对同一对象进行操作时,并不能确定哪个线程先操作完毕,因此,当执行add()方法时,num的值其实是不可预测的,所以每次打印出来的值也是不一样的。



public?class?Test?{	static?int?num?=?0;	public?static?void?main(String[]?args)?{
Test?t?=?new?Test(); for?(int?i?=?0;?i?<?10;?i++)?{ new?Thread(new?Runnable()?{ @Override
public?void?run()?{
?syncAdd();
}
}).start();

	}

}	private?static?void?add()?{
	num?+=?1;
	System.out.print(num?+?"?");
}	private?static?synchronized?void?syncAdd()?{
	num?+=?1;
	System.out.print(num?+?"?");
}

}

<span class="cke_reset cke_widget_drag_handler_container" style="background: url("https://csdnimg.cn/release/blog_editor_html/release1.3.1/ckeditor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">

????? ? 当我们使用了锁之后,运行程序,打印出来的结果和预期结果一样,为:1 2 3 4 5 6 7 8 9 10 。当使用锁控制的时候,线程操作将会变得有序。

? ? ? ?这里还有一个公平锁和非公平锁的概念。多线程就像是很多人一起在食堂打饭,当你不进行锁控制的时候,所有线程就一起蜂拥而至去抢饭,非常的无序而且混乱;而使用非公平锁控制的时候,所有线程就开始“排队”了,一次只有一个线程能“从食堂大妈哪里打到饭”,其余的线程都会等待该线程处理完成后再一起抢那个“打饭的机会”;使用公平锁控制的时候,所有线程就变成了一群乖孩子,会井然有序地排队。1号同学先来的,先打饭,然后2号同学接着。

????? ? 一句话概括就是:非公平锁是所有线程一起哄抢对象使用权;公平锁是按线程顺序,排队使用对象。

????? ? 同样,这里也附上一个demo:

import?java.util.concurrent.locks.Lock;import?java.util.concurrent.locks.ReentrantLock;public?class?Test?{	static?Lock?lock?=?new?ReentrantLock(true);	/**
??synchronized锁
?

?*/
static?class?SyncLocker?implements?Runnable?{ @Override
public?void?run()?{ for?(int?i?=?0;?i?<?5;?i++)?{
System.out.println(Thread.currentThread().getName()?+?"获得锁");
}
}

}	/**
?*?ReentrantLock锁
?*
?*/
static?class?ReentrantLocker?implements?Runnable?{		@Override
	public?void?run()?{			for?(int?i?=?0;?i?<?5;?i++)?{
			lock.lock();//?上锁
			try?{
				System.out.println(Thread.currentThread().getName()?+?"获得锁");
			}?finally?{
				lock.unlock();//?解锁
			}
		}
	}

}

}

<span class="cke_reset cke_widget_drag_handler_container" style="background: url("https://csdnimg.cn/release/blog_editor_html/release1.3.1/ckeditor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">

????????这里给出了两个不同锁类型的内部类,SyncLocker是使用synchronized的非公平锁,而使用ReentrantLock(true)初始化来的ReentrantLocker是公平锁。

????? ? 下面我们在main方法中实例化两个SyncLocker对象,看看输出结果。

public?static?void?main(String[]?args)?{
SyncLocker?syncLocker?=?new?SyncLocker();
Thread?th1?=?new?Thread(syncLocker);
Thread?th2?=?new?Thread(syncLocker); /?运行两个线程测试?/
th1.start();
th2.start();
}

<span class="cke_reset cke_widget_drag_handler_container" style="background: url("https://csdnimg.cn/release/blog_editor_html/release1.3.1/ckeditor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">

????? ? 运行结果为:

20180608114244875.png

????? ? 且每次运行打印出的结果不同。该方式运行出来的结果的特征是同一线程可能会连续执行多次。

????????因为在第一条线程打印出结果后,所有线程都在同时抢第二个输出的资格,因此哪个子线程能第二个打印出结果并不确定,所以就导致了乱序输出的结果,且每次运行结果均不一样。


????????下面我们在main方法中实例化两个ReentrantLocker对象,再看看输出结果。

public?static?void?main(String[]?args)?{
ReentrantLocker?reentrantLock?=?new?ReentrantLocker();
Thread?th1?=?new?Thread(reentrantLock);
Thread?th2?=?new?Thread(reentrantLock); /?运行两个线程测试?/
th1.start();
th2.start();
}

<span class="cke_reset cke_widget_drag_handler_container" style="background: url("https://csdnimg.cn/release/blog_editor_html/release1.3.1/ckeditor/plugins/widget/images/handle.png") rgba(220, 220, 220, 0.5); top: -15px; left: 0px; display: block;">

????? ? 运行结果为:

20180608114544788.png

????? ? 或

20180608114657485.png

????? ? 输出特征为两条线程交替执行,并不会存在同一线程执行多次的情况。由此可见,公平锁能够让线程们有效地“排队”。


????? ? 回到正题,在公司人员定位项目中,因为考虑到对插入数据的时间准确性有要求,所以在众多线程中也只能要“最先产生的一条”,因此在这里我是用的是公平锁。

????? ? 最终代码测试通过,也无重复数据插入数据库了。



摸鱼癌晚期患者