一:多线程
现在的操作系统就是多任务操作系统, 多线程是实现多任务的一种方式。
进程就是指一个内存中运行的应用程序。 每一个进程都有自己独有的一个内存空间,一个进程中可以启动多个线程。
线程就是指进程中的一个执行流程, 一个进程中可以运行多个线程。 线程总是属于某一个进程,进程中的 多个线程共享进程的内存。
二: java中的线程
在java中.“线程”值得就是两件事情:
1: java.lang.Thread类的一个实例
- 线程的执行
使用 java.lang.Thread类或者是 java.lang.Runnable接口来编写代码,从而实现实例化、启动新的线程、
一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死与堆上。
java中,每一个线程都有一个调用栈, 即使不在程序中创建任何线程,线程也会在后台运行着。
一旦创建一个新的线程,就是产生一个新的调用栈。
线程分为两类: 用户线程和守候线程。
当所有的用户线程执行完毕的时候,JVM自动关闭, 但是 守候线程不独立与JVM,守候线程通常是由操作系统或者是用户自己创建的。
三: 创建与启动
- 定义线程
扩展java.lang.Thread类
此类中有一个run()方法
- 实例化线程
- 启动线程
在线程的Thread对象上调用start()方法。
在调用start方法之前,线程处于新状态中,新状态指的是有一个Thread
在调用start()方法之后,发生了一系列复杂的事情:
启动新的执行线程(具有新的调用栈)
该线程从新状态转移到可运行状态
当该线程获得机会执行时,其目标run()方法将运行
线程栈模型和线程的变量
要理解线程调度的原理,线程执行的过程,必须先理解 线程栈模型。
线程栈指的是 某时某刻内存中线程调度的栈信息。 当前调用的方法总是处于栈顶。 线程栈的内容是随着程序的运行动态变化的,所以,研究线程栈时必须选择一个运行的时刻。 实际上指的就是指代码运行到什么地方。
线程状态装换:
一: 线程状态
线程的状态装换是线程控制的基础。 线程状态总分为五大状态: 生 死 可运行 运行 等待/阻塞
- 新状态: 线程对象已经创建,但是还没有在其上调用start()方法。
- 可运行状态: 当线程有资格运行,但调度程序还没有把它设为运行线程时线程所处的状态。 当start()方法调用时,线程首先进入可运行状态,在线程运行之后或者从阻塞、等待或睡眠状态回来后, 也返回可运行状态。
- 运行状态: 线程调度程序从可运行池子中选择一个线程作为 当前线程时线程所处的状态。 这也是线程进入可运行状态唯一方式。
- 等待/阻塞/睡眠状态: 这是线程有资格运行时它所处的状态,实际上这三个状态组合成一种,其共同点就是 线程依旧是活的。但是当前没有条件执行。 换句话说:它是可以执行的,
- 死亡态: 当线程的run()方法完成时就认为它死去。 虽然这个线程有可能是活的,但是,他已经不是一个单独执行的线程,因为线程一旦死亡,就不可能复生, 如果在一个死亡的线程上调用start() 方法, 会抛出 java.lang.lllengalThreadStateException异常。
二: 阻止线程执行
对于线程的阻止:
睡眠
等待
因为需要一个对象的锁定而被阻塞
- 睡眠
- Thread.sleep(long millis) 和 Thread.sleep(long millis, int nanos) 静态方法强制当前正在执行的线程休眠,当线程休眠时,它入睡在某一个地方,在苏醒之前不会返回到可运行状态,当睡眠时间到期后,线程回到可运行状态。
- 线程休眠的原因: 线程执行太快,或者需要强制进入下一轮时,因为java规范不保证合理的转换、
- 睡眠的实现: 调用静态方法
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置: 为了让其他线程有机会执行,可以将Thread.sleep() 的调用放在run() 之内。这样就会保证该线程在执行过程中会休眠。
注意: 1. 线程睡眠是帮助所有线程获得运行机会的最好方法。+-
线程睡眠到期后自动苏醒,会进入到可运行状态,而不是运行状态。 sleep()中指定的时间指的是线程不会运行的最短时间。
- sleep()是静态方法。只能控制当前正在运行的线程。
- 线程的优先级和线程让步 yield() 线程的让步就是通过Thread.yield()来实现的, yield()方法的作用就是:暂停当前执行的线程对象,并执行其他线程。
线程总是存在优先级的,优先级范围在1~10之间,JVM线程调度程序是基于优先级的抢先调度机制,在大多数情况下,当前运行的线程优先级将大于或等于线程池中的任何线程的线程级。
有一点需要,在设计线程的时候,不能依赖线程的优先级,因为线程的优先级是没有保障的, 只能把线程的优先级作为一种提高线程效率的方法,但是呢,保证程序不依赖这种操作。
当线程池中的线程都具有相同的优先级, 那么调度程序的JVM 实现自由选择他自己喜欢的线程,这个时候线程调度有两种选择: 第一种选择就是: 选择一个线程运行,知道它阻塞或者是运行完毕, 二选择: 时间分片,为每一个线程提供均等的运行机会。
设置线程的优先级: 线程默认的优先级是创建它的执行线程的优先级,可以通过 setPriority(int new Priority());。
Thread td = new MyThread();
td.setPriority(5);
td.start();
线程优先级为1~10之间,JVM不会改变线程的优先级,然后,1~10之间的值是不能保证的,一些JVM可能不会识别10个不同的值,而是将这些优先级进行合并, 变成了少于10个的优先级,意思就是说:将两个或多个优先级的线程可能会被隐射成一个优先级。
线程默认优先级是5,Thread类中有三个常量,定义线程
static int MAX_PRIORITY
线程可以具有的最高优先级
static int MIN_PRIORITY
线程具有的最低优先级
static int MORM_PRIORITY
分配给线程的默认优先级
- Thread.yield()方法
Thread.yield() : 暂停当前执行的线程对象,执行其他线程。
yeild() 应该做的就是让当前线程回到可运行状态,让其他线程获得运行机会。 实际上并不能完全做到线程让步的目的,因为让步的线程还是有可能会被线程调度再次选中。
- join()方法
Thread的非静态方法join()让一个线程B加入到线程A后面,等线程A执行完毕后,才可以执行线程B,必须是线程A执行完才可以执行线程B。
Thread t = new MyThread();
t.start();
t.join();
另外,join()方法还具有超时限制的重载版本, 例如thread.join(5000); 则让线程等待5000毫秒,如果超过这个时间的话,则停止等待,变为可运行状态。
线程的同步与锁
一: 同步问题的提出
线程的同步是为了防止多个线程同时访问一个数据对象时,对数据造成的破坏。
二: 同步和锁定
- 锁的原理
- Java中每一个对象都有一个内置锁
释放锁: 持锁线程退出了synchronized() 同步方法或者代码块。
关于锁和同步,有几个要点:
只能同步方法,而不能同步变量和类
每一个对象都有一个锁,在提到同步时,应该清楚在什么地方上同步,在哪一个对象上同步、
不必同步类中的所有的方法,类可以同时拥有同步和非同步的方法。
一个线程在对象上获得一个锁,就没有其他线程可以进入该对象的勒种的任何一个同步方法。
线程睡眠时候,它所持有的任何锁都不会被释放。
线程可以同时获得多个锁。
在使用同步代码块的时候,应该指定在哪一个对象上同步,也就是说获取哪个对象的锁。
三: 静态方法同步
要同步静态方法,需要一个用于整个类对象的锁,这个对象 就是这个类。
四:如果线程不能获得锁会怎样?
如果线程想进入同步方法,但是锁还是被占用着,这个时候呢,线程在这个对象上就会阻塞。 实际上,线程进入该对象的一种池中,必须在那里等待,知道其锁被释放, 该线程才会变为可运行状态或者是运行状态。
当考虑阻塞时,一定要注意是哪个对象正在被锁定;
- 调用同一个对象中非静态同步方法的线程将彼此阻塞, 如果是不同的对象,则每一个线程都有自己对象的锁,线程间彼此互不干涉。
- 调用同一个类中的静态同步方法的线程将会被阻塞。它们都是锁定在相同的Class对象上。
- 静态同步方法和非静态同步方法将永远不会彼此阻塞,因为非静态同步方法锁定在该类的对象上, 静态同步方法则锁定在该Class对象上。
- 对于同步代码块,要看清楚设么对象已经被锁定(synchronized后面的内容). 在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会被阻塞。
五: 啥时候需要同步
在多个线程同时访问互斥(可交换)的数据时候,应该同步以保护数据,确保两个线程不会同时去改变它。
对于非静态字段中可更改的数据,通常使用非静态方法访问。
对于静态字段中可更改的数据,通常使用静态方法访问。
六:线程安全类
当一个类已经很好的同步以保护好它的数据时,这个类就叫做“线程安全的”
七:线程死锁
八:线程同步小结
- 线程同步的目的就是为了保护多个线程访问一个资源时对资源的破坏。
- 线程同步方法是通过锁来实现的,因为每一个对象内部都只有一个锁,这个锁与一个特定的对象相关联,线程一旦获取了这个对象的锁,那么其他访问该对象的线程就无法再次访问该对象的其他同步方法。
- 对于同步,要时刻清楚在哪个对象上同步。这才是关键。
- 当多个线程等待一个对象锁时,没有获取到锁的线程将会发生阻塞。
- 死锁就是线程之间相互之间等待造成的。
线程的交互
void notity() : 指的就是唤醒在此对象监视器上等待的单个线程。
void notityAll() : 指的就是唤醒再此对象监视器上等待的所有线程。
void wait() : 指的就是导致当前线程睡眠,知道其他的线程调用此对象的notity()方法时候或者是调用notityAll()方法。
void wait(long timeout): 导致当前的线程等待,知道其他得线程调用此对象的notity()方法或者是超过指定的时间量。
void wait(long timeout,int nanos): 导致当前的线程等待,知道其他的线程调用此对象的notity()方法或者是调用notityAll()方法,或者是超过时间时间量。
wait() 、notity()、notityAll()、 都是Object的实例方法。
二: 多个线程在等待一个对象锁时候使用notityAll()
线程的让步含义就是使当前运行着线程让出CPU资源,但是让给谁不知道,仅仅是让出,线程状态回到可运行状态。
线程的让步使用Thread.yield()方法,yield() 为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程。
线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。