使用Runnable接口创建多线程

使用Runnable接口创建多线程

  • 适合多个相同的程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效的分离,较好地体现了面向对象的设计思想
  • 可以避免由于java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的之类放入多线程中,由于一个类不可能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable
  • 当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是实现了Runnable接口的类的实例。
  •  事实上,几乎所有多线程应用都可用Runnable接口方式。
下面这段代码是张老师的一个车票例子:
package blackhouse.thread;

public class ThreadDemo1 {
	public static void main(String[] args) {
		TestThread tt = new TestThread();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
		new Thread(tt).start();
	}
}

class TestThread implements Runnable {
	int tickets = 100;
	@Override
	
	public void run() {
			while (true) {			
		     synchronized (str) {
					if (tickets > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "  is saling ticket" + tickets--);
					}	
	      	}
		}
	}
}

多线程在实际中的应用

  • 网络聊天程序的收发
  • 表记录的中途取消
  • www服务器为每一个来访者都建立专属服务

线程同步问题

上面的票号可能出现负数

1.解决的办法是使用synchronized语句块——同步代码块

将上面的部分代码重写如下:

String str = new String("");
@Override
public void run() {
		while (true) {			
	     synchronized (str) {
				if (tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "  is saling ticket" + tickets--);
				}	
      	}
	}
}

线程同步的原理:

str这个对象也被称为监视器,并且多个线程的这个监视器应该是同一个。

所以这个String str = new String("");这一段不能放在run()方法中。

首先我们规定str这个对象的标志位为1时不可以执行下面的程序片,为0时可以。

synchronized 关键字把str对象的标志位置为0,第一个线程进入时得到锁旗标,并把对象标志置为0,其他线程再进入时进入线程池等待第一个线程归还其锁旗标,然后再执行代码片。

在这个程序中,同步会牺牲程序的性能。因为在一点时间点上,CPU只能执行一个线程,但是同步会消耗系统线程调度时间。


除了使用同步代码块外,我们还可以使用同步函数。

我们把run方法中的if语句块抽取出来构造一个sale()方法如下:

	public synchronized void sale() {
			if (tickets > 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()
						+ "  is saling ticket" + tickets--);
			}
		}

然后再在run()方法中调用这个方法也可以达到线程同步。

public void run() {
		while (true) {		
			sale();
		}
	}

现在我们来想一想这个问题,我们知道同步机制是通过对象的标志位来实现的,那么我们定义的sale()方法是哪个对象的标志位呢?我们不妨看看下面的这段代码:

package blackhouse.thread;

public class ThreadDemo1 {
	public static void main(String[] args) {
		TestThread tt = new TestThread();
		new Thread(tt).start();
		tt.str="method";
		new Thread(tt).start();
 	}
}

class TestThread implements Runnable {
	int tickets = 100;
	String str = new String("");

	@Override
	public void run() {
		if (str.equals("method")) {
			while (true) {
				sale();
			}
		} else {
			while (true) {
				synchronized (str) {
					if (tickets > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "  is saling ticket" + tickets--);
					}
				}
			}
		}

	}

	public synchronized void sale() {
		if (tickets > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()
					+ "  is saling ticket" + tickets--);
		}
	}
}

可能你会认为这个程序的线程是不同步的,我们来看看执行的结果

Thread-0  is saling ticket100
Thread-0  is saling ticket99
Thread-1  is saling ticket98
Thread-1  is saling ticket97
Thread-1  is saling ticket……
Thread-1  is saling ticket2
Thread-1  is saling ticket1

我们发现这两个线程是同步的。并不是一个执行同步代码块,一个执行同步方法,他们可能都是执行的同步代码块或都是执行的同步方法。如果他们都是执行的同步方法,那么他们的同步监视器都是使用的this对象,如果他们都是执行的同步代码块,那么他们都是使用的str对象作为监视器。那么他究竟是执行了那块synchronized 呢?

我们接下来在sale()方法中加入一条语句以示区别:

	System.out.print("sale()");
	System.out.println(Thread.currentThread().getName()+" is saling ticket "  +    tickets--);

结果输出如下:

sale()Thread-0  is saling ticket100
sale()Thread-0  is saling ticket……
sale()Thread-1  is saling ticket2
sale()Thread-1  is saling ticket1

那么就是说 if (str.equals("method")) 这条语句一开始就是成立的。要知道我们只是在第一个线程启动之后把str 改为method。

new Thread(tt).start();  语句只是说明这个线程出于就绪状态。main()线程可能还在往下面执行tt.str="method";

我们来将主线程暂停一下看看:

TestThread tt = new TestThread();
		new Thread(tt).start();
				try
				{
					Thread.sleep(1);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
	tt.str = "method";
	new Thread(tt).start();

输出结果:

Thread-0  is saling ticket100
sale()Thread-1  is saling ticket99
Thread-0  is saling ticket98
sale()Thread-1  is saling ticket97
Thread-0  is saling ticket35
sale()Thread-1  is saling ticket34
……………………………………
sale()Thread-1  is saling ticket6
Thread-0  is saling ticket5
sale()Thread-1  is saling ticket4
sale()Thread-1  is saling ticket3
Thread-0  is saling ticket2
sale()Thread-1  is saling ticket1
Thread-0  is saling ticket0

最后输出的是ticket0,说明线程不同步。和我们最初的推理相同,我们再来把同步代码块的str改为this,输出结果为:

Thread-0  is saling ticket100
Thread-0  is saling ticket99
sale()Thread-1  is saling ticket98
……………………
sale()Thread-1  is saling ticket90
Thread-0  is saling ticket89
Thread-0  is saling ticket88
……………………
Thread-0  is saling ticket3
Thread-0  is saling ticket2
Thread-0  is saling ticket1——————>最后为ticket1

我们发现又是同步的了,这样代码块与函数就同步了。说明sale()函数的监视器对象就是this 。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值