线程 Thread
进程: 正在进行的程序 一个进程中至少有一个线程
进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
多进程:可以同时干很多事情,但不是同时进行,而是在程序间高效切换。
线程:在同一个进程中可以执行多个任务,而每一个任务,就是一个线程,是程序的执行单元,执行路径,是程序使用cpu的最基本单位。
单线程:程序只有一条执行路径
多线程:程序有多条执行路径
多线程存在的意义:不是提高程序的执行速度,而是为了提高应用程序的使用率。
并发:一个处理器同时处理多个任务(逻辑上的同时发生)
并行:多个处理器或是多核的处理器同时处理多个不同的任务 (物理上的同时发生)
Java 程序的运行原理:
由Java命令启动JVM,JVM启动就相当于启动了一个进程。
接着由该进程创建了一个主线程去调用 main 方法。
JVM 虚拟机的启动是单线程的还是多线程的?
多线程的。
原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。
现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,JVM的启动其实是多线程的。
线程的生命周期:
新建:创建一个线程对象
就绪:调用 start()方法后,线程有执行资格,但没有获取到执行权(也就是没有获取到CPU内存资源)
运行:获取到执行权(获取到CPU内存资源)
阻塞:没有执行资格也没有执行权,一般是调用方法 suspend()、sleep()、wait()、join()方法后线程进入阻塞状态
死亡:run()方法结束,或线程被中断(调用方法stop()或destroy())
如何实现多线程:
方式1:继承 Thread 类
A:自定义类 MyThread 继承 Thread 类。
B:MyThread 类里面重写 run()。
C:创建对象。
D:启动线程 使用 start()方法。
方式2:实现 Runnable 接口
A:自定义类 MyRunnable 实现 Runnable 接口
B:重写 run()方法
C:创建 MyRunnable 类的对象
D:创建 Thread 类的对象,并把MyRunnable 类的对象作为构造参数传递
两种实现线程的方法的区别:
实现 Runnable 接口可以避免 Java 中单一继承的局限性;
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序代码数据有效的分离。
增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
继承 Thread : 线程代码存放在 Thread 子类run()方法中。
实现 Runnable : 线程代码存放在 Runnable 接口的子类的run()方法中。
一个线程只能调用一次 start()方法,如果调用多次会出现:IllegalThreadStateException 非法的线程状态异常
题1:为什么要重写run()方法?
答:不是类中的所有代码都需要被线程执行的。
而在这个时候,为了区分哪些代码能够被线程执行,Java提供了 Thread类中的run()方法用来包含那些被线程执行的代码。
Thread 类中的 run()方法,是用于存储线程要运行的代码。
复写run()方法的目的:将自定义代码存储在run ()方法中,让线程运行。
题2:run()和 start()的区别?
答:run():仅仅是封装被线程执行的代码,直接调用是普通方法。仅仅是对象调用方法,而线程创建了却并没有执行。
start():首先启动了线程,然后再由 JVM去调用该线程的 run()方法。开启线程并执行该线程的run()方法
多线程安全问题的原因(判断一个程序是否有线程安全问题的依据):
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
同步的前提:
① 必须要有两个或者两个以上的线程
② 必须是多个线程使用同一锁
③ 必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源
线程同步:
① 线程同步就是线程排队
② 只有共享资源的读写访问才需要同步
③ 只有变量才需要同步访问
④ 多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码。
解决线程安全问题:
A:同步代码块:
synchronized(对象){
需要被同步的代码;
}
这里锁的对象是任意对象
B:同步方法
把同步加在方法上。
这里的锁对象是 this。
C:静态同步方法
把同步加在静态方法上
这里的锁对象是 当前类的字节码文件对象 文件名.class
同一个对象共享数据不需要加 static,多个不同对象共享数据要加 static
多线程有几种实现方案,分别是哪几种?
答:两种 继承 Thread 类,实现 Runnable 接口
扩展一种:实现 Callable 接口和线程池结合。重写 call()方法
同步有几种方式?分别是什么?
同步代码块,同步方法,同步静态方法
sleep()和 wait()方法的区别:
sleep():必须指时间;不释放锁
wait():可以不指定时间,也可以指定时间;释放锁
为什么wait(),notify(),notifyAll()等方法都定义在 Object 类中?
因为这些方法都调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而 Object 代表任意的对象,所以,定义在这里面。
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
分几种情况:
1.其他方法前是否加了synchronized关键字,如果没加,则能。
2.如果这个方法内部调用了wait,则可以进入其他synchronized方法。
3.如果其他个方法都加了synchronized关键字,并且内部没有调用wait,则不能。
4.如果其他方法是static,它用的同步锁是当前类的字节码,与非静态的方法不能同步,因为非静态的方法用的是this。
简述synchronized和java.util.concurrent.locks.Lock的异同?
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。
synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。
守护线程:也叫精灵线程、后台线程
特点:
A : 不具备生命周期,它随着虚拟机的结束而结束
B : 如果想把线程转换成精灵线程,必须在start()之前调用 setDaemon(boolean)方法
一旦线程变为精灵线程就不能变为用户线程了。
守护线程和用户线程的区别:
守护线程是无生命周期的,伴随着用户线程存在,如果用户线程执行完毕,守护线程还没有执行完毕,
那么守护线程也将结束,守护线程在线程启动前调用,setDaemon(true)
线程池: 根据自身的环境情况,有效的限制执行线程的数量,使得运行效果达到最佳。
线程池的作用:
减少创建和销毁线程的次数,每个工作线程可以多次使用
可根据系统情况调整执行的线程数量,防止消耗过多内存。
使用:
ExecutorService pool = Executors.常见线程池;
常见线程池:
①newSingleThreadExecutor : 创建一个单线程的线程池
②newFixedThreadExecutor(n) : 创建固定大小的线程池
③newCacheThreadExecutor(推荐使用) : 创建一个可缓存的线程池
④newScheduleThreadExecutor : 大小无限制的线程池,支持定时和周期性的执行线程
1.线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
2.线程同步方法是通过锁来实现的,每个对象都有且仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,
其他访问该对象的线程就无法再访问该对象的其他同步方法。
3.对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。文件名.class
静态和非静态方法的锁互不干预。
一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4.对于同步,要时刻清醒在哪个对象上同步,这是关键。
5.编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全作出正确的判断,
对"原子"操作作出分析,并保证原子操作期间别的线程无法访问竞争资源。
6.当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7.死锁是线程间相互等待锁造成的,在实际中发生的概率非常的小。
线程组(ThreadGroup): 是指 java.lang.ThreadGroup 类的对象
线程组与线程池的区别:
①线程组存在的意义,首要原因是 安全。
java默认创建的线程都是属于系统线程组,而同一个线程组的数据是可以相互修改对方的数据的。
线程组除安全外,最有用的一个地方就是 控制,只需用单个命令即可完成对整个线程组的操作。
②线程池存在的意义,首要作用是 效率。
线程的创建和结束都需要耗费一定的系统时间,不停创建和删除线程会浪费大量的时间,
所以,在创建出一条线程并使其在执行完任务后不结束,而是使其进入休眠状态,在需要用时再唤醒,
那么 就可以节省一定的时间
线程组和线程池共有的特点:
1. 都是管理一定数量的线程
2. 都可以对线程进行控制---包括休眠,唤醒,结束,创建,中断(暂停)--但并不一定包含全部这些操作。