转自:https://www.cnblogs.com/skywang12345/p/3479063.html (含部分修改)
概要
本章,我们学习“常用的实现多线程的2种方式”:Thread 和 Runnable。(比如还有Callable来实现)
之所以说是常用的,是因为还可以通过java.util.concurrent包中的线程池来实现多线程。关于线程池的内容,我们以后会详细介绍;现在,先对Thread和Runnable进行了解。本章内容包括:
- Thread和Runnable的简介
- Thread和Runnable的异同点
- Thread和Runnable的多线程的示例
一. Thread和Runnable简介
Runnable 是一个接口,该接口中只包含了一个run()方法。它的定义如下:
public interface Runnable { public abstract void run(); }
Runnable的作用,实现多线程。我们可以定义一个类A实现Runnable接口;然后,通过new Thread(new A())等方式新建线程。
Thread 是一个类。Thread本身就实现了Runnable接口。它的声明如下:
public class Thread implements Runnable {}
Thread的作用,实现多线程。
二. Thread和Runnable的异同点
Thread是类,Runnable是接口。网传 ”Runnable可以实现资源共享“ 是不对的。具体分析看下面示例。
至于用类还是用接口,取决于继承上的实际需要。(note:看了网上大牛https://blog.csdn.net/mayp1/article/details/69950530说用”
Java这门语言发展到今天,在语言层面提供的多线程机制已经比较丰富且高级,完全不用在线程层面操作。直接使用Thread和Runnable这样的“裸线程”元素比较容易出错,还需要额外关注线程数等问题。建议:
简单的多线程程序,使用Executor。
简单的多线程,但不想关注线程层面因素,又熟悉Java8的:使用Java8的并行流,它底层基于ForkJoinPool,还能享受函数式编程的快捷。
复杂的多线程程序,使用一个Actor库,首推Akka。“)
三. Thread和Runnable的多线程示例
1. Thread的多线程示例
下面通过示例更好的理解Thread和Runnable,借鉴网上一个比较具有说服性的例子。
// ThreadTest.java 源码 class MyThread extends Thread{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println(this.getName()+" 卖票:ticket"+this.ticket--);//this.getName是调用得Thread中得getName方法 } } } }; public class ThreadTest { public static void main(String[] args) { // 启动3个线程t1,t2,t3;每个线程各卖10张票! MyThread t1=new MyThread(); MyThread t2=new MyThread(); MyThread t3=new MyThread(); t1.start(); t2.start(); t3.start(); } }
其中的一种运行结果:
Thread-0 卖票:ticket10 Thread-1 卖票:ticket10 Thread-2 卖票:ticket10 Thread-1 卖票:ticket9 Thread-0 卖票:ticket9 Thread-1 卖票:ticket8 Thread-2 卖票:ticket9 Thread-1 卖票:ticket7 Thread-0 卖票:ticket8 Thread-1 卖票:ticket6 Thread-2 卖票:ticket8 Thread-1 卖票:ticket5 Thread-0 卖票:ticket7 Thread-1 卖票:ticket4 Thread-2 卖票:ticket7 Thread-1 卖票:ticket3 Thread-0 卖票:ticket6 Thread-1 卖票:ticket2 Thread-2 卖票:ticket6 Thread-2 卖票:ticket5 Thread-2 卖票:ticket4 Thread-1 卖票:ticket1 Thread-0 卖票:ticket5 Thread-2 卖票:ticket3 Thread-0 卖票:ticket4 Thread-2 卖票:ticket2 Thread-0 卖票:ticket3 Thread-2 卖票:ticket1 Thread-0 卖票:ticket2 Thread-0 卖票:ticket1
结果说明:
(01) MyThread继承于Thread,它是自定义个线程。每个MyThread都会卖出10张票。
(02) 主线程main创建并启动3个MyThread子线程。每个子线程都各自卖出了10张票。(mynote:这3个myThread线程的执行结果是随机的,并发的,没有先后顺序,谁先抢到CUP谁先执行)
2. Runnable的多线程示例
下面,我们对上面的程序进行修改。通过Runnable实现一个接口,从而实现多线程。
// RunnableTest.java 源码 class MyThread implements Runnable{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--);//Runnable接口没有getName方法 } } } }; public class RunnableTest { public static void main(String[] args) { MyThread mt=new MyThread(); // 启动3个线程t1,t2,t3(它们共用一个Runnable对象) Thread t1=new Thread(mt); Thread t2=new Thread(mt); Thread t3=new Thread(mt); t1.start(); t2.start(); t3.start(); } }
运行结果:
Thread-0 卖票:ticket10
Thread-0 卖票:ticket8
Thread-0 卖票:ticket7
Thread-2 卖票:ticket10
Thread-2 卖票:ticket5
Thread-1 卖票:ticket9
Thread-1 卖票:ticket3
Thread-1 卖票:ticket2
Thread-1 卖票:ticket1
Thread-2 卖票:ticket4
Thread-0 卖票:ticket6
结果说明:
(01) 和上面“MyThread继承于Thread”不同;这里的MyThread实现了Runnable接口。
(02) 主线程main创建并启动3个子线程,而且这3个子线程都是基于“mt这个Runnable对象”而创建的。和资源共享没有关系。发现票数居然大于10了
问题分析:是的,编号10的票被卖了2次。为什么会这样,相信对多线程有了解的程序员都应该知道,操作符内部的实现并不是原子的。解决方法很简单,一是用synchronized这种内置锁,二是用AtomicInteger这样的concurrent包里封装好的元素。
解决办法1:
package com.test.a; public class MyThread implements Runnable { int ticket = 10; @Override public void run() { for (int i = 0; i < 20; i++) { //添加synchronized。保证多个线程对是同一个ByRunnable对象的run()是互斥操作的 synchronized (this) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖票:" + (ticket--)); } } } } }
package com.test.a; public class Test { public static void main(String[] args) { MyThread mt=new MyThread(); //主线程main创建并启动3个子线程,而且这3个子线程都是基于“thread这个Runnable对象”而创建的。 Thread thread1 = new Thread(mt); Thread thread2 = new Thread(mt); Thread thread3 = new Thread(mt); //运行结果是这3个子线程一共卖出了10张票。说明它们是共享了Runnable接口的资源 thread1.start(); thread2.start(); thread3.start(); } }
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-2卖票:10 Thread-2卖票: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
解决办法2:
package com.test.a; import java.util.concurrent.atomic.AtomicInteger; public class MyThread implements Runnable { private AtomicInteger tickets = new AtomicInteger(10); @Override public void run() { while (true) { int ticketnum; if ((ticketnum = tickets.getAndDecrement()) > 0) { System.out.println(Thread.currentThread().getName() + " is saling ticket: " + ticketnum); } else { break; } } } }
Thread-1 is saling ticket: 10 Thread-0 is saling ticket: 9 Thread-2 is saling ticket: 8 Thread-0 is saling ticket: 6 Thread-0 is saling ticket: 4 Thread-0 is saling ticket: 3 Thread-0 is saling ticket: 2 Thread-0 is saling ticket: 1 Thread-1 is saling ticket: 7 Thread-2 is saling ticket: 5
总结:Thread和Runnable的区别就是前者是类,后者是接口。并且用法差不多。没有Runnable比Thread多了资源共享这一说法,因为都可以通过一些手段来实现的。