线程基础
1、什么是进程和线程
操作系统进行资源分配的最小单位(例打开一个应用程序)
线程是CPU调度的最小单位(必须依赖进程)
CPU核心数和线程数的关系:一个CPU的内核可以执行一个线程,但Intel引用了超线程技术。内核:逻辑处理器(1:2)
CPU时间片轮转机制(又叫RR调度):CPU时间切片,每个进程分配一个时间段
CPU :1.6G执行一个指令的时间大概0.6纳秒(1秒=10亿纳秒)
代价:上下文切换(很耗CPU时间,一次上下文切换2万个CPU时间周期)
2、并行和并发
并行:可以同时运行的任务数。同时执行不同的任务
并发:单位时间内的并发量。交替执行
3、高并发编程的意义,好处和注意事项
1、 可以充分利用CPU的资源
2、 加快响应用户的时间
3、 可以使代码模块化,异步化和简单化
注意:
1、 线程的安全性,线程会共享进程的资源
2、 引入锁,可能会出现死锁,锁的竞争之间导致性能下降
3、 操作系统OS有限制,linux里一个进程里不能超过1000个线程,windows一个进程最大线程数2000个,
新启线程时是需要分配资源的(栈空间,缺省值 1M),还有句柄(文件描述符或指针),linux里一个进程最大1024个句柄
实际开发中一般会用线程池来做
java里的程序天生就是多线程的
4、新启线程的方式
1、 类Thread
2、 接口Runnable
5、Thread类和Runnable的区别
Thread是java语言里对线程的抽象
Runnable 是对任务的抽象(对业务逻辑的抽象)
6、线程中断
stop()方法为什么不建议使用:因为它可能导致线程所占用的资源不能正常释放。
Interrupt();是对线程进行中断,并不是真的中止线程,其实是给线程的一个中断标志位。
在jdk里线程是协作式的,不是抢占式的。
IsInterrupt();是判定当前线程是否被中断
Interrupted();是判定当前线程是否被中断(会把中断标志位给清除,重新由true改成false)
在实现Runnable接口里调用isInterrupted();方法:
Thread.currentThread().isInterrupted();
当阻塞类(sleep(),wait())会抛出中断异常的方法捕捉到了异常以后还会把中断标志位由true改成false,并不会中断,如果确实要中断线程的话还需要catch里手动调用一次interrupt();才会去中断线程。(需自己决定是否释放资源)。
处于死锁状态的线程是不会理会中断的。
7、线程的常用方法
Run()和start()
Start()方法允许且只能调用一次,多次调用会抛异常,在start()方法调用以后才正真意义上的启动了一个线程。
Run()方法是业务逻辑实现的方法,可以被反复调用,也可以被脱离我们的线程单独调用。
8、线程的生命周期
Yield()方法:将线程从运行转到可运行状态(让线程让出CPU的执行权),但让出的时间不可确定,yield()方法不会释放锁资源。所有执行yield()的线程在进入到系统就绪状态后会有可能会被操作系统再次选中马上又被执行。
Join()方法:把指定的线程加入到当前线程之前执行。使用join()方法可以使两个线程有顺序的执行。比如线程B中调用了线和A的join()方法,直到线程A执行完毕后,才能继续执行线程B。
守护线程:主要被用作程序中后台调度以及支持性工作。比如垃圾回收线程就是守护线程
如果要把我们自己new的用户线程改为守护线程只需要设置useThread.setDaemon(true);即可,默认值为false。主线程结束了,守护线程也会跟着结束。守护线程的finally方法不一定会被执行 。
9、Synchronized关键字(内置锁)
线程开始运行,拥有自己的栈空间,java支持多个线程同时访问一个对象或者对象的成员变量,关键字synchronized可以修饰方法或者以同步块的形式来进行使用,确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。(两种方法:可以在方法上加锁,也可以在代码块上加锁)
10、Volatile关键字
(一写多读的情况下比较适合)只能保证可见性,不能保证原子性。
11、ThreadLocal
为每一个线程提供一个变量的副本,从而实现了线程的隔离。(Spring的事务有用到ThreadLocal,为了保证拿到同一个连接)
ThreadLocal引发的内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。当ThreadLocal存储很多key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。最好的做法是不再需要使用ThreadLocal变量后,都调用它的remove()方法,清除数据。
12、线程间的协作
线程之间相互配合,完成某项工作,比如:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后再进行相应的操作 。
13、等待/通知机制:是指一个线程A调用了对象o的wait()方法进入待待状态,而另一个线程B调用了对象o的notify()或者notifyAll()方法,线程A收到通知后从对象o的wait()方法返回,进而执行后续操作。上述两个线程通过对象o来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信息一样,用来完成等待方和通知方之间的交互工作。
Notify()只随机通知一个等待在该对象上的线程,notifyAll()通知所有待待在该对象上的线程,我们一般都用notifyAll();
调用wait()方法后,会释放对象的锁。
14、等待和通知的标准范式
等待方:
(1)获取对象的锁
(2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件
(3)条件满足则执行对应的逻辑
Synchronized(对象){
While(条件不满足){
对象.wait();
}
对应的处理逻辑
}
通知方:
(1) 获得对象锁
(2) 改变条件
(3) 通知所有等待在对象上的线程
Synchronized(对象){
改变条件
对象.notifyAll();
}
在调用wait(),notify()系列方法之前,线程必须要获取对象锁,只能在同步方法或同步块中调用wait(),notify()系列方法。进入wait()后当前线程释放锁,在从wait()返回前,线程与其他线程竞争重新获得锁,执行notify()系列方法的线程退出调用了notifyAll的synchronized代码块的时候,他们就会去竞争,如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其它线程又会竞争该锁,这样一直下去,直到所有被唤醒的线程都执行完。