java实现多线程的三种方式

原帖地址:原帖
个人网站地址:个人网站


三种方式

  1. 通过继承 Thread 类本身;
  2. 通过实现 Runnable 接口;
  3. 通过 Callable 和 Future 创建线程。

1,2无返回值,3返回future对象。(其实Thread中的run方法调用的是Runnable接口的run方法。Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。)

2相对于1灵活性更好,因为接口可以多实现,同时还可以继承其他类,而1无法完成;而且2方便共享资源:同一份资源,多个代理访问。

直接集成Thread类

在Java中负责线程这个功能的是Java.lang.Thread这个类。 可以通过创建Thread的实例来创建新的线程。 每个线程都是通个某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。 通过调用Thread类的start()方法来启动一个线程。

代码大致框架如下:

class 类名 extends Thread{
    属性1;
    属性2;
    …
    方法1;
    方法2;
    …
    public void run(){
    // other code…
    }
}

一个小例子,每个线程进行5次打印。

package thread;

class TestThread extends Thread {  
    public void run() {  
        for (int i = 0; i < 100; i++) {  
            if (count > 0) {  
                System.out.println("count= " + count--);  
            }
            else{
                break;
            }
        }  
    }  
    public static void main(String[] args) {  
        TestThread h1 = new TestThread();  
        TestThread h2 = new TestThread();  
        TestThread h3 = new TestThread();  
        h1.start();  
        h2.start();  
        h3.start();  
    }  
    private int count = 5;  
}   

某次的运行结果:

h2: count= 5
h2: count= 4
h2: count= 3
h2: count= 2
h2: count= 1
h1: count= 5
h1: count= 4
h1: count= 3
h1: count= 2
h1: count= 1
h3: count= 5
h3: count= 4
h3: count= 3
h3: count= 2
h3: count= 1

实现Runnable接口

代码大致框架如下:

class 类名 implements Runnable{
    属性1;
    属性2;
    …
    方法1;
    方法2;
    …
    public void run(){
    // other code…
    }
}

一个小例子,实现资源共享:三个卖票窗口,一起来卖5张票。

package thread;

/**
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个【相同】的程序代码的线程去处理【同一】个资源
2):可以跨过java中的单继承的限制,接口可以多实现
 */
class TestRunnable implements Runnable {
    private int ticket = 5; // 5张票

    public void run() {
        while(ticket>0){
            synchronized (this){
                if(ticket>0){
                    String nameString = Thread.currentThread().getName();
                    System.out.println(name + "正在卖票" + this.ticket--);
                    try {
                        Thread.sleep((int) Math.random() * 10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } 
                }
            }
        }
    }

    public static void main(String[] args) {
        TestRunnable t = new TestRunnable();
        new Thread(t, "1号窗口").start();
        new Thread(t, "2号窗口").start();
        new Thread(t, "3号窗口").start();
    }

}

某次的运行结果:

1号窗口正在卖票5
3号窗口正在卖票4
2号窗口正在卖票3
2号窗口正在卖票2
3号窗口正在卖票1

实现Callable接口

Callable和Runnable的不同

Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable接口的类都是可被其它线程执行的任务 。Callable和Runnable有几点不同:

  • Callable规定的方法是call(),而Runnable规定的方法是run()
  • call()方法可抛出异常,而run()方法不能抛出异常
  • Callable的任务执行后可返回值,运行Callable任务可得到一个Future对象,而Runnable的任务无返回值。Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。

使用思路

通过FutureTask对象

创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

package thread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class TestCallable implements Callable<Integer> {
    
    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }

    public static void main(String[] args) {
        TestCallable tCallable = new TestCallable();
        FutureTask<Integer> fTask = new FutureTask<Integer>(tCallable);
        for (int i = 0; i < 8; i++) {
            System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
            if (i == 3) {
                new Thread(fTask, "有返回值的线程").start();
            }
        }
            try {
                System.out.println("有返回值的线程的返回值:" + fTask.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
    }

}

某次运行结果:

main 的循环变量i的值0
main 的循环变量i的值1
main 的循环变量i的值2
main 的循环变量i的值3
main 的循环变量i的值4
main 的循环变量i的值5
main 的循环变量i的值6
有返回值的线程 0
main 的循环变量i的值7
有返回值的线程 1
有返回值的线程 2
有返回值的线程 3
有返回值的线程 4
有返回值的线程 5
有返回值的线程 6
有返回值的线程 7
有返回值的线程 8
有返回值的线程 9
有返回值的线程的返回值:10

通过Executor

Executor框架是Java5中引入的,其内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好。参考:Executor框架与线程池

创建:Callable实现类 +重写call

借助ExecutorService:执行调度服务ExecutorService,获取Future对象

ExecutorService ser = Executors.newFixedThreadPool(2);
Future result = ser.submit(new CallableImpl()) (submit有返回值,execute无返回值)

获取值:result.get()

停止服务:ser.shutdownNow();

package thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestCallable implements Callable<Integer> {
    private boolean flag = true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public Integer call() throws Exception {
        int i = 0;
        while(flag){
            i++;
            System.out.println(Thread.currentThread().getName() + " " + i);
            Thread.yield();
        }
        return i;
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        TestCallable tCallable1 = new TestCallable();
        TestCallable tCallable2 = new TestCallable();
        
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Future<Integer> result1 = executorService.submit(tCallable1);
        Future<Integer> result2 = executorService.submit(tCallable1);
        
        Thread.sleep(500);
        tCallable1.setFlag(false);
        tCallable2.setFlag(false);
        
        System.out.println("tCallable1: "+result1.get());
        System.out.println("tCallable2: "+result2.get());   
        executorService.shutdownNow();
    }
}

某次运行的部分结果:

......
pool-1-thread-1 12215
pool-1-thread-1 12216
pool-1-thread-2 14854
pool-1-thread-2 14855
pool-1-thread-2 14856
pool-1-thread-2 14857
pool-1-thread-2 14858
pool-1-thread-2 14859
pool-1-thread-2 14860
pool-1-thread-2 14861
pool-1-thread-2 14862
pool-1-thread-2 14863
pool-1-thread-2 14864
pool-1-thread-2 14865
pool-1-thread-2 14866
pool-1-thread-2 14867
pool-1-thread-2 14868
pool-1-thread-2 14869
pool-1-thread-1 12217
tCallable1: 12217
tCallable2: 14869
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容