第2次
Posted zly123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第2次相关的知识,希望对你有一定的参考价值。
第2天(1/16)
1、在java中守护线程和本地线程区别?
守护线程
指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程
自己创建的线程。比如:new Thread。这就是自己创建了一个线程。
守护线程和用户线程的区别
虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
也就是说JVM中所有的线程都是守护线程,那么JVM就会退出,进而守护线程也会退出。
如果JVM中还存在用户线程,那么JVM就会一直存活,不会退出。
由此可以得出
守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
用户线程是独立存在的,不会因为其他用户线程退出而退出。
默认情况下启动的线程是用户线程,通过setDaemon(true)将线程设置成守护线程,这个函数务必在线程启动前进行调用,否则会报java.lang.IllegalThreadStateException异常,启动的线程无法变成守护线程,而是用户线程。
2、死锁与活锁的区别&&死锁与饥饿的区别?
死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去了。
死锁:
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
死锁的四个条件: (有一个条件不成立,则不会产生死锁)
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁具体概念的博客链接:http://blog.csdn.net/beyond_2016/article/details/81326877
预防死锁:
1) 预防死锁。
通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。
2) 避免死锁。(银行家算法)
在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。
死锁避免(deadlock avoidence)是在系统运行过程中注意避免死锁的发生。这就要求每当申请一个资源时,系统都应根据一定的算法判断是否认可这次申请,使得在今后一段时间内系统不会出现死锁。
3)检测和解除死锁。
先检测:通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。
再解除:常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。
活锁
活锁就是指线程一直处于运行状态,但却是在做无用功,而这个线程本身要完成的任务却一直无法进展。就像小猫追着自己的尾巴咬,虽然一直在咬却一直没有咬到。
也可以理解为线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。
活锁的典型例子是某些重试机制导致一个交易(请求)被不断地重试,而每次重试都是失败的(线程在做无用功),这就导致其他失败的交易无法得到重试的机会(任务无法进展)
简单理解:就是一直尝试去获取需要的锁,不断的try,这种情况下线程并没有阻塞,所以是活的状态,但是在做无用功。
饥饿锁:
指的线程无法访问到它需要的资源而不能继续执行时。
引发饥饿最常见资源就是CPU时钟周期。虽然在Thread API中由指定线程优先级的机制,但是只能作为操作系统进行线程调度的一个参考,换句话说就是操作系统在进行线程调度是平台无关的,会尽可能提供公平的、活跃性良好的调度,那么即使在程序中指定了线程的优先级,也有可能在操作系统进行调度的时候映射到了同一个优先级。
通常情况下,不要去修改线程的优先级,一旦修改程序的行为就会与平台相关,并且会导致饥饿问题的产生。在程序中使用的Thread.yield或者Thread.sleep表明该程序试图客服优先级调整问题,让优先级更低的线程拥有被CPU调度的机会。
**死锁与活锁的区别**
活锁和死锁类似,不同之处在于活锁的线程或进程的状态是一直在不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。
简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是不能继续执行。
**死锁与饥饿的区别**
1、进程状态:死锁进程都是处于等待状态,饥饿进程是处于运行或就绪态中的忙式等待状态(比如在一个while循环中无法跳出)。在忙式等待中发生的界,我们也称其为活锁。
2、死锁进程等待永不被释放的资源,饿死的进程则是等待可以被释放,但不会分配给自己的资源。
3、死锁一定发生循环等待,但是饿死则不一定。
所以,死锁检测算法无法检测到饿死状态。
死锁涉及的进程至少是两个,但是饥饿却可能只有一个。
3、什么是并发容器的实现?
并发容器大集合
https://blog.csdn.net/u010425776/article/details/54890215
同步容器 & 并发容器
1.同步容器。同步容器只有包括Vector和HashTable,如果你觉得陌生,那就对了,二者事早期JDK就有的,在java.util包下面,和同包下的其他不能保证同步安全的容器类相比显得鹤立鸡群,但是它们的技术相比其他容器累只是多用了Synchronize的技术,下面的并发容器,算法和底层技术上做了改进,并且在容器的并发操作方面有诸多优化,性能更优,所以知道这对难兄难弟就好,实战千万不要用.
2.并发容器。并发容器是java 5.0开始提供的,在java.util.concurrent包下。一个东西的好一定是比较出来的,同步容器实现的线程安全性简单粗暴,把所有对容器状态的操作串行化,并发容器针对各种容器的特定功能,采用最佳的设计,比如BlockingQueue的读写分离锁、阻塞访问、定时阻塞,ConcurrentHashMap的分段锁,CopyOnWriteArraySet的写入时复制
细说并发容器
java 5.0增加了两种新的容器类型,Queue和BlockingQueue,队列的特点是先进先出,插入和移除操作分别仅能在队列的两端操作。BlockingQueue比Queue多了put、take、offer和poll,他们分别是阻塞插入、阻塞移除、定时阻塞插入和定时阻塞移除。
事实上,Queue是通过LinkedList实现的(队列的修改操作仅限首尾端,用链表比数组更好),因为它能去掉List的随机访问功能,从而实现了更高效的开发,封装性更好。
- ConCurrentLinkedQueue
插入和移除具有线程安全的一个队列实现类。当只有队列+并发的需求,就是它了;
2.LinkedBlockingQueue
使用很广泛的一个类,因为真的很实用啊。LinkedBlockingQueue满足队列+并发+阻塞访问的需求,是生产者-消费者模型的最佳实现对象,当然,如果有Priority这样的需求,可以在LinkedBlockingQueue基础上开发;
3.ConcurrentHashMap
使用HashMap,任何说需要增加并发需求的情况下,那一定是ConcurrentHashMap了,ConcurrentHashMap使用锁控制线程并发访问,同时分段锁机制确保了并发访问不会是性能损失很大。
不同的线程可以同时访问ConcurrentHashMap下不同的HashMap,当然HashMap的读写是保证线程安全的,这个设计兼顾了并发和性能两个要素:
- CopyOnWriteArrayList,CopyOnWriteArraySet
带有“CopyOnWriteArray“关键字的容器,具有写入时复制的特点。写入时复制,能够权衡两个问题,容器迭代操作引发的并发问题,和容器迭代使用锁而带来的性能损耗。非同步、并发的容器进行迭代操作,会抛出一个运行时异常ConcurrentModificationException,虽然运行时异常并不要求上层代码一定要处理,但并不代表应该忽视这种情况。
容器迭代操作引发的并发问题。非同步、并发的容器迭代过程,容器就有可能被修改,因此next(),hasNext()方法都要执行checkForComodification操作,ConcurrentModificationException异常也仅在于告知上层代码有并发的存在,并不会处理。
容器迭代使用锁而带来的性能损耗。同步类和并发类的迭代功能,会持有容器的锁确保迭代不会有并发问题,这里有个例外是ConcurrentHashMap,虽然迭代ConcurrentHashMap也会持有锁,奈何ConcurrentHashMap锁不止一个,能够确保迭代并发性的大概也只有最有一个被迭代的HashMap。当容器的容量很大时,迭代将长时间占着锁,其他的线程苦不堪言,因此迭代会让大容量的并发容器性能大打折扣。
写入时复制。“写入时复制“容器会维护两套数据,修改是一套,查询是一套,当修改是会把修改内容更新到副本。那么迭代过程就不需要持锁访问容器了,是不是很简单粗暴。
显然,每当修改容器时都会复制底层数组,这需要一定的开销,特别是当容器规模特别大的时。仅当迭代操作远远大于修改操作时,才应该使用“写入时复制“容器。
5.Deque,BlockingDeque
java 6.0增加了两种容器类型,Deque(发音为“deck“)和BlockingDeque,它们分别对Queue和BlockingQueue做了扩展,能够在队列两端进行插入和移除操作,称为双端队列。
在队列两端开放插入和移除功能并非多此一举,在工作密取模式中,Deque能够很好的工作。在生产者-消费者设计中,所有消费者共享一个工作队列,而在工作密取设计中,每组生产者消费者有各自的双端队列,当它们完成了自己的工作,可以到其他组的尾端工作。工作密取模式比生产者消费者模式有更好的伸缩性,这是因为工作者线程不会在单个共享的任务队列发生竞争。双端队列很适合用在爬虫这样的工作中。
4、并发容器和同步容器的锁的策略有什么不一样
5、什么叫线程安全?servlet是线程安全吗?
线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题。
servlet是线程安全吗? 默认的servlet是非线程安全的
servlet是单例模式,只产生一个实例,根据项目中web.xml实例,这个实例是web容器产生的,比如Tomcat,JBOSS,weblogic等。
就是说多个客户请求产生多个线程,一个线程对应一个客户,但是用的servlet对象却是一个。既然用的对象是一个,那么实例变量就是共享数据了,说到共享数据还不同步,必然是非线程安全的。
比如说两人同时在12306上买票,这时很不巧只剩下一张票,甲乙在同一时间点击购票,两人同时都到购票了,你们说到时候上车怎么办?
synchronized关键字
synchronized关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、D等)正在用这个方法,若有则要等正在使用synchronized方法的线程B(或者C、D)运行完这个方法后再运行此线程A,若没有则直接运行。
常见的线程安全的解决办法:
1.使用方法内局部变量
是因为各线程有自己堆栈空间,存储局部变量
方法参数传入,多采用传值(volue copy)传入方法内
2.对操作共享资源的语句,方法,对象, 使用同步
比如写入磁盘文件,采用同步锁,但建议尽量用同步代码块,不要用同步方法
3.使用同步的集合类
使用Vector代替ArrayList
使用Hashtable代替HashMap。
4.不要在 Servlet中再创建自己的线程来完成某个功能。
Servlet本身就是多线程的,在Servlet中再创建线程,将导致执行情况复杂化
6、mysql底层B+Tree机制?
mysql为什么使用B+tree
B+tree是B-tree的一个变种,在innodb中用的就是B+tree,主要是用在索引,比如innodb的聚集索引。
B+Tree非叶子结点只存储键值,大大滴减少了非叶子节点的大小,索引块能够存储更多的节点(每个节点就可以存放更多的记录),从磁盘读索引时所需的索引块更少,树更矮了,所以索引查找时I/O次数较B-Tree索引少,效率更高。
B+Tree在叶子节点存放的记录以链表的形式链接,范围查找或遍历效率更高,
B+Tree在同级节点间还存在一条连接,B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,如果要查询key为从a到b的所有数据记录,当找到a后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
以上是关于第2次的主要内容,如果未能解决你的问题,请参考以下文章