java笔记(五):多线程--Thread和Runnable

多线程是java高效利用CPU的关键。
首先要明白线程与进程的区别。
进程:程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程。
进程的特点:通过进程控制块(PCB)唯一的标识某个进程。同时进程占据着相应的资源(例如包括cpu的使用 ,轮转时间以及一些其它设备的权限)。是系统进行资源分配和调度的一个独立单位。有多种状态(就绪,执行,阻塞)
线程:进程中执行的一个任务。
线程的特点:需要较少的资源,和同一进程的其他线程共享进程的资源。是CPU调度和分派的基本单位(它是比进程更小的能独立运行的基本单位)。有多种状态。
举例来说,打开了一个word是打开了一个进程,一次换行操作是该进程中的一个线程。java代码的一个完整执行是一次进程的执行,代码中的多线程任务就是线程。
创建一个多线程,有三种方法:

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

Runnable

首先还是剖析下Runnable源码:

@FunctionalInterface
public interface Runnable {
    /**
     * 当使用实现该接口的类创建Thread时,开启该线程会使
     * run()方法被一个分开的线程调用,以实现多线程的目的
     */
    public abstract void run();
}

官方解释说,Runnable应该被任何希望由Thread执行的类所继承。它提供了一种方法使得即使不继承Thread,也可以执行多线程。
另外,Runnable可以通过实例化一个Thread然后把自己传给Thread来执行多线程,而不是靠继承Thread。
多数情况下,当我们只是想覆盖run()方法时,使用Runnable是最好的选择。

/** 以下
 * 为可能的结果之一
 * thread2:0
 * thread1:0
 * thread2:1
 * thread1:1
 * thread2:2
 * thread1:2
 * thread1:3
 * thread2:3
 * thread1:4
 * thread2:4
 * 单纯的执行new ThreadRunnable("thread3").run()得不到多线
 * 的效果
 */
new Thread(new ThreadRunnable("thread1")).start();
new Thread(new ThreadRunnable("thread2")).start();
//new ThreadRunnable("thread3").run();
//new ThreadRunnable("thread4").run();

class ThreadRunnable implements Runnable{
    private String name;
    public ThreadRunnable(String name){
        this.name = name;
    }
    public void run(){
        for(int i = 0;i<5;i++){
            System.out.println(name+":"+i);
        }
    }
}

单独的Runnable是没法实现多线程,无论如何都绕不开Thread这个类。
另一种更具有封装性的使用Runnable接口的方法如下:

//new Thread(new ThreadRunnable("thread1")).run();
//new Thread(new ThreadRunnable("thread2")).run();
new ThreadRunnable("thread3").start();
new ThreadRunnable("thread4").start();

class ThreadRunnable implements Runnable{
    private String name;

    public ThreadRunnable(String name){
        this.name = name;
    }
    public void run(){
        for(int i = 0;i<10;i++){
            System.out.println(name+":"+i);
        }
    }
    public void start(){
        new Thread(this, name).start();
    }
}

上述代码同样可以完成多线程的功能。

Thread

继承Thread并重写run()是实现多线程的另一种方法。
java多线程无论如何多绕不开Thread,下面简单剖析下Thread的源码。

public class Thread implements Runnable {
    private int priority;  //优先级
    private boolean daemon = false;  //是不是守护线程
    private Runnable target;

sleep()

该方法导致当前正在执行的线程休眠指定的毫秒数.

ThreadTest t = new ThreadTest("thread0");
t.start();
Thread.sleep(10000);
t.flag = false;

class ThreadTest extends Thread{
    private String name;
    private int i = 0;
    boolean flag = true;
    public ThreadTest(String name){
        this.name = name;
    }
    public void run(){
        while(flag){
            try{
                Thread.sleep(1000);             
            } catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(name+":"+i++);
        }
    }
}

上述程序会每隔1s进行一次输出。

join()

该方法的作用是使得线程对象先执行完(或者先执行指定的毫秒),然后主程序在接着执行。

/**
 *结果如下:
 main
thread
main
thread
main
thread
main
thread
main
thread
thread
thread
thread
thread
thread
main
main
main
main
main
*/

ThreadTest t = new ThreadTest();
t.start();
for(int i = 0;i<10;i++){
    if(i == 5){
        t.join();
    }
    try{
        Thread.sleep(1000);             
    } catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("main");
}

class ThreadTest extends Thread{
    private int i = 0;
    public void run(){
        for(int i = 0;i<10;i++){
            try{
                Thread.sleep(1000);             
            } catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("thread");
        }
    }
}

join方法的源码如下:

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

重点在于wait()。可能许多人有疑问,为什么调用wait()方法后,线程对象仍可以继续执行,实际上。wait()方法的调用是在join()方法里,也就是说在主线程中(理解这一点很重要),而线程对象的run()方法并不受while()循环的影响,反而是主线程因为循环而停止执行。因此,直到线程对象执行完,isAlive()方法返回false,主线程才跳出while,重新执行。
补充一句,线程的join方法常用于有明确先后顺序需求的场景。

ThreadTest t1 = new ThreadTest();
ThreadTest t2 = new ThreadTest();
ThreadTest t3 = new ThreadTest();
//t1先执行完后执行t2,在执行t3。
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();

yield()

源码的注释解释,该方法表明当前线程愿意放弃cpu给同等优先级的其他线程。但也可能该线程放弃cpu在进入到可执行状态后马上又被执行。yield执行后线程进入就绪状态。

守护线程

Daemon Thread,又译为后台线程,是指在程序运行时在后台提供一种通用服务的线程,当所有非后台线程结束时,程序就会终止。这意味着,程序的终止不依赖于守护线程,程序终止的同时会杀死所有守护线程。
通过setDaemon(true)来设置。
参考程序如下:

public class Test01{
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<5;i++){
            Thread t = new Thread(new DaemonThread());
            t.setDaemon(true);
            t.start();
        }
        TimeUnit.MILLISECONDS.sleep(180);
    }
}
class DaemonThread implements Runnable{

    public void run() {
        try{
            while (true){
                TimeUnit.MILLISECONDS.sleep(100);
                System.out.println(Thread.currentThread()+" "+this);
            }
        } catch (InterruptedException e) {
            System.out.println("sleep interrupted");
        }finally {
            System.out.println(Thread.currentThread()+" over");
        }
    }
}

上述代码的输出结果如下:

Thread[Thread-4,5,main] DaemonThread@56384cb7
Thread[Thread-0,5,main] DaemonThread@546431f0
Thread[Thread-1,5,main] DaemonThread@383a0ba
Thread[Thread-2,5,main] DaemonThread@95458f7
Thread[Thread-3,5,main] DaemonThread@1857637d

由此说明了几个问题:

  • 主线程结束后,守护线程也都停止了
  • 守护线程被打断后finally的代码块未被执行
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值