java多线程与线程池(一):多线程概述

引用自https://www.runoob.com/java/java-multithreading.html

Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

一个线程也有他的生命周期:

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

其实这与操作系统中的概念,没有太大的区别。

下面来说说如何在java中创建一个线程:

1)通过继承Thread类:

public class MyThread extends Thread{
    private int i=0;
    public MyThread(){}

    @Override
    public synchronized void start() {
        super.start();
    }

    @Override
    public void run() {
        for (i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName() + i);
            if(i == 3){
                Thread thread1 = new MyThread();
                Thread thread2 = new MyThread();
                thread1.start();
                thread2.start();
            }
        }
    }
}

运行结果如下:

如果在if里加上sleep(100),那么main4就会在最下面输出。这里还有一点要注意的是,在这种实现多线程方法下,调用start()即会调用对应线程的run(),无需start()后再调用run()。如果再调用run(),就会变成这样:

图中带空格的main i,就是因为main中直接调用线程类的run()方法导致的,因为是main直接调用线程的run()方法,所以currentThread.getName()结果就是main(下面线程的结果我没有截进来)

2)通过实现Runnable接口来创建线程

public class MyThread implements Runnable{
    Thread thread;
    MyThread(){}
    public void start(){
        if(thread == null){
            thread = new Thread(this);
            thread.start();
        }
    }
    @Override
    public void run() {
        int i;
        for (i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName() + i);
            if(i == 3){
                MyThread thread1 = new MyThread();
                MyThread thread2 = new MyThread();
                thread1.start();
                thread2.start();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这种方法下就需要自己在线程类下实例化Thread,实例化时目标选为this,再调用start()方法,之后再main里执行start()时,被启动的线程便会自动调用其run()方法。

3)通过Callable和Future创建线程

public class TestThread implements Callable<Integer> {
    public static void main(String[] args) {
        TestThread thread = new TestThread();
        FutureTask<Integer> futureTask = new FutureTask<>(thread);
        for(int i = 0;i<5;i++){
            System.out.println(Thread.currentThread().getName() + i);
            if(i==2){
                new Thread(futureTask).start();
            }
        }
        try {
            System.out.println("子线程的返回值:"+ futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    @Override
    public Integer call() throws Exception {
        int i = 0;
        for(;i<3;i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }
}

说实话这种方法我觉得异常的繁琐,但是使用他的优点是线程结束后可以有返回值,想必也有它独特的用处,先了解着。

创建线程的三种方式的对比:

  • 1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。

  • 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

之后的内容:

  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复
  • 线程池

多线程使用时的注意事项:

有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。

通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。

请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!

原文地址:https://www.cnblogs.com/MYoda/p/11231707.html