前言
网上有多关于“JAVA多线程”的文章,但是对于不太熟悉多线程的编程人员来说人来说太抽象了,干脆我就直接用代码告诉大家何为多线程,不同的编写方式会带来怎样的后果以及如何正确的使用多线程。
示例程序功能描述
SynchronizedDemo.java业务功能:模拟火车售票场景。成员变量total表示火车表总量。showToal()一旦被执行就开始卖表(total-1),票没了(当total的值小于等于零)就停止卖票。
业务代码及执行结果
/**
* 模拟火车售票场景。成员变量total表示火车表总量。showToal()一旦被执行就开始卖表(total-1),票没了(当total的值小于等于零)就停止卖票
* @author 许井龙
*
*/
public class SynchronizedDemo implements Runnable {
/* 火车票总量*/
private static int total = 100;
/**
* 程序入口
* @param args
*/
public static void main(String[] args) {
//模拟八个售票窗口
Thread td1 = new Thread(new SynchronizedDemo());
Thread td2 = new Thread(new SynchronizedDemo());
Thread td3 = new Thread(new SynchronizedDemo());
Thread td4 = new Thread(new SynchronizedDemo());
Thread td5 = new Thread(new SynchronizedDemo());
Thread td6 = new Thread(new SynchronizedDemo());
Thread td7 = new Thread(new SynchronizedDemo());
Thread td8 = new Thread(new SynchronizedDemo());
//八个售票窗口同时售票;
td1.start();
td2.start();
td3.start();
td4.start();
td5.start();
td6.start();
td7.start();
td8.start();
}
@Override
/**
* 多线程执行入口
*/
public void run() {
//售票:稍后会有不同的具体实现
showToal();
}
}
如果showToal()代码如下,
public void showToal(){
//long sTime = System.currentTimeMillis();
while(true){
//当总量(total)当小于等于0的时候,就不能在继续减少了
if(total>0){
try {
//模拟线程阻塞1ms
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
total = total - 1;
System.out.println(Thread.currentThread().getName() + " 余票: " + total + "张");
}else{
//反之结束程序
break;
}
}
//System.out.println(Thread.currentThread().getName() + "耗时:" + (System.currentTimeMillis() - sTime)/1000);
}
执行结果如下,
....
Thread-4 余票: 5张
Thread-1 余票: 4张
Thread-0 余票: 3张
Thread-3 余票: 0张
Thread-5 余票: 1张
Thread-2 余票: 2张
Thread-7 余票: -1张
Thread-1 余票: -2张
Thread-6 余票: -3张
Thread-4 余票: -4张
余票居然出现了负数,显然是我们程序出现了漏洞。聪明的你马上就意识到,showTota()没有使用synchronized。因此你修改了showTota(),
public synchronized void showToal(){
//long sTime = System.currentTimeMillis();
while(true){
// 当总量(total)当小于等于0的时候,就不能在继续减少了
if(total>0){
try {
//模拟线程阻塞1ms
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
total = total - 1;
System.out.println(Thread.currentThread().getName() + " 余票: " + total + "张");
//反之结束程序
}else{
break;
}
}
//System.out.println(Thread.currentThread().getName() + "耗时:" + (System.currentTimeMillis() - sTime)/1000);
}
但是我们看看运行结果,
Thread-5 余票: 4张
Thread-2 余票: 4张
Thread-4 余票: 6张
Thread-6 余票: 6张
Thread-3 余票: 6张
Thread-0 余票: 2张
Thread-7 余票: 2张
Thread-4 余票: -3张
Thread-1 余票: -4张
Thread-2 余票: -4张
Thread-3 余票: -3张
Thread-5 余票: -3张
Thread-6 余票: -3张
Thread-0 余票: -6张
Thread-7 余票: -6张
似乎和你预期的不太一样哦。那么是synchronized没生效?还是我们使用的不对呢?现在我告诉你:showToal()需要声明为static。
public synchronized static void showToal(){
//long sTime = System.currentTimeMillis();
while(true){
if(total>0){
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
total = total - 1;
System.out.println(Thread.currentThread().getName() + " 余票: " + total + "张");
}else{
break;
}
}
//System.out.println(Thread.currentThread().getName() + "耗时:" + (System.currentTimeMillis() - sTime)/1000);
}
我们再来看看执行结果,
们再来看看执行结果,
Thread-0 total = 14
Thread-0 total = 13
Thread-0 total = 12
....
Thread-0 total = 11
Thread-0 total = 10
Thread-0 total = 9
Thread-0 total = 8
Thread-0 total = 7
Thread-0 total = 6
Thread-0 total = 5
Thread-0 total = 4
Thread-0 total = 3
Thread-0 total = 2
Thread-0 total = 1
Thread-0 total = 0
Thread-0耗时:0
Thread-7耗时:0
Thread-6耗时:0
Thread-5耗时:0
Thread-4耗时:0
Thread-3耗时:0
Thread-2耗时:0
Thread-1耗时:0
没有出现负数的余票,我们的目标达到了。那一定会问为什么我在非static方法使用synchronized不生效呢?这就涉及到了线程同步锁的问题。
线程同步锁
如果要了解线程同步锁,你需要对JVM内存及对象初始化有基本了解,我们把上述三个场景通过图来描述(我们简称SynchronizedDemo为SD),将会更直观,
第一个场景Total出现负值,因为total静态成员变量,在内存中只有唯一的一份,他可以被多个线程中的showTotal操作,因此出现了并发操作的问题。如图,
第二个场景,我们寄希望于是用synchronized解决上述问题,但是由于showTotal()非static,因此及时使用了synchronized,也是各自对象加各自的锁。达不到控制并发的目的。如图,
第三个场景,通过synchronized和static双重约束,我们发现total不再出现负数,因为showTotal被声明为static后,内存中也只有一份,因此其线程锁也是唯一的,这样就很好的控制了线程并发。
通过“线程同步锁”的讲解,我们发现控制并发是有先决条件的,
1、在同一个JVM内;
2、操作同一资源的线程,其线程锁要一致;
使用多线程需要注意的事项
实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
我们思考下,是否可以使用synchronized和static双重约束的方法控制数据库并发呢?
答案是否定的,通过三个场景在JVM中的剖析图,我们可以清楚的发现,synchronized和static双重约束的方法的上下文为单个JVM,超过了这个范围他就力不从心了。所以如果要控制数据库的并发,还要使用数据库的技术,正所谓术业有专攻。
许井龙 于腊月二十四