《Java并发编程的艺术》第四章——Java并发编程基础

来源:互联网 发布:java可变长参数 使用 编辑:程序博客网 时间:2024/05/17 03:45

知识点:

  1. 线程简介 。
  2. 启动和终止线程 。
  3. 线程间通信 。
  4. 线程应用实例。

1.线程简介

1.1 什么是线程?
要想明白什么是线程,必须先明白什么是进程!现在操作系统在运行一个程序时,会为其创建一个进程。而线程是操作系统调度的最小单元,也叫轻量级进程。一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈、和局部变量等属性,并能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程是在同时执行。
1.2 为什么要使用多线程?
- 更多的处理器核心:随着处理器上的核心数量越来越多,以及超线程技术的广泛应用,目前大多数计算机比以前更擅长并行计算,处理器性能的提升方式,也从更高的主频向更多的核心发展。如何利用好处理器上的多个核心也成了突出问题。 线程作为操作系统调度的最小单位,在一个时刻内只能运行在一个处理器核心上。如果不使用多线程,那么引入在多的处理器核心也对提升性能没有意义。
- 更快的响应时间: 有时候我们会编写一些较为复杂的代码,其中包含很多个操作。那么用户提交请求和需要等待所有操作全部执行完毕才可以。如果使用多线程,我们可以把不需要同步返回信息的操作交给其他线程处理,以缩短响应时间。
- 更好的编程模型:Java为多线程编程提供了良好、考究且一致的编程模型,使开发人员能够更加专注于问题的解决,即为所遇到的问题建立合适的模型,而不是绞尽脑汁的考虑如何将其多线程化。
1.3 线程优先级
现在操作系统基本采用分配时间片的方式调度运行的线程,线程会分配到若干时间片,当线程时间片用完后就会发生调度,等待下次分配。线程分配到的时间片多少决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或少分配处理器资源的线程属性。
在Java中,通过一个整型变量priority来控制优先级,优先级范围从1~10,在线程构建时可以使用setPriority(int)来修改优先级,默认为5,优先级高的线程分配时间片的数量要多于优先级低的线程。在不同的JVM及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。
【备注】:设置优先级时,针对频繁阻塞的线程需要设置较高的优先级,而偏重计算的线程则设置较低的优先级,确保处理器不会被独占。
1.4 线程的状态
Java线程在运行的生命周期中可能处于下表所示的6中不同的状态,在给定的一个时刻,线程只能处于其中一个状态:
这里写图片描述
实例:

