Java——多线程
一、进程
进程简单地说就是在多任务操作系统中,每个独立执行的程序,所以进程也就是“正在进行的程序”。(windows os 我们可以在任务管理器中看到进程)
二、线程
线程是程序运行的基本执行单元。当操作系统执行一个程序时,会在系统中建立一个进程,该进程必须至少建立一个线程(这个线程被称为主线程)作为这个程序运行的入口点。因此,在操作系统中运行的任何程序都至少有一个线程。
三、并行和并发
- 并行:指两个或多个线程在同一时刻执行(同时执行)
- 并发:指两个或多个线程在同一时间段内发生(交替执行)
四、线程的调度
进程启动后线程开始执行任务,多线程并发处理任务可以大大提高程序的执行效率。但一个CPU核心在同一时间只能执行一个线程,不能同时执行两个任务。为了提高程序运行的效率,操作系统会在一个线程空闲时会撤下这个线程,并且会让其他的线程来执行,这种方式叫线程调度。
JVM的线程调度:
JVM的线程调度使用抢占式调度,Java中线程会按优先级分配CPU时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。
- 通常程序在运行时,并没有把计算机的硬件资源全部消耗掉,也就意味着计算机还有很多空闲资源,可以让程序利用起来,加快程序的运行
- 并发编程,就是把CPU的运行资源最大化的利用起来
- 线程就是由CPU来控制和调用
- 线程调度:CPU来调用线程,执行程序
- 分时调度:多个线程平均分配CPU的时间片
- 抢占式调用:CPU会根据线程的优先级,来优先调用优先级高的线程(优先级高的线程更容易被CPU抢到)
五、进程和线程的内存分析
5.1、进程和进程之间的内存关系
系统中的进程是在各自独立的内存空间中运行,进程和进程之间是不共享内存的。
5.2、进程和线程之间的内存关系
一个进程中的线程可以共享系统分配给这个进程的内存空间。并且拥有一个属于线程自己的内存空间叫做线程栈。线程栈是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数(方法)时所定义的变量。
5.3、线程和线程之间的内存关系
线程自己的内存空间叫做线程栈,每个线程都有自己独立的空间互不干涉。但是对于其他公共内存是可以共享的,比如堆内存,方法区。
六、线程的创建
6.1、线程类的概述
java.lang.Thread是线程类,可以用来给进程创建线程处理任务使用。要使用线程先介绍两个比较重要的方法
- public void run() 线程执行任务的方法,是线程启动后第一个执行的方法
- public void start() 启动线程的方法;线程对象调用该方法后,Java虚拟机就会调用此线程的run方法
6.2、线程的创建方式1——继承Thread方式
Thread类本身就是指线程
步骤
- 创建一个类继承Thread类
- 在类中重写run方法(线程执行的任务放在这里)
- 创建线程对象,调用线程的start方法开启线程
例子
class ThreadTest extends Thread{ @Override public void run(){ System.out.println("ThreadTest线程开始-------------------------------"); for (int i = 0; i < 100; i++) { System.out.println("ThreadTest线程" + i); } System.out.println("ThreadTest线程结束-------------------------------"); } } public class MyThread { public static void main(String[] args) { System.out.println("main线程开始-------------------------------"); // 创建一个线程对象 Thread th = new ThreadTest(); // 启动线程 th.start(); // 开辟一个新的线程栈,并把run方法加载到线程栈中执行 for(int i = 1000; i < 1100; i++){ System.out.println("main线程" + i); } System.out.println("main线程结束-------------------------------"); } }
效果
6.3、线程的创建方式2——实现Runable方式
这种方法是使用如下构造方法,指定任务给线程执行
public Thread(Runnable target) public Thread(Runnable target, String name)
参数中的Runnable是一个接口,用来定义线程要执行的任务,如下
public interface Runnable{ public abstract void run(); } // 线程启动后就会执行该run方法了
开发中是推荐使用此方式
创建一个实现Runnable接口的子类,Runnable接口是定义线程要执行线程任务,此方式有如下优点
- 降低程序的耦合度
- 一个子类,在实现接口后,还可以继承其他父类
实现步骤:
- 定义任务类实现Runnable,并重写run方法
- 创建任务对象
- 使用含有Runnable参数的构造方法,创建线程对象并指定任务
- 调用线程的start方法,开启线程
6.3、两种方式对比
优点 | 缺点 | |
实现Runnable | 扩展性强,实现该接口的同时还可以继承其他的类 |
编程相对复杂,不能直接使用Thread类中的方法 |
继承Thread | 编程比较简单,可以直接使用Thread类中的方法 | 可扩展性差,不能再继承其他的类 |
七、线程中常用方法
7.1、getName
public String getName()
获取当前线程名称
7.2、setName
public String setName(String name)
给当前线程设置名称
7.3、start
public void start()
导致此线程开始执行;Java虚拟机调用此线程的run方法
7.4、run
public void run()
此线程要执行的任务在此处定义代码
7.5、join
public void join()
等待这个线程死亡
7.6、sleep
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
7.7、currentThread
public static Thread currentThread()
返回对当前正在执行的线程对象的引用
7.8、setPriority
public final void setPriority(int newPriority)
设置线程的优先级,优先级要在[1,10]之间,默认是5
7.9、getPriority
public final int getPriority()
获取线程的优先级
7.10、例子
class Task implements Runnable{ @Override public void run(){ System.out.println("新线程开始-------------------------------"); // 书写线程要执行的任务 for (int i = 0; i < 100; i++) { // Thread类中的静态方法 Thread t = Thread.currentThread(); // 获取当前CPU正在执行的线程对象(引用) // 获取当前线程的名称 String name = t.getName(); // 暂停2秒 if(i % 20 == 0) try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + "\t" + i); } System.out.println("新线程结束-------------------------------"); } } public class RunnableTest { public static void main(String[] args) throws InterruptedException { System.out.println("main线程开始-------------------------------"); // 基于线程任务类,创建对象 Task task = new Task(); // 创建Thread对象 Thread th = new Thread(task); // 把线程任务对象作为参数传递 // 设置线程优先级 th.setPriority(1); // 线程默认优先级5 // 给Thread线程设置名字 // 1、使用setName方法 th.setName("新创建的线程"); // 2、使用Thread类的构造方法 // Thread(String name) // Thread(Runnable r, String name) // 调用线程中的start方法 th.start(); // th.join(); for(int i = 1000; i < 1100; i++){ // String name = Thread.currentThread().getName(); System.out.println("main线程" + i); } System.out.println("main线程结束-------------------------------"); } }