线程总结(一):初识线程

线程和进程

在讲线程之前,先说一下进程。

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早起面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

进程和线程的基本定义如下:

  • 进程是程序的一次执行
  • 线程是 CPU 的基本调度单位

在一篇博文中看到一个很好的类比:
计算机的核心是 CPU,它承担了所有的计算任务。它就像一个工厂,时刻在运行着。假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义是,单个的 CPU 一次只能运行一个任务。

进程就好比工程的车间,它代表 CPU 所能处理的单个任务。任一时期,CPU 总是运行一个进程,其他进程处于非运行状态。

一个车间里,可以有很多工人,他们协同完成一个任务。线程就好比车间里的工人,一个进程可以包含多个线程。

车间的控件是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内容控件是共享的,每个线程都可以使用这些共享内存。

可是每个房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束之后,才能使用这一块内存。

一个防止他们进入的简单方法,就是门口加一把锁。先到的人锁上们,后到的人看到锁,就在门口排队,等锁打开再进去。这就叫“互斥锁”,用来放置多个线程同时读写某一块内存区域。

还有一些房间,可以同时容纳 n 个人,比如厨房。也就是说,如果人数大于 n,多出来的人只能在外面等着,这好比某些内存区域,只能供给固定数目的线程使用。

这时候的解决办法,就是在门口挂 n 把要是,进去的人就取一把要是,等出来时再把要是挂回去。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做“信号量”,用来保证多个线程不会相互冲突。


每个 Java 程序都至少有一个线程——主线程。当一个 Java 程序启动的时候,JVM 会创建主线程,并在该线程中调用程序的 main() 方法。


线程有什么用

在 Java 程序中使用线程有以下好处:

  • 进程之间不能共享内存,但线程之间共享内存却非常容易。
  • 系统创建进程时需要为该进程重新非配系统资源,但创建线程则代价要小很多,因此使用多线程来实现多任务并发效率会比多进程高很多。
  • Java 内置了多线程功能的支持,而不是单纯的作为底层操作系统的调度方式,从而简化了 Java的多线程编程。

譬如之前的 RandomAccessFile 类的例子,采用多个线程去复制文件效率要比单纯的使用 FileInputStream/FileOutputStream 高很多。


线程的生命周期

线程新建(NEW)状态

当线程对象被新建出来的时候处于新建状态,新建线程有两种方法

  • 新建继承自 Thread 的子类,并实现父类的 run 方法
public class TestThread {

    public static void main(String[] args) {
        Thread thread = new MyCustomThread();//创建线程对象,此时线程处于 新建状态 
    }
}

class MyCustomThread extends Thread {
    @Override
    public void run() {
        // 线程执行内容
    }
}
  • 声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
public class TestThread {

    public static void main(String[] args) {
        Thread thread = new Thread(new MyCustomThread());//创建线程对象,此时线程处于 新建状态 也叫 NEW 状态
    }
}

class MyCustomThread implements Runnable {
    @Override
    public void run() {
        // 线程执行内容
    }
}

线程就绪(RUNNABLE)状态

线程对象被创建出来之后,并没有执行,只是还需要调用对象的 start() 方法:

thread.start()  //执行线程,线程进入就绪状态,也叫RUNNABLE状态

事实上,线程进入就绪状态之后也并不一定会立即执行,只是告诉线程调度器说:“我可以执行了!来执行我吧!”,至于何时执行,还需要依靠线程调度器的调度。

线程阻塞(BLOCKED)状态

当两个线程争夺一个线程锁的时候,没有争夺到线程锁的线程会处于阻塞状态。关于线程锁以后会专门讲到。

public class TestThread {

