也许有人会问 “既然用了多线程,为什么还要同步?还要顺序执行呢?”。这个看似脑残的问题其实并非我们想象的那么简单。
假设(这里只是一个假设,类似下面的情形有很多,这里不一一阐述)当你执行定时任务的时候,你需要执行ScheduledExecutorService的一个scheduleAtFixedRate方法的时候,那么你需要给这个方法传入一个线程A的实例。如果这个线程A是一个大的业务,这个大业务里边分多个步骤。假设第一个步骤需要用到多线程,而且业务需求是必须执行完第一步才能执行下面的操作,那么惨了。因为多线程不等第一步运行完毕就有可能执行第二步的操作,那我们该怎么办?
我们可以把每个步骤看做是一个线程,执行完一个,再执行下一个,那么该如何让这些线程同步并且顺序地执行呢?
(在网上找了好多资料,有说用join的,但是我没弄出来。于是就自己硬搞了,希望有高手能用join解决一下,谢过!)
第一步是让线程同步,第二部是让线程有顺序。
同步:我们可以用synchronized来解决。
java线程同步原理: java会为每个object对象分配一个monitor,当某个对象的同步方法(synchronizedmethods )被多个线程调用时,该对象的monitor将负责处理这些访问的并发独占要求。
当一个线程调用一个对象的同步方法时,JVM会检查该对象的monitor。如果monitor没有被占用,那么这个线程就得到了monitor的占有权,可以继续执行该对象的同步方法;如果monitor被其他线程所占用,那么该线程将被挂起,直到monitor被释放。
当线程退出同步方法调用时,该线程会释放monitor,这将允许其他等待的线程获得monitor以使对同步方法的调用执行下去。就像下面这样:
public void test() {
synchronized (this) {
//做一些事
//这里只会有一个线程来调用该方法,因为只有一个this对象作为资源分配给该线程
}
}
顺序:我们可以用List来解决,因为它是有序的。我们只需要将要执行的线程放入到List中不就好解决了吗
上代码:
/**
* 让多个线程同步顺序执行的线程管理器
* @author bianrx
* @date 2012.1.18
* SynchronizedRunMultiThread
*/
public class SyncManager {
/**
* 待执行的线程集合,注意这里必须是Runnable接口类型,下面会解释
*/
private List runnableList;
public SyncManager(){}
public SyncManager(List runnableList) {
this.runnableList = runnableList;
}
public void setRunnable(List runnableList) {
this.runnableList = runnableList;
}
public void run() {
//遍历代执行的线程集合
for(Runnable runnable: runnableList) {
runThread(runnable);
}
}
/**
* 按顺序同步执行多个线程
* @author bianrx
* @date 2012.1.18
* @param runnable
*/
private void runThread(Runnable runnable) {
synchronized (this) {
runnable.run();//这里需要注意的是:必须调用run方法,因为如果你调用了start方法,线程只会向JVM请求资源,但是未必就执行其中的run。
//这个方法是同步的,所以当前只有一个线程占用了this对象。
}
}
}
测试代码:有两个要执行的线程
public class Thread1 extends Thread {
@Override
public void run() {
System.out.println("运行线程1");
}
}
public class Thread2 extends Thread {
@Override
public void run() {
System.out.println("运行线程2");
}
}
//主函数测试
public class MainTest {
/**
* @param args
* @throws ParseException
* @throws InterruptedException
*/
public static void main(String[] args) throws ParseException, InterruptedException {
List runnableList = new ArrayList();
runnableList.add(new Thread1());
runnableList.add(new Thread2());
while(true) {
Thread.sleep(1000);
new SyncManager(runnableList).run();
System.out.println("---------------");
}
}
在这里补充一下:我做错了。情况是这样的,我要定时执行一个任务,而我查到的java定时任务的api是这样的:
executorService.scheduleAtFixedRate(srtm, initialDelay, period, unit);
第一个参数是一个runnable接口。这个任务分两个步骤,我为了方便维护程序,就分别写到了两个线程里,实际上算不上并发。但是我的第一步里确实是要有并发的(多线程),我上面写的方法只能保证在主线程里不嵌套子线程的情况下才好用,但是如果有嵌套该怎么办呢?这些是我遇到的情况,大家不必关注。也就是说想做到在主线程中等待多线程都执行完毕了再执行后续代码该怎么办呢?
答案是用join。join方法大家可以查下api,它的意思是等待当前线程执行完后执行完毕才执行其他线程。也就是说如果一个类中有这样一个代码段:
thread1.start();
thread2.start();
thread1.join();
thread2.join();
do something 1;
do something 2;
那么这段代码会等待两个线程执行完毕后再执行 do something 1 和 do something 2,注意:必须先启动所有线程,再join。如果启动一个就join一个,结果是什么?对,那就会是等待thread1执行完再执行thread2,再执行后续代码。
我就是想告诉大家,真正在做项目时是基本不会遇到红色字体那种情况的。但是经常会遇到让线程同步或者是蓝色代码那种问题。很感谢各位能给出宝贵的点评。
4
顶
3
踩
分享到:
2012-01-18 15:14
浏览 27419
评论
6 楼
DEMONU
2017-04-25
使用List方式是可以的,不过你这实现不够优雅。可以看Executor这个类
5 楼
rokuan
2012-01-19
ZaneLee007 写道
aoingl 写道
SyncManager 需要实现 Runnable, 或者继承 Thread 吧。 SyncManager.run() 需要在一个 Runnable.run() 下运行才能与主线程分开。
开启新的线程不是run方法做的,是Thread的native方法start0做的,start0在底层请求JVM分配资源,然后start0会调用你重写的Runnable实例的run方法。Runnable是一个接口,这个接口只有一个方法run,Thread类就实现了这个接口。
所以Java里创建线程都必须要实现这个接口,也就是他们都是Runnable实例。只是Runnable实例并不会启动新的线程,必须调用Thread的start方法,才会启动新的线程。可以这么写:new Thread(Runnable).start()。
嗯,那我的理解应该是对的
4 楼
ZaneLee007
2012-01-19
aoingl 写道
SyncManager 需要实现 Runnable, 或者继承 Thread 吧。 SyncManager.run() 需要在一个 Runnable.run() 下运行才能与主线程分开。
开启新的线程不是run方法做的,是Thread的native方法start0做的,start0在底层请求JVM分配资源,然后start0会调用你重写的Runnable实例的run方法。Runnable是一个接口,这个接口只有一个方法run,Thread类就实现了这个接口。
所以Java里创建线程都必须要实现这个接口,也就是他们都是Runnable实例。只是Runnable实例并不会启动新的线程,必须调用Thread的start方法,才会启动新的线程。可以这么写:new Thread(Runnable).start()。
3 楼
ZaneLee007
2012-01-19
rokuan 写道
调用run方法应该没有新建线程,按我的理解,同步就没有意义了,这个产生多线程了吗?求博主答疑啊
run没有启新的线程,start方法才会调用Thread的native的start0方法,start0会调用run方法,开启新的线程,博主这么做,不是多线程顺序执行,而是把业务阻塞在主线程里。请打印:Thread.currentThread().getId(),会发现主线程ID和run里的ID是相同的;如果调用start方法,就不会相同了。
2 楼
aoingl
2012-01-19
SyncManager 需要实现 Runnable, 或者继承 Thread 吧。 SyncManager.run() 需要在一个 Runnable.run() 下运行才能与主线程分开。
1 楼
rokuan
2012-01-18
调用run方法应该没有新建线程,按我的理解,同步就没有意义了,这个产生多线程了吗?求博主答疑啊