1.对Java里的线程再多一点点认识
1.run():此方法是跟类挂钩的,就是一个方法,具体呢就是创建多线程时重写的那个run方法。
2.start():此方法是跟系统挂钩的,比方说我创建了一个线程而他的作用就是让jvm虚拟机去调用run方法
run与start的区别:
区别就是run方法可以多次调用,因为跟类挂钩的就是跟我们平常获取对象的get方法一样,而start呢他是跟系统挂钩的只能调用一次,因为线程已经启动了执行了不可能还能够退到就绪状态然后继续启动一波,如果多次调用会直接报错。
3.yield() :此方法其实就是把当前线程暂停了,把时间轮转机制(RR调度)让给同级别的线程让他们去运行,等待同级别的线程执行完毕,在恢复当前线程继续执行。
4.join():此方法为让线程顺序执行,比方说我父线程main主线程中有子线程,main主线程会等待子线程执行完毕然后在执行,顺序:主线程 - 子线程 -主线程
2.线程间的共享
1.synchronized内置锁:
用法呢有两种:1.使用在同步代码块上,2.使用在方法上
目前有两种锁:对象锁跟类锁
1.对象锁:锁住当前对象实例在我当前对象实例区域内线程访问类中的方法是同步的
2.类锁:其实把类锁跟对象锁其实也差不多只不过他比较特殊一点,他锁的是一个class文件,就算我创建的对象实例再多只要创建的对象实例是锁着的这个class的都会把线程进行一个同步的,其实就是个全局同步锁了
2.volatile关键字
volatile可以让所有线程对我的成员变量可见,但是不能多线程可写,线程多写会出现数据混乱
适用场景:一写多读
3.ThreadLocal源码辨析
java中线程都有自己独立的栈堆,但是没有想进程一样的空间地址
ThreadLocal跟synchronized其实差不多,只不过synchronized是同步的,ThreadLocal是并行的,synchronized是通过同步锁来管控数据安全的,而ThreadLocal是在每个线程中创建一个副本相当于数据隔离一样来管控数据安全的。
1.源码解析:
其实内部就是通过一个叫ThreadLocalMap的对象来存放你的数据副本的
ThreadLocalMap内部实现主要是通过Entry[]来对ThreadLocal来进行管理,为什么要用数组呢因为ThreadLocal是个泛型的会有各种各样的类型的ThreadLocal
ThreadLocalMap内部主要是使用Entry(ThreadLocal<?> k, Object v)
来管理的,k创建了一个弱引用的ThreadLocal指针对象,说白了就是创建一个弱引用对象,v就是自己set进来的值
set方法,都是根据当前线程然后去获取ThreadLocal对象的ThreadLocalMap去存放数据的
2.使用ThreadLocal引发的内存泄漏分析:
强引用:栈中的变量指针直接指向堆中的实例对象,比如(Object test=new Object),Object test是在栈中的然后直接指向堆中的new Object内存地址
软引用:当程序发生内存溢出了,就是内存不够用了,直接把软引用的对象值为空的清理,如果内存还是不够用,就直接把所有的软引用对象全部清理
弱引用:只要发生GC垃圾回收了就会把所有弱引用对象全部清理
虚引用:只要发生GC垃圾回收了就会把所有弱引用对象全部清理,不过可以通知一下被清理的对象你马上要被清理了你要不要做些什么东西
开始测试:我写了一个线程池提供5个线程,循环使用500次,大家都知道线程池没有线程使用的时候是会阻塞的,所以可以把目前线程统计为5个线程,只不过是循环在使用罢了,下面看代码,就很简单的例子代码
设置堆最大内存启防止动久了弄得的电脑卡死最大为256M
然后目前我是没有使用ThreadLocal的,执行可以看到堆栈平均稳定在21M左右,嗯目前可以说是正常的没有内存泄漏,分析:5个线程同时在使用,那就是5M*5=25M,就是一般稳定在25M之内算是正常的。
然后我加上ThreadLocal进行一个测试启动
居然直接给飙升到了170M
原因是每个线程创建了一个副本但是线程使用完毕由于ThreadLocal是引用Entry数组发生栈堆的强引用关系,导致GC没有把对象进行一个回收,就是ThreadLocal不为空回收不了,会出现内存泄漏。
那么为什么堆使用只是维持在1百多M左右,按道理来说应该会直接使用满使用2百多M的才对啊,原因内部源码上加了一个弱引用然后每次set值的时候进行了判断替换重复数据,以及清除掉一些为空的数据,也就是ThreadMap中对应的Key中的Value进行一个赋值为空的操作,然后我堆使用大小直接使用满,然后造成内存溢出触发GC,进行对使用了弱引用的ThreadLocal对象进行一个清理,如果内存还是溢出,那就会把使用了弱引用的ThreadLocal全部清除,如果不加弱引用的话发生内存泄漏是百分之一百的问题,加上了如果线程操作不那么频繁是可以进行一个清理的,就是被set中的那些检测处理旧数据的方法处理完毕,就会进行一个清理。
内部set跟get方法都进行了对数据的一个检测替换老数据,检测是否为空数据等。
set方法调用了replaceStaleEntry然后replaceStaleEntry里面又使用expungeStaleEntry来进行一个清除
remove方法也有这些检测的方法
然后怎么解决这个问题呢,其实很好解决编码的时候注意一下就是了,在使用完毕手动的情况的进行一些移除当前这个线程副本就行了
再次启动进行JDK提供的一个jvisualvm监控工具观测,一般也维持到了40多M了,为什么是40M呢不应该是25M吗因为内部清理需要有的时间的反正迟早会进行一个清理的,不用担心
3.使用ThreadLocal时的线程不安全
如果我拿ThreadLocal内部使用一个静态共享变量会怎么样呢,开始测试
结果都变成了1,因为用了共享对象导致的
解决方法给每个ThreadLocal创建一个独立的副本就好了
现在spring的事务也用到了ThreadLocal,具体是为了实现每一个线程拥有自己的连接比如我的一个service层的调用dao层的方法,如果调用不同的dao层Mapper接口就会造成获取多个数据库连接,比如MapperA:数据库连接1,MapperB:数据库连接2等待,那么事务肯定是需要在同一个连接中进行的,所以出现了这个问题,然后 spring事务通过把数据库连接对象存放到当前这个线程中,就导致每个线程都拥有属于自己的独立数据库连接对象,就成功的解决了这个问题,相当于把连接对象存放到了线程的副本中,使数据库连接对象相互隔离。
4.什么是线程间的协作
协作式:协作式是指线程间相互进行协调工作,比如线程A睡眠了,线程B把他给唤醒
抢占式:抢占式是指线程之间抢占时间轮转机制的一种抢夺资源的方式
1.wait():使当前线程进入一个等待中相当于睡眠,注意wait方法是一个内置方法,处于Object对象中,必须要在synchronized内部使用,要不然报错。
2.notify与notifyAll区别
概念:一个对象是可以被多个线程使用的
notify:唤醒当前这个对象其中的一个(睡眠/等待)线程进入锁池开始工作
notifyAll:唤醒当前这个对象所有(睡眠/等待)线程进入锁池开始工作
3.等待和通知的标准范式
必须要使用在Synchronized内部,要不然报错。
4.调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?**
yield():yield会让出CPU调度不可以由用户自己控制,只能由系统控制,让出RR调度的资源给到同级别的线程或者比自己级别更高的线程去让他们获取资源运行,等待他们运行完毕在运行yield的这个线程
sleep():sleep可以由用户手动控制睡眠多少秒等等,让后他会释放CPU资源,但是不会释放锁
重点:yield()跟sleep()区别在于yield由系统控制不能让用户手动控制,sleep可以让用户手动控制,重点他们都不会释放锁资源。
wait():使线程进入等待,会释放锁资源
notify():不会释放锁资源,会一直等待线程执行完毕,然后释放锁资源
死锁问题分析:
基本例子:原因就是一个线程执行顺序产生的下面这个例子线程A执行完毕了再去执行线程B,由于线程A先使用的notify但是当前没有唤醒的线程,因为线程B还没有执行,然后线程A进入wait,线程B一运行就进入wait所以导致都不能进入运行中,解决方法:把notify放到后面就行了
Deadlock deadlock=new Deadlock();
new Thread(()->{
System.out.println("线程名称:A");
synchronized (deadlock){
try {
System.out.println(Thread.currentThread().getName()+"线程名称:A进入唤醒");
deadlock.notify();
System.out.println("线程名称:A进入等待");
deadlock.wait();
System.out.println("我叫小狗");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
System.out.println("线程名称:B");
synchronized (deadlock){
try {
System.out.println("线程名称:B进入等待");
deadlock.wait();
System.out.println("我叫小猪");
System.out.println("线程名称:B进入唤醒");
deadlock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();