java并发基础--多线程

一、多线程基础知识

1.进程和线程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

线程:进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。

进程和线程的区别:

  • 进程有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。线程堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。
  • 进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。
  • 程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行,线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。
  • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点
  • 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间

注意:

  • 因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
  • Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
  • 创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

2.并行和并发

并行:指两个或多个时间在同一时刻发生(同时发生)
并发:指两个或多个事件在一个时间段内发生。

注意:单核处理器的计算机肯定不能并行的处理多个任务,只能是多个任务交替的在单个 CPU 上运行。

3.线程状态

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。
  • 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。
  • 阻塞(BLOCKED):表线程阻塞于锁。
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
  • 终止(TERMINATED):表示该线程已经执行完毕。

4.守护线程和非守护线程

守护线程:和主线程一起结束的线程,叫守护线程,例如典型的守护线程---(GC)垃圾回收线程。
非守护线程:主线程的结束不影响线程的执行的线程,也叫用户线程。例如用户线程。

注意:

  • 用户可以将非守护线程设置为守护线程,利用Thread.setDaemon()即可。thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程
  • 在守护线程中产生的新线程也是守护线程
  • 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

5.多线程和单线程的选择

对于单核CPU,如果是CPU密集型任务,如解压文件,多线程的性能反而不如单线程性能,因为解压文件需要一直占用CPU资源,如果采用多线程,线程切换导致的开销反而会让性能下降。但是对于比如交互类型的任务,肯定是需要使用多线程的、而对于多核CPU,对于解压文件来说,多线程肯定优于单线程,因为多个线程能够更加充分利用每个核的资源。虽然多线程能够提升程序性能,但是相对于单线程来说,它的编程要复杂地多,要考虑线程安全问题。因此,在实际编程过程中,要根据实际情况具体选择。

二、多线程--java线程的创建

java中创建线程的话,一般有两种方式:1)继承Thread类;2)实现Runnable接口。

1.继承Thread类

 1 /**      
 2 *    继承Thread类,需要重写 Thread 类的 run()方法   
 3 */
 4 public class ThreadDemo extends Thread{
 5     
 6     @Override
 7     public void run() {
 8         System.out.println("子线程执行完毕");
 9     }
10     public static void main(String[] args) {
11         ThreadDemo t1 = new ThreadDemo(); 
12         t1.start();//启动线程,调用run
13         System.out.println("主线程执行完毕");
14     }
15 
16 }

结果输出:

主线程执行完毕
子线程执行完毕
或者
子线程执行完毕
主线程执行完毕

通过继承Thread类创建线程类后,通过start()方法启动线程,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

2.实现Runnable接口

 1 public class RunableDemo implements Runnable{
 2 
 3     @Override
 4     public void run() {
 5         System.out.println("子线程执行完毕1");
 6     }
 7     
 8     public static void main(String[] args) {
 9         new Thread(new RunableDemo()).start();
10         System.out.println("主线程执行完毕1");
11     }
12 
13 }

结果输出:

子线程执行完毕1
主线程执行完毕1
或
主线程执行完毕1
子线程执行完毕1

通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,与普通的方法调用没有任何区别。

3.优先选择通过实现接口的方式来实现多线程的原因

  • 可以避免由于Java的单继承特性而带来的局限;
  • 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;
  • 适合多个相同程序代码的线程区处理同一资源的情况。

猜你喜欢

转载自www.cnblogs.com/liupiao/p/9318528.html