package com.lipeng.fourth;import java.util.concurrent.TimeUnit;public class Demo {    public static void main(String[] args) {        final Object object1=new Object();        final Object object2=new Object();        Thread thread1=new Thread(new Runnable() {            @Override            public void run() {                try {                    synchronized (object1) {                        TimeUnit.SECONDS.sleep(30);                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"Demo-Thread-1");        Thread thread2=new Thread(new Runnable() {            @Override            public void run() {                try {                    synchronized (object1) {                        object1.wait();                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"Demo-Thread-2");        Thread thread3=new Thread(new Runnable() {            @Override            public void run() {                try {                    synchronized (object2) {                        object2.wait();                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"Demo-Thread-3");        Thread thread4=new Thread(new Runnable() {            @Override            public void run() {                try {                    synchronized (object2) {                        object2.wait(30000);                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        },"Demo-Thread-4");        thread1.start();        thread2.start();        thread3.start();        thread4.start();    }}

通过Java VisualVM查看线程栈信息:
这里写图片描述
线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间切换。其状态转换图如下:
这里写图片描述
【备注】:Java将操作系统中的运行和就绪两个状态合并称为运行状态。
1.5 守护(Daemon)线程
守护线程是一种支持型线程,主要用作程序中后台调度以及支持性工作(如JVM中负责GC的线程就是守护线程)。当JVM中不存在非守护线程时,JVM将退出。可以通过调用Thread.setDaemon(true)方法将线程设置为守护线程。


实例一
代码:

package com.lipeng.fourth;import java.util.concurrent.TimeUnit;public class DaemonDemo {    public static void main(String[] args) {        Thread thread=new Thread(new Runnable() {            @Override            public void run() {                try {                    System.out.println("daemon thread start");                    TimeUnit.SECONDS.sleep(3);                } catch (Exception e) {                }finally{                    System.out.println("daemon thread done");                }            }        });        thread.setDaemon(true);        thread.start();    }}

运行结果:
这里写图片描述
解析:main线程在启动守护线程之后main方法终止,目前JVM中无非Daemon线程,JVM退出。JVM中所有Daemon线程立即终止,所以finally块中的代码没有执行。
【备注】:在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。


实例二
代码:

package com.lipeng.fourth;public class DaemonDemo {    public static void main(String[] args) {        Thread thread=new Thread(new Runnable() {            @Override            public void run() {                try {                    System.out.println("daemon thread start");                } catch (Exception e) {                }finally{                    System.out.println("daemon thread done");                }            }        });        thread.setDaemon(true);        thread.start();    }}

运行结果:
这里写图片描述
解析:之所以会执行守护线程finally块中的代码,是因为在守护线程执行完毕之前,main线程还没有执行完,所以JVM不会退出。


2. 启动和终止线程
2.1 构造线程
在运行线程之前首先要构造一个线程对象,线程对象在构造时需要提供线程所需要的属性。如线程所属线程组、优先级、是否是Daemon线程等信息。下图为java.lang.Thread中对线程进行初始化的部分:
这里写图片描述
一个新构造的线程对象其parent线程进行空间分配,而child线程继承了parent是否是Daemon、优先级和加载资源的contextClassLoader及可继承的ThreadLocal,同时会分配一个唯一的ID来标识此child线程。至此,一个能够运行的线程对象初始化完毕,在堆内存中等待运行。
【备注】:在Java中,构造线程有三种方式:实现Runnable接口、实现Callable接口、继承Thread类。
2.2 启动线程
调用start()方法可启动线程。其含义为:当前线程(parent)同步告知JVM,只要线程规划器空闲,应立即启动调用start()方法的线程。
【备注】:直接调用run()方法和调用start()方法的区别:调用run方法,实质上只是以普通的方式调用方法,并不能使JVM去启动一个新线程。
2.3 中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否被中断来进行响应,线程可通过isInterrupted()来进行判断是否被中断,也可调用Thread.interrupted()方法将中断标识位进行复位。如果该线程已经处于终结状态,即使被中断过,调用isInterrupted()时仍会返回false。
在Java中,许多声明抛出InterruptedException的方法在抛出异常之前,JVM会先将该线程的中断标识位清除,然后抛出异常,此时调用isInterrupted()时同样会返回false。


实例一
代码:

package com.lipeng.fourth;import java.util.concurrent.TimeUnit;public class InterruptDemo {    public static void main(String[] args) {        Thread thread1=new Thread(new Runnable() {            @Override            public void run() {                while(true){                }            }        });        Thread thread2=new Thread(new Runnable() {            @Override            public void run() {                while(true){                    try {                        TimeUnit.SECONDS.sleep(30);                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            }        });        thread1.start();        thread2.start();        try {            TimeUnit.SECONDS.sleep(5);        } catch (InterruptedException e) {            e.printStackTrace();        }        thread1.interrupt();        thread2.interrupt();        System.out.println("thread 1 interrupt is "+thread1.isInterrupted());        System.out.println("thread 2 interrupt is "+thread2.isInterrupted());    }}

运行结果:
这里写图片描述
解析:线程2在抛出InterruptedException前,其中断标识位被清除,所以isInterrupted()方法返回false。


2.4 安全的终止线程
除了上面提到的使用中断来终止线程外,还可以利用一个boolean变量来作为条件控制线程是否终止。


实例一

package com.lipeng.fourth;import java.util.concurrent.TimeUnit;public class CancelThreadDemo {    static volatile boolean flag=true;    public static void main(String[] args) {        WorkThread workThread=new WorkThread();        new Thread(workThread).start();        try {            TimeUnit.SECONDS.sleep(3);        } catch (InterruptedException e) {            e.printStackTrace();        }        flag=false;    }    static class WorkThread implements Runnable{        @Override        public void run() {            while(flag){                //do something                System.out.println("do something"+System.currentTimeMillis());            }        }    }}

3. 线程间通信

阅读全文
0 0