线程基础概念

来源:互联网 发布:时代周刊封面 知乎ps 编辑:程序博客网 时间:2024/05/18 19:20

线程生命周期

  在正常情况下线程的生命周期,首先是创建一个线程对象,设置一些线程参数,例如,名字和优先级之类的,这是第一步。然后调用start()方法,这时线程并不会真正开始运行了,此时,只是准备就绪阶段,表示线程可以运行了,但是还没有开始运行。没有开始是因为在等待抢占到CPU的执行权限,一旦获得了执行权限就会开始执行执行run()方法的内容,直到run()方法退出了或者调用了中断后线程才会停止,然后变成垃圾等待GC回收器回收。
单线程简单周期
  
  上面的线程生命周期表示的是只有单个线程工作,且不是无限循环的状态,随着线程的增加线程的生命周期会有很多变化,如下图所示:
这里写图片描述
  首先,是运行状态中如果使用了sleep()或者wait()等让线程暂停的函数,就会进入阻塞状态,这个状态什么都不做,线程会停在这里。当sleep()的时间到了或者收到了相应的notify()通知时,线程会再次进入就绪状态,等待获取CPU的执行权限。当某一个线程在获得了执行权限后JVM会分配CPU的时间,此时,如果执行权限被其他线程抢占了则被抢占的线程则会再次返回就绪状态。
  根据上边的图可以看出,多个线程正常运行情况下是会一直在就绪和运行两个状态来回切换。

Thread类

  这种方式比较简单,但有一点要说明一下,先看代码:

public class StartThread extends Thread {    @Override    public void run() {        for(int i = 0; i < 20; i++) {            System.out.print((i + 1) + " ");        }        System.out.println("打印完成!");    }}public static void main(String[] args) {    Thread thread_1 = new StartThread();    Thread thread_2 = new StartThread();    thread_1.run();    thread_2.run();}

  此时,是不会有任何线程效果的,如果是由用户调用直接调用run()方法,则此方法就作为普通函数使用,线程的运行是要通过虚拟机(JVM)来调用,JVM会调用run()方法并给线程分配CPU资源
  那么,如何让JVM自动调用run()方法呢?一般来说,线程的启动都是调用start()方法,代码如下:

public class StartThread extends Thread {    @Override    public void run() {        for(int i = 0; i < 300; i++) {            // 获得线程的名字 getName();            System.out.println("线程 " + getName() + "  " + (i + 1));        }    }}public static void main(String[] args) {    Thread thread_1 = new StartThread();    Thread thread_2 = new StartThread();    thread_1.start();    thread_2.start();}

运行结果:

         thread_show_1
         
  从上边的代码可以看到线程是交替执行的了,这里在查询API的start()方法的时候有一点内容需要注意:
  api_1
  
上面红框的内容,如果对同一个线程调用两次start()会抛出紫色框中的异常,其异常的解释如下:

api_2

  上面的代码使用了getName()来获取线程的名字,默认是打印线程的ID,可以通过setName()的方式来对线程进行命名,例如:、

public static void main(String[] args) {    Thread thread_1 = new StartThread();    thread_1.setName("张三");    Thread thread_2 = new StartThread();    thread_2.setName("李四");    thread_1.start();    thread_2.start();}

                thread_show_2

Runnable接口

  这种实现接口的方式和继承Thread类基本上没区别,只不过由于Java是单继承模式,所以,才需要这种接口方式实现线程,代码如下:

public class RunnableThread implements Runnable {    @Override    public void run() {        int nIndex = 0;        for(int i = 0; i < 5000; i++) {            nIndex++;        }        System.out.println("线程执行完毕,执行次数:" + nIndex);    }}public static void main(String[] args) {    Thread thread = new Thread(new RunnableThread());    thread.start();}

Callable接口