    public static void main(String[] args) {

        Object object = new Object();

        Thread thread1 = new MyCustomThread(object, "线程1");

        Thread thread2 = new MyCustomThread(object, "线程2");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
            System.out
                    .println(thread1.getName() + " 状态: " + thread1.getState());
            System.out
                    .println(thread2.getName() + " 状态: " + thread2.getState());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class MyCustomThread extends Thread {

    Object object;
    String threadName;

    public MyCustomThread(Object object, String threadName) {
        this.object = object;
        this.threadName = threadName;
        this.setName(threadName);
    }

    @Override
    public void run() {

        synchronized (object) {
            try {
                System.out.println(this.getName() + " 执行了");

                FileInputStream fis = new FileInputStream(new File(
                        "D:\\李宗盛 - 山丘.wav"));
                int i = 0;
                while ((i = fis.read()) != -1) {

                }
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

当一个线程开始执行时,会获取到线程锁,另一个线程则会处于阻塞状态也叫BLOCKED状态。

等待状态(WAITING)和定时等待(TIMED_WAITING)状态

  • 当线程调用了不带指定时间参数的 join()wait()方法后,那么该线程则进入等待状态,
  • 当线程调用了sleep()带时间参数的 join() 方法带时间参数的 wait() 方法
public class TestThread {

    public static void main(String[] args) {
        Thread thread = new MyCustomThread("自定义线程",Thread.currentThread());
        thread.start();

        try {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + (i + 1));

                if (i == 3) {
                    thread.join(2000);
                }
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

class MyCustomThread extends Thread {

    private String threadName;
    private Thread thread;

    public MyCustomThread(String threadName, Thread thread) {
        this.threadName = threadName;
        this.thread = thread;
        this.setName(threadName);
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                Thread.sleep(1000);
                System.out.println(this.getName() + (i + 1));
                System.out.println(thread.getName() + " 状态: " + thread.getState());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

停止状态(TERMINATED)

当线程执行完成之后进入停止状态,有如下几种方式可以使线程进入停止状态:

  • 线程体执行完成。
  • 线程遇到 Exception 或者 Error 终止。
  • 调用线程对象的 stop() 方法,但该方法已被废除,不建议使用。
public class TestThread {

    public static void main(String[] args) {
        Thread thread = new MyCustomThread("自定义线程", Thread.currentThread());
        thread.start();

        try {
            Thread.sleep(5000);
            System.out.println(thread.getName() + "   ---   "
                    + thread.getState());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

class MyCustomThread extends Thread {

    private String threadName;
    private Thread thread;

    public MyCustomThread(String threadName, Thread thread) {
        this.threadName = threadName;
        this.thread = thread;
        this.setName(threadName);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                break;
            }
        }
    }
}
  • 可以调用线程对象的 isAlive() 方法来判断该线程是否是活动线程,处于 NEW 和 TERMINATED 的状态返回 false,其余的状态返回 true。
  • 当线程死亡了就是死亡了,不能再调用 start() 方法去启动它,否则会抛出异常。

线程的标识和优先级

线程的名称及标识符

每个线程都有一个名称,这个名称由系统默认设定,也可以自己设定。

通过调用 Thread 类的currentThread()方法来获取当前运行的线程对象,通过setName来设置线程名,通过getName()来获取线程名。

通过setId()来设置线程名,通过getId()来获取线程名。

public class TestThread {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        System.out.println("main线程ID: " + Thread.currentThread().getId()
                + ";main线程名: " + Thread.currentThread().getName());
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        this.setName("自定义线程");
        System.out.println("这是 " + this.getName() + ";ID为: " + this.getId());
    }
}

运行结果

这是 自定义线程;ID为: 9
main线程ID: 1;main线程名: main

线程优先级

通过setPriority(int newPriority) 来设置线程的优先级,通过getPriority() 来获取线程的优先级。

public class TestThread {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();

        System.out.println("主线程名: " + Thread.currentThread().getName()
                + ";优先级是 " + Thread.currentThread().getPriority());
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        this.setName("自定义线程");
        this.setPriority(10);
        System.out.println("这是 " + this.getName() + ";优先级是 "
                + this.getPriority());
    }
}

一般情况下,我们很少去更改线程的优先级,因为通常这样只会把问题移到别的地方。大多数程序应该完全避免更改线程优先级。


参考资料