[笔记][Java7并发编程实战手册][后补]1.线程管理

[笔记][Java7并发编程实战手册]系列目录


本章 是补充章节(学的时候跳过了这一章节),简单的就不写程序了.直接把笔记总结.

本章内容包括

  1. 线程的创建和运行
  2. 线程信息的获取和设置
  3. 线程的中断
  4. 线程中断的控制
  5. 线程的休眠和恢复
  6. 等待线程的终止
  7. 守护线程的创建和运行
  8. 线程中不可空异常的处理
  9. 线程局部变量的使用
  10. 线程的分组
  11. 线程组中不可空异常的处理
  12. 使用工厂类创建线程

1.1.简介

  1. 并发(Concurrency):
    只一系列任务的同时运行,如果电脑有多个处理器或者有一个多核处理器,这个同时性是真正意义的并发,但是一台电脑只有一个单核处理器,这个同时性并不是真正的并发.
  2. 进程级(Process_level)并发:
    在windows中,同时可看电视,听歌,这样的是进程级并发
  3. 线程(Thread)
    在一个进程内可以有多个同时进行的任务
  4. 并行
    书上说得有几种,把我搞蒙了,我觉得:并发是同时间执行的线程,并发是几个线程可以一起执行?

1.2.线程的创建和运行

  1. implements Runnable 接口 或则 extends Thread类,并实现或则重写run方法.
  2. new Thread(Runnable).start() 可开启线程

1.3.线程信息的获取和设置

Thread类的一些属性:

  1. ID:线程的唯一标识符
  2. Name:线程名称
  3. Priority:线程优先级,从1 到 10,1是最低级,一般不推荐修改线程的优先级
  4. Status:线程的状态.有: new、runnable、blocked、waiting、time waiting或者terminated

Thread类的属性存储了线程的所有信息,并且线程的id和status不能手动更改。如果你是实现 Runnable接口,那么可以通过 Thread的静态方法currentThread()来获取Thread对象


1.4.线程的中断

  一个Java程序,如果不止一个执行线程,当所有的线程都运行结束的时候,这个Java程序才能运行结束: 更准确的来说是 所有的非守护线程运行结束时,或则其中一个线程调用了System.exit()方法时,这个Java程序才运行结束. 如果你想终止一个程序,或则程序的某个用户视图取消线程对象正则运行的任务,就需要结束这个线程.
  中断机制: 可以用来结束一个线程. 这种机制要求线程检测它是否被中断了,然后决定是不是相应这个中断请求.线程运行忽略中断请求并且继续执行.

本节将学习如何将一个线程 中断.(对于中断的机制,其实还是有点难的.需要写一下)

* 总结 *
难就难在,中断的代码相关代码,大部分都是原生代码,看到怎么实现的.

  1. 线程在休眠中,如果被中断 会抛出InterruptedException异常.捕获这个异常,通过跳出循环的方式先 run方法体执行完成.
  2. 外部设置 中断. 如果线程没有被休眠,可以通过 thread.isInterrupted() 来获取线程的中断状态.
  3. 还可以使用 Thread的静态方法 interrupted() 来判断是否被中断
  4. interrupted 和 isInterrupted() :的区别,前者,会清除 中断状态.也就是说,如果外部设置了中断状态,调用interrupted的时候,返回true,同时会把中断状态清除为false
  5. 在递归循环的复杂调用中.(run方法中调用了多个方法.可以通过 在 多个方法中. 判断是否被中断了.然后手动的 抛出一个中断异常,在run中捕获这个异常,最终让任务结束)

示例

/**
 * Created by zhuqiang on 2015/10/12 0012.
 */
public class Client {
    public static void main(String[] args){
        Thread thread = new Thread(new Task());
        thread.start();

        //休眠3秒后发起中断请求
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            System.out.println("出错了");
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("请求中断:");
    }
}

class Task implements Runnable{

