多线程基础学习二:Runnable基础学习
前几天学习了Thread的基础知识,今天开始学习一下Runnable的基础知识。
Runnable与Thread的关系
Thread实现了Runnable,也就是说Thread是Runnable的一个实现类,并且扩展了很多有用的方法。
源码:
public
class Thread implements Runnable {
`````
}
可以确认的是,如果Runnable能实现一个功能,那Thread也能实现,如果Runnable无法实现二代功能,Thread可能能实现。
Runnable的使用
一个简单的例子:
public class RunnableTest {
public static void main (String[] args) {
Thread thread = new Thread(new TestRunnable());
thread.setDaemon(true);
thread.start();
SleepUtil.sleep(2000);
}
static class TestRunnable implements Runnable {
@Override
public void run () {
System.out.println("这是一个简单的Runnable");
}
}
}
从这里看到Runnable和Thread的区别,
– Thread需要继承,Runnable需要实现;
– Runnable没有start方法,没有办法启动线程,需要把实现类交给Thread处理。(当然如果实现Runnable时,像Thread一样,创建一个功能和Thread 的start方法一样的方法,也是可以的)
Runnable与Thread的区别
- Thread需要继承,Runnable需要实现
Thread本身就是Runnable的实现类,两种实现方式的优缺点是有继承和实现这两个方式带来,Runnable能实习的功能,Thread都能实现,甚至能提供更多有用的方法
– 继承只能继承一个,实现可以实现多个接口;
这是两种实现方式造成的区别之一,不过根据我在项目中看到和自己使用的经历来看,基本没有遇到需要实现多个接口的场景,所以说这个区别大多数情况下,没什么影响。
以上是我认为的区别
下面是在网上查看博客学习时,看到的区别
– Runnable相比Thread,代码被多个线程共享,代码和数据独立
我不知道这种说法对不对,博客上很多人这么写,博客评论里也有很多人说这是错的,所以写代码测一下。
就用很多博客里写的那个卖票的例子。
Runnable:
public class RunnableTest {
public static void main (String[] args) {
Runnable test = new TestRunnable();
Thread first = new Thread(test, "first");
first.setDaemon(true);
first.start();
Thread second = new Thread(test, "second");
second.setDaemon(true);
second.start();
SleepUtil.sleep(50000);
}
static class TestRunnable implements Runnable {
int i = 5;
@Override
public void run () {
for (int j = 0; j < 10; j++) {
if (i > 0) {
//SleepUtil.sleep(1000);
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "卖出了一张票" + i--);
}
}
}
}
}
这是网上很多博客用来说明这个区别时,所举的例子。
运行结果:
线程first卖出了一张票5
线程second卖出了一张票4
线程first卖出了一张票3
线程second卖出了一张票2
线程first卖出了一张票1
只从结果上看,这个是对的,5张票被两个线程卖掉了,没多卖也没少卖。
多次运行(大概90多次)后出现其它结果:
第一种:
线程first卖出了一张票5
线程first卖出了一张票4
线程first卖出了一张票3
线程first卖出了一张票2
线程first卖出了一张票1
这是一个线程卖了5张票,另外一个线程无票可卖。
第二种(出现8-10次):
线程second卖出了一张票5
线程first卖出了一张票5
线程second卖出了一张票4
线程first卖出了一张票3
线程second卖出了一张票2
线程first卖出了一张票1
这是两个线程都卖了同一张票(票5)
第三种(出现2次):
线程second卖出了一张票5
线程first卖出了一张票4
线程second卖出了一张票3
线程first卖出了一张票2
线程second卖出了一张票1
线程first卖出了一张票0
有一个线程卖了一张不存在的票。
显而易见,大多情况正确,部分情况错误,i 的值在多线程的情况下不能保证原子性。如果加了同步关键字,执行结果有事什么样子呢?
代码修改:
static class TestRunnable implements Runnable {
int i = 5;
@Override
public synchronized void run () {
for (int j = 0; j < 10; j++) {
if (i > 0) {
//SleepUtil.sleep(1000);
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "卖出了一张票" + i--);
}
}
}
}
测试结果(执行40多次):
第一种(多数):
线程first卖出了一张票5
线程first卖出了一张票4
线程first卖出了一张票3
线程first卖出了一张票2
线程first卖出了一张票1
第二种(少数):
线程second卖出了一张票5
线程second卖出了一张票4
线程second卖出了一张票3
线程second卖出了一张票2
线程second卖出了一张票1
可以看到,没有出现两个线程一起卖的情况了,只有一个线程把所有的票卖完了。
这个可以这样解释,for遍历了10次,总共有5张票,又有关键字锁定了Runnable实例,导致只有会有一个线程把票卖完。
如果把for修改,让它在同步的情况下,不能把所有的票卖完,估计结果又不一样。
修改:
public synchronized void run () {
for (int j = 0; j < 3; j++) {
if (i > 0) {
//SleepUtil.sleep(1000);
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "卖出了一张票" + i--);
}
}
}
执行结果 (执行15次):
第一种:
线程first卖出了一张票5
线程first卖出了一张票4
线程first卖出了一张票3
线程second卖出了一张票2
线程second卖出了一张票1
第二种:
线程second卖出了一张票5
线程second卖出了一张票4
线程second卖出了一张票3
线程first卖出了一张票2
线程first卖出了一张票1
可以看到,只有两种结果,一个线程卖三张,另外一个线程卖两张。
这也很容易理解,同步的情况下,遍历三次,一个线程最多卖三张,线程执行结束,剩下两张有另外一个想成执行。
根据以上测试可知,为了能两个线程一起卖,就不能加synchronized或者在添加同步的情况控制遍历次数;没有添加同步的情况,这样的写法是有问题的,不能保证数据的原子性;添加同步的情况下,如果遍历的数量大于票数,使用多线程也是没有意义的(只会有一个线程卖完票);添加同步的情况下,如果遍历的数量小与票数,有可能票卖不完。
以上是往里例子的测试总结。
实际上Thread也可以做到多个线程处理同一个资源,一下是用Thread写的,
代码:
public class ThreadTest {
private static AtomicLong i = new AtomicLong(10);
public static void main (String[] args) {
Thread first = new TestThread();
first.setDaemon(true);
first.start();
Thread second = new TestThread();
second.setDaemon(true);
second.start();
SleepUtil.sleep(50000);
}
static class TestThread extends Thread {
@Override
public void run () {
while (i.get() > 0) {
//System.out.println(i.get());
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "卖出了一张票" + i);
i.decrementAndGet();
}
}
}
}
执行结果:
第一种:
线程Thread-0卖出了一张票10
线程Thread-0卖出了一张票9
线程Thread-0卖出了一张票8
线程Thread-0卖出了一张票7
线程Thread-0卖出了一张票6
线程Thread-0卖出了一张票5
线程Thread-0卖出了一张票4
线程Thread-0卖出了一张票3
线程Thread-0卖出了一张票2
线程Thread-0卖出了一张票1
第二种:
线程Thread-0卖出了一张票10
线程Thread-0卖出了一张票9
线程Thread-0卖出了一张票8
线程Thread-1卖出了一张票8
线程Thread-0卖出了一张票7
线程Thread-1卖出了一张票6
线程Thread-0卖出了一张票5
线程Thread-1卖出了一张票4
线程Thread-0卖出了一张票3
线程Thread-1卖出了一张票2
线程Thread-0卖出了一张票1
可能还有全是线程-1的,没有充分测试。
这样写也实现了代码复用,数据共享处理,也没有出现多卖的情况。
总而言之,网上很多博客里总结这个【代码被多个线程共享,代码和数据独立】区别,我认为不存在这种区别,网上很多博客里的demo,参考即可,对于我目前项目中遇到的使用场景,没什么太大的意义。
还没有评论,来说两句吧...