Java并发编程实战(一)线程管理

一、线程的创建和运行

1. 实现Runnable接口

public class Calculator implements Runnable {
    private int number;
    public Calculator(int number) {
        this.number = number;
    }
    public void run(){
        for(int i=1;i<=10;i++){
            System.out.printf("%s: %d * %d = %d\n",Thread.currentThread().getName(),number,i,i*number);
        }
    }

    public static void main(String[] args) {
        for(int i=1;i<=10;i++){
            Thread thread = new Thread(new Calculator(i));
            thread.start(); // start()启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用
            // thread.run();   // run()和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!
        }
    }
}

2. 继承Thread类

public class Calculator extends Thread {
    private int number;
    public Calculator(int number) {
        this.number = number;
    }
    public void run(){
        for(int i=1;i<=10;i++){
            System.out.printf("%s: %d * %d = %d\n",Thread.currentThread().getName(),number,i,i*number);
        }
    }

    public static void main(String[] args) {
        for(int i=1;i<=10;i++){
            Thread calculator = new Calculator(i);
            calculator.setName("Thread");
            calculator.start();
        }
    }
}

每个Java程序最少有一个执行线程。当你运行程序的时候, JVM运行负责调用main()方法的执行线程。

当调用Thread对象的start()方法时, 我们创建了另一个执行线程。在这些start()方法调出后,我们的程序就有了多个执行线程。

当全部的线程执行结束时(更具体点,所有非守护线程结束时),Java程序就结束了。如果初始线程(执行main()方法的主线程)运行结束,其他的线程还是会继续执行直到执行完成。但是如果某个线程调用System.exit()指示终结程序,那么全部的线程都会结束执行。

创建一个Thread类的对象不会创建新的执行线程。同样,调用实现Runnable接口的 run()方法也不会创建一个新的执行线程。只有调用start()方法才能创建一个新的执行线程。

二、线程信息的获取和设置

  1. ID:每个线程的独特标识
  2. Name:线程的名称
  3. Priority:线程对象的优先级。优先级别在1-10之间,1是最低级,10是最高级。不建议改变它们的优先级,但是你想的话也是可以的
  4. Status:线程的状态。(new, runnable, blocked, waiting, time waiting, terminated)

如果你没有声明一个线程的名字,那么JVM会自动命名它为:Thread-XX,XX是一个数字。线程的ID或者状态是不可修改的。Thread类没有实现setId()和setStatus()方法来允许修改它们。

public class Calculator implements Runnable {
    private int number;
    public Calculator(int number) {
        this.number = number;
    }
    public void run(){
        for(int i=1;i<=10;i++){
            System.out.printf("%s: %d * %d = %d\n",Thread.currentThread().getName(),number,i,i*number);
        }
    }

    public static void main(String[] args) {
        Thread threads[] = new Thread[10];
        Thread.State status[] = new Thread.State[10];

        for(int i=0;i<10;i++){
            threads[i] = new Thread(new Calculator(i));
            // setPriority()方法会抛出 IllegalArgumentException 异常,如果你设置的优先级不是在1-10之间。
            if ((i%2)==0) threads[i].setPriority(Thread.MAX_PRIORITY);
            else threads[i].setPriority(Thread.MIN_PRIORITY);
            threads[i].setName("Thread "+i);
        }

        for (int i=0;i<10;i++){
            threads[i].start();
        }
    }
}

三、线程的中断

一个多个线程在执行的Java程序,只有当其全部的线程执行结束时(更具体的说,是所有非守护线程结束或者某个线程调用System.exit()方法的时候),它才会结束运行。有时,你需要为了终止程序而结束一个线程,或者当程序的用户想要取消某个Thread对象正在做的任务。

Java提供中断机制来通知线程表明我们想要结束它。中断机制的特性是线程需要检查是否被中断,而且还可以决定是否响应结束的请求。所以,线程可以忽略中断请求并且继续运行。

在这个指南中, 我们将开发一个程序,它创建线程,然后在5秒之后,它会使用中断机制来强制结束线程。

public class PrimeGenerator extends Thread {
    public void run() {
        long number=1L;
        while(true){
            if(isPrime(number)) System.out.printf("Number %d is Prime",number);
            // 处理完一个数字, 调用isInterrupted()方法来检查线程是否被中断了。如果它返回值为真,就写一个信息并结束线程的运行
            if(isInterrupted()){
                System.out.println("The Prime Generator has been Interrupted");
                return;
            }
            number++;
        }
    }

