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{ public static void main(String[] args) { TestThread thread = new TestThread(); FutureTask 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 花费在上下文的切换的时间将多于执行程序的时间!