    @Override
    public void run() {
        boolean flag = true;
        Thread thread = Thread.currentThread();
        while (flag){
            System.out.println(thread.getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("是否被中断1 : " + thread.isInterrupted()); // 这个结果是fals
                flag = false;  //要注意这里的坑. 在休眠中,如果线程被中断会被抛出
            }

            if(thread.isInterrupted()){  //是否被中断
                System.out.println("是否被中断2 : " + thread.isInterrupted()); // 把上面休眠的代码注释掉,能看到这个结果是 true
                flag = false;
            }
        }
    }
}

* 某一次运行结果 *

Thread-0
Thread-0
Thread-0
请求中断:
java.lang.InterruptedException: sleep interrupted
是否被中断1 : false
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at java7Concurrency.sync1_4.Task.run(Client.java:34)
    at java.lang.Thread.run(Thread.java:745)

* 结果说明 *
通过中断异常能捕获到 线程被设置为了中断.我猜测可能是. 因为线程在休眠中,然后又被设置了中断请求,然后run中肯定不能被执行到的.所以 休眠方法就抛出了一个中断异常,表示该线程已经被中断了.


1.5.线程中断的控制

线程的中断 中总结了


1.6.线程的休眠和恢复

  好吧,在1.4中就已经用到了 休眠,果然是补的章节啊.

  1. 休眠: 使用TimeUnit.SECONDS.sleep(1)是线程休眠1秒; 能让线程让出cpu执行时钟指令,在这个期间线程是不占用资源的,并且不占用cpu时钟的,所以cpu可以去执行其他的任务.TimeUnit.SECONDS.sleep(1) 也是包装了Thread.sleep(ms, ns)方法.
  2. 恢复: 等待休眠时间结束后,该线程又拥有了cpu时钟执行权,线程恢复.
  3. thread的yield()方法,线程让步,让出当前线程的cpu时钟,但是又可能下一次还是让当前线程获得执行权,这个也是 和 休眠的一个区别.

1.7.等待线程的终止

  在一些情形下,比如 需要先初始化一些资源,初始化完成之后,再继续执行.这个时候 我们就需要等待线程终止再执行程序的其他任务. 可以使用 thread 的 join()方法. 示例就不写了,在后面的章节中会大量的用到.很简单的调用一个方法而已.

  1. join() : 等待线程的终止

1.8.守护线程的创建和运行

嗯!这段的描述,让我疑惑了.准备写点demo验证下.
守护线程(daemon) : 这种线程的优先级很低,通常来说一个程序中没有其他线程运行的时候,守护线程才运行.当守护线程是程序中唯一的线程的时候,守护线程结束后,jvm也就结束了.
  因为这种特性,守护线程通常用来作为统一程序中普通线程(也称为用户线程)的服务提供者.它通常是无线循环的,以等待服务请求或则执行线程的任务.因此他们不能做重要的工作,因为不知道守护线程什么时候能获取cpu时钟指令,也有可能随时结束.

好把.我的疑问:
  优先级低,不知道何时能获取cpu始终指令我能理解,但是说 一个程序中没有其他线程运行的时候,守护线程才运行? 是什么意思

* 总结 *

  1. 作为守护线程的 线程,要在该线程start前设置.
  2. 守护线程 会一直在后台运行,如果没有任何用户线程运行的时候,守护线程也会 结束.
  3. isDaemon() : 获取是否是守护线程

示例

场景描述: 下面的示例 ,就是 用3个线程 来往队列中增加元素. 用一个守护线程来清除元素.

/**
 * Created by zhuqiang on 2015/10/12 0012.
 */
public class Client {
    public static void main(String[] args) {
        LinkedBlockingQueue<Event> clq = new LinkedBlockingQueue<Event>();
//        Deque<Event> deque = new ArrayDeque<>();
        for (int i = 0; i < 3; i++) {
            new AddTask(i + ":name", clq).start();
        }
        CleanTask cleanTask = new CleanTask(clq);
        cleanTask.setDaemon(true); //要在 任务开始前设置守护线程
        cleanTask.start();
    }
}

