Java多线程系列---“基础篇”02之 常用的实现多线程的两种方式

转自: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多了资源共享这一说法,因为都可以通过一些手段来实现的。

posted @ 2018-10-28 10:04  Hermioner  阅读(220)  评论(0编辑  收藏  举报