  使用Callable接口可以实现带返回值的线程,是个泛型接口。通过回调call()方法回去返回值,代码如下:

public class CallableThread implements Callable {    private int mNum;    public CallableThread(int num) {        mNum = num;    }    @Override    public Object call() throws Exception {        int result = 1;        for(int i = 0; i < mNum; i++) {            result *= (i + 1);        }        return result;    }}public static void main(String[] args) {    ExecutorService executorService = Executors.newFixedThreadPool(3);    Future<Integer> result_1 = executorService.submit(new CallableThread(10));    Future<Integer> result_2 = executorService.submit(new CallableThread(5));    try {        System.out.println("10! = " + result_1.get() + "  5! = " + result_2.get());    } catch (InterruptedException e) {        e.printStackTrace();    } catch (ExecutionException e) {        e.printStackTrace();    }    executorService.shutdown();}

线程调度

  调度有如下模式:
  1. 分时调度模型,所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。
  2. 占式调度模型,优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
  可以使用getPriority()方法获得线程优先级,用setPriority()方法设置优先级,方法如下:

public static void main(String[] args) {    Thread thread_1 = new StartThread();    thread_1.setName("鸣人");    Thread thread_2 = new StartThread();    thread_2.setName("佐助");    Thread thread_3 = new StartThread();    thread_3.setName("小樱");    System.out.println(thread_1.getName() + " 优先级:" + thread_1.getPriority());    System.out.println(thread_2.getName() + " 优先级:" + thread_2.getPriority());    System.out.println(thread_3.getName() + " 优先级:" + thread_3.getPriority());    thread_1.start();    thread_2.start();    thread_3.start();}

          thread_show_3
  注意:设置的优先级不能超过1~10,否则会抛出参数错误异常。另外,不代表优先级越高就一定会优先完成,因为存在随机性

加入线程

  如果线程对象调用了join()方法就表示要先执行该线程的内容,直到该线程执行完毕才能继续运行后边的代码,还需要注意一点就是一地要在start()方法后边执行,代码如下:

public static void main(String[] args) {    Thread thread_1 = new StartThread();    thread_1.setName("鸣人");    Thread thread_2 = new StartThread();    thread_2.setName("佐助");    thread_1.start();    try {        thread_1.join();    } catch (InterruptedException e) {        e.printStackTrace();    }    thread_2.start();}

礼让线程

  如果线程在运行时使用了yield()方法就表示次线程本次什么都不做将CPU资源直接释放,让其他线程来工作,代码如下:

public class StartThread extends Thread {    @Override    public void run() {        for(int i = 0; i < 300; i++) {            System.out.println(getName() + " " + (i + 1));            Thread.yield();        }    }}

守护线程

  使用setDaemon(true)方法可以将线程设置为守护线程,例如,在A线程上运行B和C两个线程并且都设置了守护线程,这时如果A线程退出了则B和C都会相继结束,代码如下:

public class NormalThread extends  Thread{    @Override    public void run() {        System.out.println("线程 " + getName() + " 开始执行");        int nIndex = 0;        for(int i = 0; i < 5000; i++) {            nIndex++;        }        System.out.println("线程 " + getName() + " 执行完毕,执行次数:" + nIndex);    }}public class DaemonThread extends Thread {    @Override    public void run() {        System.out.println("线程 " + getName() + " 开始执行");        int nIndex = 0;        for(int i = 0; i < 5000; i++) {            nIndex++;            System.out.println("nIndex: " + nIndex);        }        System.out.println("线程 " + getName() + " 执行完毕,执行次数:" + nIndex);    }}

  上边的代码一个是普通线程,一个是守护线程,为了看清效果,在守护线程中加入了打印当前执行次数的打印,执行代码及结果如下:

public static void main(String[] args) {    Thread normalThread = new NormalThread();    normalThread.setName("正常线程");    Thread daemonThread = new DaemonThread();    daemonThread.setName("守护线程");    daemonThread.setDaemon(true);    normalThread.start();    daemonThread.start();    System.out.println("退出 " + Thread.currentThread().getName() + " 线程!");}

    thread_show_4

  上图中,可以看到首先是打印了main线程退出,然后守护线程只执行了12次就结束了,而且没有打印结束语句是直接结束的。而正常线程则完成了5000次的循环。

线程中断

  interrupt()方法可以中断线程,该方法是通过改变线程的状态来实现终止,并且在终止的时候会抛出异常。

原创粉丝点击