class AddTask extends Thread {
    private String name; //任务名称
//    private Deque<Event> dq; //队列,该线程任务将 每秒往队列中增加一个元素
    private LinkedBlockingQueue<Event> clq;
    public AddTask(String name, LinkedBlockingQueue<Event> clq) {
        this.name = name;
        this.clq = clq;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            clq.add(new Event(name + ":" + i, new Date()));
            System.out.println(Thread.currentThread().getName() + " : 增加了元素 size:" + clq.size());
            try {
                TimeUnit.SECONDS.sleep(1);
//                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 清除线程,一直运行
class CleanTask extends Thread {
//    private Deque<Event> dq;
    private LinkedBlockingQueue<Event> clq;
    public CleanTask(LinkedBlockingQueue<Event> clq) {
        this.clq = clq;
    }

    @Override
    public void run() {
        while (true) {
            long time;
                Date now = new Date();
                Event last = null;
                try {
                    last = clq.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Date date = last.getDate();
                time = now.getTime() - date.getTime();

                System.out.println("清除了一个元素:" + JSON.toJSONString(last) + "  dq.size=" + clq.size());

//            System.out.println("清除线程 运行中 ************" + dq.size());
        }

    }
}

class Event {
    private String name;  //事件名称
    private Date date; //创建时间

    public Event(String name, Date date) {
        this.name = name;
        this.date = date;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

* 一部分运行结果 *

Thread-0 : 增加了元素 size:1
Thread-1 : 增加了元素 size:2
Thread-2 : 增加了元素 size:3
清除了一个元素:{"date":1444663754124,"name":"0:name:0"}  dq.size=2
清除了一个元素:{"date":1444663754125,"name":"1:name:0"}  dq.size=1
清除了一个元素:{"date":1444663754125,"name":"2:name:0"}  dq.size=0
Thread-0 : 增加了元素 size:2
Thread-1 : 增加了元素 size:3
Thread-2 : 增加了元素 size:2
清除了一个元素:{"date":1444663755126,"name":"2:name:1"}  dq.size=2
清除了一个元素:{"date":1444663755126,"name":"0:name:1"}  dq.size=1
清除了一个元素:{"date":1444663755126,"name":"1:name:1"}  dq.size=0
Thread-1 : 增加了元素 size:2
Thread-2 : 增加了元素 size:3

* 结果说明 *
我最开始按照书上的例子,使用ArrayDeque来作为存储元素的队列,最后发现守护线程获取到的大小一直是0,然后我把守护线程休眠,就能获取到非0的队列. 这个就是一个线程问题了. 所以我觉得书上的列子是错误的结论. 然后使用 线程安全的队列(就是上面的示例),也只证实了.守护线程 会随着没有用户线程的时候跟着消失! 不知道是不是我的机器性能太好的缘故.书上的例子 没有办法重现证书说的是正确的.


1.9.线程中不可空异常的处理

Java中分为两种异常:

  1. 非运行时异常(Checked Exception):这种异常必须在方法声明throws语句.或则在方法体中捕获
  2. 运行时异常(Unchecket Exception): 这种感异常 不必抛出,也可以捕获.

在run方法中,不支持抛出异常.所以 如果出现了在运行时异常,你不捕获(一般不仔细看api是不会知道的).默认是在控制台打印. 好在java提供了另一种在线程中捕获运行时异常的一种机制.

  1. implements Thread.UncaughtExceptionHandler 类
  2. 在线程start前设置异常处理器 thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler);
  3. 这种捕获异常的方法.只可能是 运行时异常.原理肯定是 try 了下,然后把线程对象和异常对象回调给处理器的.( 不过短时间没有找到相关的源码)
  4. 也可以使用静态方法 Thread.setDefaultUncaughtExceptionHandler(new MyExHander()); 设置异常处理器
  5. jvm调用查找异常处理器的顺序: 线程对象设置的 –> 线程组(ThreadGroup的) –> 最后是上面讲的默认的异常处理器

示例

/**
 * Created by zhuqiang on 2015/10/14 0014.
 */
public class Client {
    public static void main(String[] args) {
        Thread thread = new Thread(new Task());
        thread.setUncaughtExceptionHandler(new MyExHander()); //设置自定义的 异常处理器
        thread.start();
    }
}

//异常处理器
class MyExHander implements Thread.UncaughtExceptionHandler{

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println(t.getName());
        System.out.printf("Exception:%s,msg=%s",e.getClass().getName(),e.getMessage());
    }
}

class Task implements Runnable{

    @Override
    public void run() {
        int sss = Integer.parseInt("sss");  // 运行时异常
    }
}

* 运行结果 *

Thread-0
Exception:java.lang.NumberFormatException,msg=For input string: "sss"

1.10.线程局部变量的使用

  在一个task被多个线程运行的时候,那么这个task中的属性就成了共享属性,但是有时候,我们并不想变量被所有线程共享,这个时候 就要用到 线程局部变量(Thread-Local Variable)机制了.

很简单的使用,就不示例了.伪代码,把loc 用ThreadLocal来包装,并初始化. 而不是使用private Date date;

ThreadLocal<Date> loc = new ThreadLocal<Date>(){
            @Override
            protected Date initialValue() {
                return new Date();
            }
        };
        Date date = loc.get();  //获取值
        System.out.println("开始时间:" + date);
        loc.set(new Date()); //设置值
        System.out.println("结束时间:" + loc.get());
        loc.remove(); //删除值

InheritableThreadLocal : 也是一种局部变量,不过它是用于给子线程 获取父线程的 局部变量的.假如:A线程创建了B线程.线程B的局部变量和A的局部变量是一样的.也可以覆盖childValue()方法,这个方法用来初始化子线程在线程局部变量中的值.它使用父线程在线程局部变量中的值作为参数传入.


1.11.线程的分组

  好吧,这个我觉得是一个很有趣的功能.之前一直都没有学习过.
  ThreadGroup(线程组): 表示一组线程,可以包含线程对象,也可以包含线程组对象,它是一个树形结构.
  在某些情况下,线程分组比较好控制,比如在多个线程运行中,你要控制他们.只需要一个单一的操作就能达到效果.

好把.看示例和运行结果,原来线程分组就是这样使用的.但是在以下的示例中,其实有弊端的.前面已经讲到过了, 中断:当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常InterruptedException .这样不在这些状态的时候 就没招了.然而不是还有一种方法么 就是通过检测是否中断

if (Thread.interrupted())  // Clears interrupted status!
      throw new InterruptedException();

前面也有讲到,那么在下面的示例中 其实就很难运用到线程分组用组来操作线程了. 其他的我也没有想通.

示例

场景描述: 将创建几个线程,并把他们分到一组.只要有一个线程找到了指定的数字,就让其他几个线程中断.

/**
 * Created by zhuqiang on 2015/10/14 0014.
 */
public class Client {
    public static void main(String[] args) throws InterruptedException {

        ThreadGroup group = new ThreadGroup("xxx");
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(group, new Task(2,group));
            t.start();
//            TimeUnit.MILLISECONDS.sleep(200);
        }

        int i = group.activeCount();
        System.out.println("活动的线程:" + i);
        System.out.println("打印线程组信息---- 开始");
        group.list();
        System.out.println("打印线程组信息---- 结束");
        Thread[] list = new Thread[i];
        group.enumerate(list); //把活动的线程复制到指定的线程中.
        for (Thread t : list) {
            System.out.printf("name=%s,state=%s\n", t.getName(),t.getState());;
        }
    }
}
class Task implements Runnable{
    private int num;
    private ThreadGroup xx;

    public Task(int num, ThreadGroup xx) {
        this.num = num;
        this.xx = xx;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " : start");
            TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000));
            Random random = new Random();
            int i = random.nextInt(3);
            if(i== num){
                System.out.println(Thread.currentThread().getName() + " : 命中目标");
                xx.interrupt(); //找到了就中断所有线程组中的线程
            }
            System.out.println(Thread.currentThread().getName() + " : end");
        }catch (InterruptedException e){
            return;  //直接返回
        }
    }
}

