16、知识点总结
1、线程和进程
进程:进程是指正在运行的程序。
线程:线程是进程中的执行单元,一个进程至少有一个线程。
多线程:一个程序中有多个线程在同时执行。
单线程程序:若有多个任务只能依次执行
多线程程序:若有多个任务可以同时执行
2、程序运行原理
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
3、多线程
多线程:一个程序中有多个线程在同时执行。
4、主线程
jvm启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程。当程序的主线程执行时,如果遇到了循环而导致程序在指定位置停留时间过长,则无法马上执行下面的程序,需要等待循环结束后能够执行。
那么,能否实现一个主线程负责执行其中一个循环,再由另一个线程负责其他代码的执行,最终实现多部分代码同时执行的效果?
能够实现同时执行,通过Java中的多线程技术来解决该问题。
5、创建线程方式
1、继承Thread类,重写 Thread 类的 run 方法。然后就是分配并启动该子类的实例
2、实现Runnable 接口,该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
继承Thread类和实现Runnable接口有啥区别呢?
(1)实现Runnable接口避免了单继承的局限性,所以较为常用。
(2)实现Runnable接口的方式,更加的符合面向对象思想,其实线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,又有线程任务。
实现runnable接口,将线程任务单独分离出来封装成对象,在创建thread时作为一个参数传递并启动。Runnable接口对线程对象和线程任务进行解耦。
(将这个子类对象作为参数传递给Thread的构造函数,)类型就是Runnable接口类型。
6、线程对象调用 run方法和调用start方法区别?
线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。
7、多线程的内存图解
多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
8、获取线程名称
Thread.currentThread()获取当前线程对象
Thread.currentThread().getName();获取当前线程对象的名称
修改线程名字:用thread对象调用setName()写在开启线程之前
9、线程的基本状态以及状态之间的关系?
【new:新建状态;等待状态;runnable:就绪状态;running:运行状态;blocked:阻塞状态;dead:死亡状态】
1.新建
【新建一个线程对象,分配内存】
用new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。
2.等待
创建线程对象开始到调用start方法前,线程处于等待状态。
3.就绪
线程调用它的start()方法,进入就绪状态。处于这个状态的线程位于Java虚拟机的可运行池中,等待cpu的使用权。
线程进入就绪状态的情况
线程调用start()方法
正在运行的线程执行yield()方法
处于阻塞状态的线程:休眠状态的线程sleep状态超时
4.运行状态
处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。
线程进入运行状态的情况
只有处于就绪状态的线程才有机会转到运行状态。
5.阻塞状态
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。
阻塞状态分为三种:
(1)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(2)同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。
(3)其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。
6.死亡状态
当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。
9、线程的sleep()方法和yield()方法有什么区别?
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。两者的区别在于:
(1)sleep()方法在放弃CPU,把运行机会让给其他线程时,不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
(2)当线程执行了sleep(long millis)方法,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法,将转到就绪状态。
(3)sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常。
(4)sleep()方法比yield()方法具有更好的可移植性。
10、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,他们有什么区别?
(1)sleep 是线程类(Thread)的方法,wait 是Object 类的方法。
(2)最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
(3)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
(4)sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
12、多线程线程同步
多线程同时操作一个共享数据,往往出现安全问题。
解决安全性问题的根本办法:当一个线程进入数据操作的时候,无论是否休眠,其他线程只能等待。保证了数据操作只能有一个线程来完成
其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
解决安全问题,Java提供了同步技术
线程同步的方式有两种:
方式1:同步代码块
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
加了同步之后,线程进同步需要判断锁,获取锁,出同步需要释放锁,导致程序运行速度的下降
方式2:同步方法
好处:代码简洁
13、Lock接口
因为使用同步代码块或者同步方法时,线程在同步中时锁被获取我们不知道在哪释放,如果此时线程出现异常,锁对象将无法被释放。
所以在jdk1.5中有一个新特性就是Lock接口。
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的常用方法
使用Lock接口的实现类ReentrantLock()获取lock锁对象。
使用Lock接口实现同步步骤:
1、使用Lock接口的实现类ReentrantLock()获取lock锁对象
Lock ck = new ReentrantLock();
2、在可能会产生线程安全的代码前后分别加上ck.lock();和ck.unlock()
14、死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
多线程出现同步嵌套
多个同步,同步中有嵌套。锁对象具有唯一性。
当一个线程进入一个同步中时,再次进入里边的同步时的锁被其他线程所拿走,而刚好这个拿走锁的线程进入里边同步的锁被刚才那个线程拿着,两边都无法进入嵌套的同步中也就无法释放锁,所以才出现了死锁。
15、等待唤醒机制
通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
等待唤醒机制所涉及到的方法:
wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
3)为什么wait(),notify()方法要和synchronized一起使用?
因为wait()方法是通知当前线程等待并释放对象锁,notify()方法是通知等待此对象锁的线程重新获得对象锁,然而,如果没有获得对象锁,wait方法和notify方法都是没有意义的,即必须先获得对象锁,才能对对象锁进行操作,于是,才必须把notify和wait方法写到synchronized方法或是synchronized代码块中了。