首页 > Java > 高级篇 > 高新技术(九) JAVA多线程与并发库(一)
2014
07-20

高新技术(九) JAVA多线程与并发库(一)

本文的内容都是Java5之前就有的内容,Java5新增的内容请参考下一篇:JAVA多线程与并发库(二)。

1、创建线程的两种传统方式(继承Thread类,实现Runnable接口,此处从略)

2、定时器技术(Timer类、TimerTask类)

以定时炸弹为例,要实现定时炸弹的功能,则Timer类代表定时器,TimerTask代表炸弹,要实现炸弹的效果,就要复写TimerTask类中的run()方法。

第一种定时器:在指定延迟后执行指定的任务。下面的程序模拟了炸弹在程序开始后5秒爆炸:

new Timer().schedule(new TimerTask(){
			@Override
			public void run() {
				System.out.println("bombing……");
			}
		}, 5000);

第二种定时器:指定的延迟后开始进行重复的固定延迟执行指定的任务。有点拗口,看下面的程序,它模拟了连环爆炸:炸弹在程序开始后5秒爆炸,然后每隔2秒炸一次:

new Timer().schedule(new TimerTask(){
			@Override
			public void run() {
				System.out.println("bombing……");
			}
		}, 5000,2000);

注意:

①上面两种定时器的第二个参数 — 指定延迟,也可以用指定时间代替(Date类型),区别类似于相对时间和绝对时间的关系。

②利用上面两种定时器可作出不同的效果,如实现爆炸时间间隔为4秒,2秒,4秒,2秒,4秒……的爆炸,可以有以下两种实现方式:

实现方式一:定义一种炸弹(TimerTask),在爆炸之后“引燃”一个新的定时器,定时器的延迟根据爆炸的次数而定。

private static int count = 0;

class MyTimerTask extends TimerTask{
	@Override
	public void run() {
		count++;
		System.out.println("bombing");
		new Timer().schedule(new MyTimerTask(), 2000+2000*(count%2));

	}
}
new Timer().schedule(new MyTimerTask(), 2000);

实现方式二:定义两种炸弹(TimerTask),在爆炸后相互”引燃”

class MyTimerTask1 extends TimerTask{
	@Override
	public void run() {
		System.out.println("bombing");
		new Timer().schedule(new MyTimerTask2(), 2000);

	}
}
class MyTimerTask2 extends TimerTask{
	@Override
	public void run() {
		System.out.println("bombing");
		new Timer().schedule(new MyTimerTask1(), 4000);

	}
}
定义定时器如下:
new Timer().schedule(new MyTimerTask1(), 4000);

3、多线程的互斥与同步

线程互斥:是指散布在不同线程之间的若干程序片断,当某个线程运行其中一个程序片段时,其它线程就不能运行它们之中的任一程序片段,只能等到该线程运行完这个程序片段后才可以运行。

线程同步:是指散布在不同线程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。

显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!

概念总结:

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

3.1 JAVA中的线程互斥技术(synchronized、同步监视器)

在main方法中创建两个线程,在线程的方法体中均调用了Outter类中的print()方法打印一个字符串,代码如下:

final Outter out = new Outter();
new Thread(new Runnable(){
	public void run() {
		String name = "Flyne";
		while(true){
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			out.print(name);
		}
	}
}).start();

new Thread(new Runnable(){
	public void run() {
		String name = "wangjieyu";
		while(true){
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			out.print(name);
		}
	}
}).start();

Outer是一个使用static声明的内部类,代码如下:

static class Outter{
	public void print(String s){
		for(int i = 0 ;i < s.length();i++){
			System.out.print(s.charAt(i));
		}
		System.out.println();
	}
}

可以看到,main方法中定义的两个线程共用了Outter类中的一个代码片段,如果不进行互斥处理,则可看到如下的输出结果:

……
wangjieyu
Flyne
Flynwangjieyu
e
Flyne
wangjieyu
Flyne
……

这显然不是我们想要的,采用同步代码块进行改进print()方法:

public void print(String s){
	synchronized(this){
		for(int i = 0 ;i < s.length();i++){
			System.out.print(s.charAt(i));
		}
		System.out.println();
	}
}

则可以得到我们想要的输出,synchronized括号中的this称为同步监视器,另外还可以使用同步方法实现上述互斥效果,此时的同步监视器默认为this,即调用该同步方法所属对象:

public synchronized void print(String s){
	for(int i = 0 ;i < s.length();i++){
		System.out.print(s.charAt(i));
	}
	System.out.println();
}

特别要注意的是:静态同步方法所使用的监视器为所在类的Class对象。

3.2 JAVA中的线程同步技术(线程通信)

以一道面试题为例:

子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次,请写出程序。

分析:此题考查了线程互斥和同步的知识:

子线程循环10次的时候主线程不得执行,主线程循环100次的时候子线程不得执行,这是互斥。而子线程到主线程、主线程到子线程之间的切换则是属于线程之间的同步。

①在Test类中定义子线程和主线程,在子线程和主线程中均循环50次,分别调用Business类中的sub(i)方法和main(i)方法。sub()和main()方法用来实现循环10次和100次的逻辑。代码如下:

final Business business = new Business();

//子线程循环
new Thread(new Runnable(){
	public void run() {
		for(int i = 1; i <= 50;i++){
			business.sub(i);
		}
	}
}).start();

//主线程循环
for(int i = 1; i <= 50;i++){
	business.main(i);
}

②在Business中实现主线程和子线程互斥的逻辑(Business是被static修饰的内部类):

static class Business{
	public synchronized void sub(int i){
		for(int j = 1;j <= 10;j++){
			System.out.println("sub Thread sequence of " + j + "loop of " + i);
		}
	}

	public synchronized void main(int i){
		for(int j = 1;j <= 100;j++){
			System.out.println("main Thread sequence of " + j + "loop of " + i);
		}
	}
}

运行程序,可以发现子线程在打印的时候主线程没有进来……实现了子线程与主线程互斥的逻辑,但是主线程和子线程的执行顺序并没有按照你一次我一次的来,此时就需要用来线程通信技术来确保线程同步。

③在Business中增加主线程和子线程同步通信的逻辑(wait、notify)

static class Business{
	boolean subFlag = true;

	public synchronized void sub(int i){
		while(!subFlag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j = 1;j <= 10;j++){
			System.out.println("sub Thread sequence of " + j + "loop of " + i);
		}
		subFlag = false;
		this.notify();
	}

	public synchronized void main(int i){
		while(subFlag){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j = 1;j <= 100;j++){
			System.out.println("main Thread sequence of " + j + "loop of " + i);
		}
		subFlag = true;
		this.notify();
	}
}

心得:在本面试题的设计中是将sub()和main()方法中的逻辑放到一个Business类中,这样做方便了后面互斥和同步逻辑的编写。实际上,要用到共同数据(包括同步锁)的若干个方法应归在同一个类里面,这种设计正好体现了高内聚的思想,提升了程序的健壮性。


留下一个回复

你的email不会被公开。