什么是进程和线程
进程是程序运行资源分配的最小单位:进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、
磁盘IO 等
线程是CPU 调度的最小单位,必须依赖于进程而存在:线程是进程的一个实体,是CPU 调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位
进程是并发执行的程序在执行过程中分配和管理资源的基本单位。线程是进程的一个执行单元,是比进程还要小的独立运行的基本单位。
CPU 核心数和线程数的关系
核心数、线程数:目前主流CPU 都是多核的。增加核心数目就是为了增加线
程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1 对应关系,也
就是说四核CPU 一般拥有四个线程。但Intel 引入超线程技术后,使核心数与线程
数形成1:2 的关系
CPU 时间片轮转机制
如果在时间片结束时进程还在运行,则CPU 将被剥夺并分配给另一个进程。
如果进程在时间片结束前阻塞或结来,则CPU 当即进行切换。调度程序所要做的
就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾
并行和并发
并发: 指应用能够交替执行不同的任务,比如单CPU 核心下执行多线程并非是
同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不
断去切换这两个任务,已达到"同时执行效果",其实并不是的,只是计算机的速度太
快,我们无法察觉到而已.
并行: 指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话,
这两件事情可以同时执行
两者区别:一个是交替执行,一个是同时执行.
高并发编的好处
(1)充分利用CPU 的资源
(2)加快响应用户的时间
(3)可以使你的代码模块化,异步化,简单化
多线程程序需要注意事项
(1)线程之间的安全性
(2)线程之间的死锁
(3)线程太多了会将服务器资源耗尽形成死机当机
Java中的线程
启动:启动线程的方式有
1、X extends Thread;,然后X.start
2、X implements Runnable;然后交给Thread 运行
Thread 和Runnable 的区别
Thread 才是Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)
的抽象。Thread 可以接受任意一个Runnable 的实例并执行。
//1):定义一个类A继承于java.lang.Thread类.
class MusicThread extends Thread{
//2):在A类中覆盖Thread类中的run方法.
public void run() {
//3):在run方法中编写需要执行的操作
for(int i = 0; i < 50; i ++){
System.out.println("播放音乐"+i);
}
}
}
public class ExtendsThreadDemo {
public static void main(String[] args) {
for(int j = 0; j < 50; j ++){
System.out.println("运行游戏"+j);
if(j == 10){
//4):在main方法(线程)中,创建线程对象,并启动线程.
MusicThread music = new MusicThread();
music.start();
}
}
}
}
//1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.
class MusicImplements implements Runnable{
//2):在A类中覆盖Runnable接口中的run方法.
public void run() {
//3):在run方法中编写需要执行的操作
for(int i = 0; i < 50; i ++){
System.out.println("播放音乐"+i);
}
}
}
public class ImplementsRunnableDemo {
public static void main(String[] args) {
for(int j = 0; j < 50; j ++){
System.out.println("运行游戏"+j);
if(j == 10){
//4):在main方法(线程)中,创建线程对象,并启动线程
MusicImplements mi = new MusicImplements();
Thread t = new Thread(mi);
t.start();
}
}
}
}
继承方式:
- 从设计上分析,Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了.
- 从操作上分析,继承方式更简单,获取线程名字也简单.(操作上,更简单)
- 从多线程共享同一个资源上分析,继承方式不能做到.
实现方式:
- 从设计上分析,Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口,设计更为合理.
- 从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用.
- 从多线程共享同一个资源上分析,实现方式可以做到(是否共享同一个资源).
中止:
4. 线程自然终止stop
5. 中断interrupt()
线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法
Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()
会同时将中断标识位改写为false。
注意:处于死锁状态的线程无法被中断
1. run()和start()的区别
start()方法让一个线程进入就绪队列等待分配cpu,分到cpu 后才调用实现
的run()方法,start()方法不能重复调用,如果重复调用会抛出异常。
而run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方
法并没有任何区别,可以重复执行,也可以被单独调用。
run()方法使用的主线程类,start()方法才是使用对应的应用线程类。
2.其他的线程相关方法
yield()方法:使当前线程让出CPU 占有权,但让出的时间是不可设定的。
wait()/notify()/notifyAll()
join 方法
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。插队的意思
线程的优先级
通过一个整型成员变量priority 来控制优先级,优先级的范
围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认
优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。数字越大优先级越高
守护线程
Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调
度以及支持性工作
线程间的共享
1. synchronized 内置锁
保证了线程对变量访问的可见性和排他性
对象锁和类锁:
对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态
方法或者一个类的class 对象上的。
①对象锁,锁的是Object o
②对象锁,锁当前实例
③类锁(锁的static方法),本质上是锁了每个类所独有的Class对象。
synchronized实现原理: 线程执行monitorenter指令时尝试获取monitor的所有权:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
2. volatile,最轻量的同步机制
volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某
个变量的值,这新值对其他线程来说是立即可见的。
volatile 最适用的场景:一个线程写,多个线程读。
3. ThreadLocal
ThreadLocal 和Synchonized 都用于解决多线程并发訪问。可是ThreadLocal
与synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块
在某一时该仅仅能被一个线程訪问。而ThreadLocal 为每个线程都提供了变量的
副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线
程对数据的数据共享。
ThreadLocal 的4 个方法
- void set(Object value) 设置当前线程的线程局部变量的值。
- public Object get() 该方法返回当前线程所对应的线程局部变量。
- public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用
- protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected 的方法,显然是为了让子类覆盖而设计的。
取到当前线程,然后调用getMap 方法获取对应的ThreadLocalMap,ThreadLocalMap 是ThreadLocal 的静态内部类,然后Thread 类中有一个这样类型成员,所以getMap 是直接返回Thread 的成员。
其实就是拿到每个线程独有的ThreadLocalMap然后再用ThreadLocal 的当前实例,拿到Map 中的相应的Entry,然后就可以拿到相应的值返回出去。当然,如果Map 为空,还会先进行map 的创建,初始化等工作。
引发的内存泄漏分析
因为ThreaLocalMap是虚引用,key发生垃圾回收时就会被回收掉,但是value是强引用,不会被回收。
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value 永
远不会被访问到了,所以存在着内存泄露。
**解决OOM内存溢出的办法:**使用ThreadLocal 变量后,都调用它的remove()方法,清除数据。
线程间的协作
1. 等待/通知机制
notify():通知一个在对象上等待的线程,使其从wait 方法返回,而返回的前提是该线程
获取到了对象的锁,没有获得锁的线程重新进入WAITING 状态。
notifyAll():通知所有等待在该对象上的线程
wait() 调用该方法的线程进入WAITING 状态,只有等待另外线程的通知或被中断才会返回
wait(long) 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n 毫秒,如果没有通知就超时返回
wait (long,int) 对于超时时间更细粒度的控制,可以达到纳秒
在调用wait()、notify()系列方法之前,线程必须要获得该对象的对象级
别锁,即只能在同步方法或同步块中调用wait()方法、notify()系列方法。
notify 和notifyAll 应该用谁
尽可能用notifyall(),谨慎使用notify(),因为notify()只会唤醒一个线程,我
们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程