* 某一次运行结果 *

活动的线程:10
打印线程组信息---- 开始
java.lang.ThreadGroup[name=xxx,maxpri=10]
    Thread[Thread-0,5,xxx]
    Thread[Thread-1,5,xxx]
    Thread[Thread-2,5,xxx]
    Thread[Thread-3,5,xxx]
    Thread[Thread-4,5,xxx]
    Thread[Thread-5,5,xxx]
    Thread[Thread-6,5,xxx]
    Thread[Thread-7,5,xxx]
    Thread[Thread-8,5,xxx]
    Thread[Thread-9,5,xxx]
打印线程组信息---- 结束
name=Thread-0,state=RUNNABLE
Thread-7 : start
Thread-6 : start
Thread-3 : start
Thread-2 : start
Thread-9 : start
Thread-8 : start
Thread-5 : start
Thread-4 : start
Thread-1 : start
Thread-0 : start
name=Thread-1,state=BLOCKED
name=Thread-2,state=RUNNABLE
name=Thread-3,state=RUNNABLE
name=Thread-4,state=RUNNABLE
name=Thread-5,state=RUNNABLE
name=Thread-6,state=RUNNABLE
name=Thread-7,state=RUNNABLE
name=Thread-8,state=RUNNABLE
name=Thread-9,state=TIMED_WAITING
Thread-9 : end
Thread-0 : 命中目标
Thread-0 : end

