进程:
正在运行的程序的实例(正在进行的程序)
指运行中的程序,只要运行起来就会占用内存空间
比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间,当我们使用微信,又启动了一个进程,操作系统将为微信分配新的内存空间
线程:
是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。(每个正在进行的程序干了多件事,每个独立的流程叫线程)
(微信可以接受消息,也可以发送消息/打开main方法,可以执行多个方法)
线程是由进程创建的,是进程的一个实体
一个进程可以有多个线程
比如打开百度云(打开进程),下载任务,任务1(线程1),任务2(线程2)。
多线程 执行原理 ( 并发执行原理 )
CPU核心: 每一个计算机中CPU核心, 就相当于人的一个大脑, 可以执行一件事情.
时间片: 将CPU未来可以用于执行的时间 ,碎片化, 拆分成一个个的小的时间片段. 每一个时间片段的大小 可以都不足一毫秒.
程序在争抢到时间片段后, 会进行执行 .
Java实现线程的两种方式
- 继承Thread类,重写run方法
当Thread调用start方法时,本地方法start0会通过C和C++代码开启新的执行路径(线程)
新线程会将Thread的run方法加载进线程执行MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); t1.start(); t2.start(); } static class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("甲:"+i); } } } static class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("乙:"+i); } }
- 编写一个Runnable(接口,抽象方法run)的实现类,并创建对象
MyRunnable1 m1 = new MyRunnable1(); Thread t1 = new Thread(m1); MyRunnable2 m2 = new MyRunnable2(); Thread t2 = new Thread(m2); t1.start(); t2.start(); } static class MyRunnable1 implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("甲:"+i); } } } static class MyRunnable2 implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("乙:"+i); } } }
- 方式1的简写,直接创建匿名内部类
new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("甲:"+i); } } }.start(); new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("乙:"+i); } } }.start();
- 方式2的简写
Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("甲:"+i); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("乙:"+i); } } }); t1.start(); t2.start();
- 用第一种还是第二种?
效率其实是一样的
第一种继承Thread类,假设还需要继承别的类,Java是单继承,导致无法再继承别的类,但是第二种接口是允许多实现的,第二种更加灵活。 - Thread常用方法
面试题:调用start()与调用run()的区别在哪?
start():-->start0-->C代码-->创建线程-->在新线程加载run方法,执行
当Thread调用start方法时,本地方法start0会通过C和C++代码开启新的执行路径(线程), 新线程会将Thread的run方法加载进线程执行
run():run 执行当前的线程
面试题:Java中存在单线程程序吗?
不存在。Main和GC线程,是开始到结束始终存在的
Thread类的常用静态方法
sleep() 休眠
currentThread() Thread.currentThread().getName()获取线程名字yield() 此方法执行时所在的线程, 让出当前时间片. 用于降低线程的活跃度
Thread类的常用非静态方法
start() 启动此线程所表示的线程对象,在新的线程中执行run方法
stop()停止线程,已过时
interrupt()中断线程,此方法用于在某一线程上加入中断标记
守护线程
线程分为两类:用户线程+守护线程。当一个软件不存在任何用户线程时,守护线程自动死亡
用户线程
通常情况下, 我们自己开启的线程都是用户线程, 所有的用户线程都是一条单独的执行路径, 一个软件的所有用户线程执行完毕, 程序会死亡 .
守护线程
程序中存在一种特殊的线程, 叫做守护线程, 可以理解为 : 守护用户线程 .
当程序中所有的用户线程都死亡了. 守护线程无论是否执行完毕, 都会自动死亡.
Java中的垃圾回收器 GC 其实就是守护线程.
.setDaemon(true) 随着死亡
线程优先级
.setPriority()
A方法在新的线程中执行,A中调用其他方法,调用的方法也一定在新的线程中执行
线程的生命周期
一个线程被创建以后,进入就绪状态,开始抢时间片去执行,执行完毕死亡。中间遇到特殊状态会等在那,有可能休眠一段时间,有可能是在读取一个文件,有可能是在做一些别的事。
1.创建
Plain Text
public class MyThread extends Thread{
@Override
public void run() {
//...
}
}
//新建就是new出对象
MyThread thread = new MyThread();
当程序使用new关键字创建了一个线程之后,该线程就处于一个新建状态(初始状态),此时它和其他Java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
2.就绪
当线程对象调用了Thread.start()方法之后,该线程处于就绪状态。
Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,它只是表示该线程可以运行了。从start()源码中看出,start后添加到了线程列表中,接着在native层添加到VM中,至于该线程何时开始运行,取决于JVM里线程调度器的调度(如果OS调度选中了,就会进入到运行状态)。
3.运行
当线程对象调用了Thread.start()方法之后,该线程处于就绪状态。
添加到了线程列表中,如果OS调度选中了,就会进入到运行状态
4.阻塞
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况大概三种:
- 1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
- 2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 3、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)。
- 线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
- 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。唤醒线程后,就转为就绪(Runnable)状态。
- 线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
- 线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
- 线程I/O:线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。
- 线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意性的,并在对实现做出决定时发生。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
5.死亡
线程会以以下三种方式之一结束,结束后就处于死亡状态:
- run()方法执行完成,线程正常结束。
- 线程抛出一个未捕获的Exception或Error。
- 直接调用该线程的stop()方法来结束该线程——该方法会产生固有的不安全性,不推荐.
线程同步(线程安全问题 临界资源出错问题)
同步即排队,异步即并行
线程同步的两种实现方式:同步方法+同步代码块
两种方式都是通过加锁来实现的
同步代码块
格式:
synchronized(Object 锁对象){
}
在同步代码块时 我们手动指定锁对象时 . 多个线程必须使用同一个锁 ,才可以完成同步操作 !
static Object o = new Object();
同步方法
非静态的同步方法的锁对象为this 看是否同一个对象
静态方法的锁对象 , 是我们的类信息对象 (类名.class). 启动类.class
格式
权限修饰符 synchronized 返回值声明 方法名(形式参数列表){
}