    private boolean isPrime(long number){
        if(number<=2) return false;
        for(long i=2;i<number;i++){
            if((number%i)==0){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Thread task = new PrimeGenerator();
        task.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 等待5秒后终中断线程
        task.interrupt();
    }
}

四、线程中断的控制

http://ifeve.com/thread-management-5/

五、线程的休眠和恢复

有时你会感兴趣在一段确定的时间内中断执行线程。例如, 程序的一个线程每分钟检查反应器状态。其余时间,线程什么也不做。在这段时间,线程不使用任何计算机资源。过了这段时间,当JVM选择它时,线程会准备好继续执行。为达此目的,你可以使用Thread类的sleep()方法 。此方法接收一个整数作为参数,表示线程暂停运行的毫秒数。在调用sleep()方法后,当时间结束时,当JVM安排他们CPU时间,线程会继续按指令执行,

另一种可能是使用一个有TimeUnit列举元素的sleep()方法,使用线程类的sleep()方法让当前线程睡眠,但是它接收的参数单位是表示并转换成毫秒的。

http://ifeve.com/thread-management-6/

六、等待线程的终止

Thread类的join()方法:当一个线程对象的join()方法被调用时,调用它的线程将被挂起,直到这个线程对象完成它的任务。

public class JoinTest{
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Task1(),"Task1");
        Thread thread2 = new Thread(new Task2(),"Task2");
        thread1.start();
        thread2.start();
        try {
            thread1.join(); // 若没有这两个join方法,则程序先打印"Main: OK~"
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Main: OK~");
    }
}


class Task1 implements Runnable{
    public void run() {
        System.out.println("开始执行1...");
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行完成1...");
    }
}

class Task2 implements Runnable{
    public void run() {
        System.out.println("开始执行2...");
        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行完成2...");
    }
}

七、守护线程的创建和运行

Java有一种特别的线程叫做守护线程。这种线程的优先级非常低,通常在程序里没有其他线程运行时才会执行它。当守护线程是程序里唯一在运行的线程时,JVM会结束守护线程并终止程序。

根据这些特点,守护线程通常用于在同一程序里给普通线程(也叫使用者线程)提供服务。它们通常无限循环的等待服务请求或执行线程任务。它们不能做重要的任务,因为我们不知道什么时候会被分配到CPU时间片,并且只要没有其他线程在运行,它们可能随时被终止。JAVA中最典型的这种类型代表就是垃圾回收器。

http://ifeve.com/thread-management-8/

八、线程中不可控异常的处理

Java里有2种异常:

  • 检查异常(Checked exceptions): 这些异常必须强制捕获它们或在一个方法里的throws子句中。 例如IOException或者ClassNotFoundException。
  • 未检查异常(Unchecked exceptions): 这些异常不用强制捕获它们。例如NumberFormatException。
    在一个线程对象的run()方法里抛出一个检查异常,我们必须捕获并处理他们。因为run()方法不接受throws子句。当一个非检查异常被抛出,默认的行为是在控制台写下stacktrace并退出程序。

幸运的是, Java 提供我们一种机制可以捕获和处理线程对象抛出的未检测异常来避免程序终结。

// 实现用来处理运行时异常的类,重写了uncaughtException()方法
class ExceptionHandler implements Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("一个错误被捕获");
        System.out.printf("Thread: %s\n",t.getId());
        System.out.printf("Exception: %s: %s\n",e.getClass().getName(),e.getMessage());
    }
}

public class ExceptionTest implements Runnable {
    public void run() {
        int num = Integer.parseInt("A");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new ExceptionTest());
        thread.setUncaughtExceptionHandler(new ExceptionHandler());
        thread.start();
        System.out.println("如果你看到这行消息,说明上面的异常被自己定义的异常处理捕获了,我就可以继续运行了~");
    }
}

九、线程局部变量的使用

并发应用的一个关键地方就是共享数据。这个对那些扩展Thread类或者实现Runnable接口的对象特别重要。

如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性。这意味着,如果你在一个线程里改变一个属性,全部的线程都会受到这个改变的影响。

有时你希望程序里的各个线程的属性不会被共享。 Java并发API提供了一个很清楚的机制叫本地线程变量。

http://ifeve.com/thread-management-10/

十、线程的分组

http://ifeve.com/thread-management-11/

十一、线程组中不可控异常的处理

http://ifeve.com/thread-management-12/

十二、使用工厂类创建线程

http://ifeve.com/thread-management-13/

public class MyThreadFactory implements ThreadFactory {
    private int counter;
    private String name;
    private List<String> status;
    public MyThreadFactory(String name){
        counter=0;
        this.name=name;
        status=new ArrayList<String>();
    }

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r,name+"-Thread_"+counter);
        counter++;
        status.add(String.format("Created thread %d with name %s on %s\n",t.getId(),t.getName(),new Date()));
        return t;
    }

    public String getStatus() {
        StringBuffer buffer = new StringBuffer();
        Iterator<String> it = status.iterator();
        while (it.hasNext()){
            buffer.append(it.next());
            buffer.append("\n");
        }
        return buffer.toString();
    }

    public static void main(String[] args) {
        MyThreadFactory myThreadFactory = new MyThreadFactory("MyThreadFactory");
        Task task = new Task();
        Thread thread;
        System.out.println("开始进程...");
        for(int i=0;i<10;i++){
            thread=myThreadFactory.newThread(task);
            thread.start();
        }
        System.out.println("Factory Status:\n"+myThreadFactory.getStatus());
    }
}

class Task implements Runnable{
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,331评论 3 87
  • 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要...
    嘟爷MD阅读 7,303评论 21 272
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,735评论 14 507
  • 一、认识多任务、多进程、单线程、多线程 要认识多线程就要从操作系统的原理说起。 以前古老的DOS操作系统(V 6....
    GT921阅读 1,009评论 0 3
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,438评论 1 15