1.12.线程组中不可空异常的处理

直接上代码,全是干货

  1. extends ThreadGroup
  2. 重写 uncaughtException(Thread t, Throwable e) 方法.只要该线程组中有抛出异常的,该方法就会被调用
/**
 * Created by zhuqiang on 2015/10/14 0014.
 */
public class Client {
    public static void main(String[] args) {
        MyGroup my = new MyGroup("my");
        for (int i = 0; i < 3; i++) {
            new Thread(my,new Task()).start();
        }
    }
}

//线程组,覆盖uncaughtException方法,捕获 当前组中所有抛出的非捕获异常
class MyGroup extends ThreadGroup{

    public MyGroup(String name) {
        super(name);
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.printf("%s,Exception:%s,msg=%s\n",t.getName(),e.getClass().getName(), e.getMessage());
    }
}

class Task implements Runnable{

    @Override
    public void run() {
        Integer.parseInt("ss");
    }
}

* 运行结果 *

Thread-1,Exception:java.lang.NumberFormatException,msg=For input string: "ss"
Thread-2,Exception:java.lang.NumberFormatException,msg=For input string: "ss"
Thread-0,Exception:java.lang.NumberFormatException,msg=For input string: "ss"

1.13.使用工厂类创建线程

这个就不讲了.很简单的. 在 7.4.实现ThreadFactory接口生成定制线程 已经使用过了.

/**
 * Created by zhuqiang on 2015/10/14 0014.
 */
public class Client {
    public static void main(String[] args) {
        MyFactrory myFactrory = new MyFactrory();
        Thread thread = myFactrory.newThread(new Runnable() {  //用我们的工厂来创建 线程
            @Override
            public void run() {

            }
        });
    }
}

class MyFactrory implements ThreadFactory{
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setPriority(2);  // 可以设置优先级
        thread.setName("xxxxxx" + (long)(Math.random() * 10) ); // 可以设置线程名称等
        return thread;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值