使用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 。