todo
func main(){
fmt.Print("hw")
}
死锁的原理和解决方法
虚拟机栈参与内存回收吗
ping ttl volitale什么情况下必须被使用 innodb锁机制 mvcc 如果让你来实现一个HashMap你会怎么实现
JVM或者编译器如何实现多态的?子类方法调用过程中动态绑定的实现过程?
非公平锁 失败
IO复用技术
设计模式 MYSQL HTTP 头部字段 你了解的网络协议 HANUOTa问题 TCP UDP 区别 https://www.cnblogs.com/fundebug/p/differences-of-tcp-and-udp.html
计算机网络 学习 资源http://www.52im.net/thread-1007-1-1.html TCP UDP 头部 HTTP 状态码 3xx 5xx 4xxx 断点重续原理 HTTP 2.0 外部排序 系统调用是什么,怎么实现 内核态,用户态,系统怎么识别 IP是如何标识主机的,原理 MAC地址的作用 https://www.cnblogs.com/shengulong/p/5707967.html 分页的置换是由系统控制还是进程控制 TCP和UDP的区别 TCP为什么是字节流,UDP为什么是。。。流 TCP的字节流怎么识别两个数据(怎么区分开两次数据) LBD是什么
wait() notrfy机制
threadlocal机制 链接:https://ac.nowcoder.com/acm/contest/6220/A 来源:牛客网
牛牛想知道所有的长度为n的数中,各个位上的数字之和为m的这些数的和是多少呢。给定n和m,求这些数的和。
数据库
1.mysql常用引擎,引擎的区别
常用引擎:InnoDB MyISAM
1)事物安全:InnoDB支持事物安全,MyISAM不支持。
2)对外键的支持: InnoDB对外键支持情况较好,MyISAM不支持外键。
4)空间使用: InnoDB对空间使用程度较高,MyISAM对空间使用程度较低。
5)内存使用: InnoDB和MEMORY对内存使用程度较高,MyISAM对内存使用程度较低。
6)插入数据的速度: InnoDB插入数据的速度较低,MyISAM和MEMORY插入数据的速度较高。
7)对应索引结构的的差别:都是B+树
Innodb:Innodb的索引文件本身就是数据文件,主键索引的叶子节点储存的是实际的数据,这种索引有被称为聚集索引。普通索引叶子节点储存的是键值与对应的主键,所以会有回表查询,与索引覆盖。
MyISAM:叶子节点存储的是键值与指向数据的指针
基于以上的说法,Innodb必须有主键,MyISAM可以没有
2.给了一个查询语句怎么建索引
在where字句上的字段建立索引
为最常查询的字段建立索引
从查询语句的左边字段开始建立索引(最左匹配原则)
3.索引失效
1.or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效 2.复合索引未用左列字段; 3.like以%开头; 4.需要类型转换; 5.where中索引列有运算; 6.where中索引列使用了函数; 7.如果mysql觉得全表扫描更快时(数据少);
操作系统
1.进程的通信方式
进程间通信是指在不同进程之间传播或交换信息,在Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间,进程之间不能相互访问。必须通过内核才能进行数据交换。
1.管道:管道是一种半双工的通信方式,数据只能单向流动
- 匿名管道:匿名管道只能在具有亲缘关系的进程间使用,进程的亲缘关系通常指父子进程关系
- 命名管道:不同于匿名管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程与FIFO的创建进程之间通信),因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
2.信号:信号是一种比较复杂的通信方式,信号产生的条件:按键、硬件异常、进程调用kill函数将信号发送给另一个进程、用户调用kill命令将信号发送给其他进程,信号传递的消息比较少,主要用于通知接收进程某个事件已经发生。
3.信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
4.消息队列:消息队列是消息的链表,存放在内核中并由消息队列标识符标识,消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点。消息队列起信箱作用,到了就挂在那里,需要的时候去取。消息队列提供了一种在两个不相关进程间传递数据的简单有效的方法。与命名管道相比:消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。
5.共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式(不需要从用户态到内核态的切换),它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
6.套接字s:
2.共享内存方式的细节
共享内存的特点:
- 共享内存是进程间共享数据的一种最快的方法。一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
- 使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。若一个进程正在向共享内存区写数据,则在它做完这一步操作前,别的进程不应当去读、写这些数据。
2.共享内存的通信
因为所有进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。访问共享内存区域和访问进程独有的内存区域一样快,并不需要通过系统调用或者其它需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。 因为系统内核没有对访问共享内存进行同步,您必须提供自己的同步措施。例如,在数据被写入之前不允许进程从共享内存中读取信息、不允许两个进程同时向同一个共享内存地址写入数据等。解决这些问题的常用方法是通过使用信号量进行同步。
3.共享内存的内存模型
要使用一块共享内存,进程必须首先分配它。随后需要访问这个共享内存块的每一个进程都必须将这个共享内存绑定到自己的地址空间中。当完成通信之后,所有进程都将脱离共享内存,并且由一个进程释放该共享内存块。
3.僵尸进程与孤儿进程
我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
问题及危害
僵尸进程的危害:unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,如果父进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
孤儿进程没啥危害: 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
僵尸进程危害场景:
例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。
4.线程与进程的区别
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。
计算机网络
1.为什么需要timewait状态
1.防止具有相同「四元组」的「旧」数据包被收到;允许老的重复分节在网络中消失
经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
2.保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭;
2.HTTP1.0 1.1 2.0的区别
HTTP1.0
- HTTP 1.0 被设计用来使用短链接,即每次发送数据都会经过 TCP 的三次握手和四次挥手,效率比较低。
- HTTP 1.0 不支持断点续传,也就是说,每次都会传送全部的页面和数据。
- HTTP 1.0 认为每台计算机只能绑定一个 IP,所以请求消息中的 URL 并没有传递主机名(hostname)。
HTTP1.1
- HTTP 1.1 使用了摘要算法来进行身份验证
- HTTP 1.1 默认使用长连接,长连接就是只需一次建立就可以传输多次数据,传输完成后,只需要一次切断连接即可。长连接的连接时长可以通过请求头中的
keep-alive来设置 - HTTP 1.1 支持断点续传,通过使用请求头中的
Range来实现。
HTTP 2.0
头部压缩,由于 HTTP 1.1 经常会出现 User-Agent、Cookie、Accept、Server、Range 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。HTTP 2.0 使用HPACK算法进行压缩。二进制格式,HTTP 2.0 使用了更加靠近 TCP/IP 的二进制格式,而抛弃了 ASCII 码,提升了解析效率强化安全,由于安全已经成为重中之重,所以 HTTP2.0 一般都跑在 HTTPS 上。多路复用,即每一个请求都是是用作连接共享。一个请求对应一个id,这样一个连接上可以有多个请求。
3.DNS使用什么协议,端口是什么?
DNS占用53号端口,同时使用TCP和UDP协议。那么DNS在什么情况下使用这两种协议?
DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议。
DNS区域传输的时候使用TCP协议:
1.辅域名服务器会定时(一般3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,会执行一次区域传送,进行数据同步。区域传送使用TCP而不是UDP,因为数据同步传送的数据量比一个请求应答的数据量要多得多。
2.TCP是一种可靠连接,保证了数据的准确性。
域名解析时使用UDP协议:
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过三次握手,这样DNS服务器负载更低,响应更快。理论上说,客户端也可以指定向DNS服务器查询时用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。
(为什么使用UDP协议):1.传输内容小,错误率底。2.对速度的要求 3.对DNS服务器负载低
4.网关,网桥,路由器,交换机的区别
1.网桥:连接两个LAN,并转发数据帧
网桥也叫桥接器,是连接两个局域网的一种存储/转发设备,它能将一个大的LAN分割为多个网段,或将两个以上的LAN互联为一个逻辑LAN,使LAN上的所有用户都可访问服务器。
网桥:工作在数据链路层,在不同或相同类型的LAN之间存储并转发数据帧,必要时进行链路层上的协议转换。可连接两个或多个网络,在其中传送信息包。
在日常生活中,拓展局域网最常见的方法是使用网桥。最简单的网桥有两个端口,复杂些的网桥可以有更多的端口。网桥的每个端口与一个网段相连。
2.网关:只要连接两个不同的网络的设备都可以叫网关
3.交换机:工作在数据链路层,原理等同于多端口网桥。作用是连接数个相同网段的不同主机,减少网内冲突,隔离冲突域。利用存储转发和过滤技术来从物理上分割网段。
4.路由器:是连接因特网中各局域网、广域网的设备,是用于连接多个逻辑上分开的网络,所谓逻辑网络是代表一个单独的网络或者一个子网。当数据从一个子网传输到另一个子网时,可通过路由器的路由功能来完成。因此,路由器具有判断网络地址和选择IP路径的功能,它能在多网络互联环境中,建立灵活的连接,可用完全不同的数据分组和介质访问方法连接各种子网,路由器只接受源站或其他路由器的信息,属网络层的一种互联设备。它会根据信道的情况自动选择和设定路由,以***路径,按前后顺序发送信号。
JAVA-多线程
1.终止线程的四种方式
(1)正常执行结束
(2)使用同一标志,多个线程共用一个变量,变量使用volite修饰,每次把他作为标志位来进行判断。
(3)interrupt结束线程
当线程处于阻塞状态的时候,如果使用sleep,同步锁的wait方法,socket的receive方法的时候,会使线程处于阻塞状态。
当调用线程的interrupt方法的时候。会抛出interruptexception异常。阻塞中的那个方法抛出异常,通过代码捕获异常,然后结束执行。
线程未处于阻塞状态的时候,可以使用isinterrupted来进行判断,while来调这个函数。
(4)stop方法终止线程
stop方法强制执行,会导致现场释放他所占有的所有锁、被保护的数据可能就会出现不一致性。可能会出现很多奇怪的应用程序错误。
2.sleep和wait方法的区别
1.对于sleep方法,属于Thread类,wait方法数据object类中。
2.sleep方法导致线程的短暂执行,让出cpu去执行其他线程。依然监控cpu,当时间到了,立马拿到cpu的执行权。
调用sleep方法的时候,线程不会释放锁。wait方法会放弃对象锁,进入锁的等待池。此方法调用了notify之后,才能进入锁池,进行重新竞争。
(每个对象下会维护两个池:锁池与等待池,只有锁池中的线程才能进行锁的竞争,wait()方法会把线程放进对象的等待池,notify方法会随机(基于jvm的实现)将等待池中的一个线程放进锁池)
3.Thread 和Runnable的区别
首先,
实质上都是继承runnable接口。
继承Thread类:
步骤:1.继承thread类,重写run方法。2.此时FIrstThread就是一个线程对象,直接新建FIrstThread对象,执行start方法即可。
实现Runnable接口:
步骤:1.实现runnable接口,重写run方法。此时SecondThread就是一个runnable对象。2.新建一个Thread对象,在构造函数中传入SecondThread对象(runnable对象),再执行Thread对象的start方法即可。
两者区别:
1.适合多个相同的程序代码的线程去处理同一个资源
2.可以避免java中的单继承的限制
3.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
4.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
4.Synchronized 和 ReenTrantLock 的对比
① 两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
③ ReenTrantLock 比 synchronized 增加了一些高级功能
相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)
- ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的
ReentrantLock(boolean fair)构造方法来制定是否是公平的。 - synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
④ 性能已不是选择标准
在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。
5.synchronized 关键字底层原理总结
synchronized 关键字底层原理属于 JVM 层面。
① synchronized 同步语句块的情况
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 javac SynchronizedDemo.java 命令生成编译后的 .class 文件,然后执行javap -c -s -v -l SynchronizedDemo.class。
从上面我们可以看出:
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
② synchronized 修饰方法的的情况
public class SynchronizedDemo2 {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
JDK1.6 之后的底层优化
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。
①偏向锁
引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉。
偏向锁的“偏”就是偏心的偏,它的意思是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步!关于偏向锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。
但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。
② 轻量级锁
倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。
轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!
③ 自旋锁和自适应自旋
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。
互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。
一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋。
百度百科对自旋锁的解释:
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋"一词就是因此而得名。
自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过--XX:+UseSpinning参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。如果锁被占用的时间短,那么效果当然就很好了!反之,相反!自旋等待的时间必须要有限度。如果自旋超过了限定次数任然没有获得锁,就应该挂起线程。自旋次数的默认值是10次,用户可以修改--XX:PreBlockSpin来更改。
另外,在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了。
④ 锁消除
锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。
⑤ 锁粗化
原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。
大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。
6.并发编程的三个重要特性
- 原子性 : 一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。
synchronized可以保证代码片段的原子性。 - 可见性 :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。
volatile关键字可以保证共享变量的可见性。 - 有序性 :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。
volatile关键字可以禁止指令进行重排序优化。
7.说说 synchronized 关键字和 volatile 关键字的区别
synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在:
- volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
- 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
- volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
JAVA-JVM
1.多态 动态绑定 静态绑定
静态绑定
Java中只有private、static和final修饰的方法以及构造方法是静态绑定。
a、private方法的特点是不能被继承,也就是不存在调用其子类的对象,只能调用对象自身,因此private方法和定义该方法的类绑定在一起。
b、static方法又称类方法,类方法属于类文件。它不依赖对象而存在,在调用的时候就已经知道是哪个类的,所以是类方法是属于静态绑定。
c、final方法:final方法可以被继承,但是不能被重写,所以也就是说final方法是属于静态绑定的,因为调用的方法是一样的。
总结:如果一个方法不可被继承或者继承后不可被覆盖,那么这个方法就采用的静态绑定。
所有私有方法、静态方法、构造器及初始化方法
动态绑定
在JVM加载类的同时,会在方法区中为这个类存放很多信息。其中就有一个数据结构叫方法表。它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存中的直接地址 。下图是上面源代码中Father和Sun类在方法区中的方法表:
上图中的方法表有两个特点:(1) 子类方法表中继承了父类的方法,比如Father extends Object。 (2) 相同的方法(相同的方法签名:方法名和参数列表)在所有类的方法表中的索引相同。比如Father方法表中的f1()和Son方法表中的f1()都位于各自方法表的第11项中。
绑定过程:
类对象方法的调用必须在运行过程中采用动态绑定机制。
1.首先,根据对象的**声明类型(对象引用的类型)**找到“合适”的方法。具体步骤如下:
① 如果能在声明类型中匹配到方法签名完全一样(参数类型一致)的方法,那么这个方法是最合适的。
② 在第①条不能满足的情况下,寻找可以“凑合”的方法。标准就是通过将参数类型进行自动转型之后再进行匹配。如果匹配到多个自动转型后的方法签名f(A)和f(B),则用下面的标准来确定合适的方法:传递给f(A)方法的参数都可以传递给f(B),则f(A)最合适。反之f(B)最合适 。
③ 如果仍然在声明类型中找不到“合适”的方法,则编译阶段就无法通过。
2.然后,根据在堆中创建对象的实际类型找到对应的方法表,从中确定具体的方法在内存中的位置。
**归纳:**1.先找引用类型的方法表,确定方法的索引。2.再找堆中对象实际类型的方法表,根据索引,确定要调用的方法。
比如:father x=new son(); x.aciton();
绑定过程:1.先找类father的方法表,找出action()方法的索引下标。
2.再找son的方法表,由下标直接确定action()方法的内存位置,调用之。
JAVA-基础知识
1.简述内部类、静态内部类、匿名内部类的区别
内部类:
成员内部类可访问外部类所有的方法和成员变量。
不能有静态的方法和成员变量。
静态内部类:
只能访问外部类的静态成员变量与静态方法。
静态内部类的非静态成员可访问外部类的静态变量,而不可访问外部类的非静态变量。
匿名内部类:
没有类名,没有class关键字也没有extends和implements等关键字修饰。
类的定义和对象的实例化同时进行。
2.数组和链表的异同
数组的特点:
- 在内存中,数组是一块连续的区域。
- 插入和删除数据效率低(插入数据时,这个位置后面的数据都要后移)
- 适合随机访问。查询效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给地址的数据(直接索引就能实现,不需要重头遍历)。
- 不利于扩展,一开始数组定义的空间不够时要重新定义数组。
链表的特点:
- 在内存中,不要求连续。
- 每一个数据都保存了下一个数据的内存地址(指针),通过这个指针指向下一个数据。
- 增加和删除数据很容易。 (增加一个数,不需要动后面的数据,直接改变指针指向就行)
- 查找效率低,只能重头开始依次遍历寻找。
- 不指定大小,扩展方便。链表大小不用定义,数据随意增删。
面试问题:介绍一下数组与链表的优缺点与适用场合
-
数组:查找快,增删慢;链表:增删快,查找慢
-
4、按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);
5、按值查找时,若数组无序,数组和链表时间复杂度均为O(n),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
-
-
数组的内存在数组定义时分配,因此元素个数是固定的;链表结点的内存在程序执行时动态向系统申请,因此链表节点个数可按照需要动态增减;
-
数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定;
-
数组中的元素顺序关系由元素由索引体现,链表中的结点顺序关系由指针来体现;
-
数组大小固定,不能动态扩展;但是链表可以进行动态增删。
-
存储空间上,链表由于带有指针域,存储密度不如数组大;
-
空间分配方面,数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;
数组应用场景: 1、注重存储密度; 2、经常做的运算是按序号访问数据元素; 3、数组更容易实现,任何高级语言都支持; 4、构建的线性表较稳定。
链表应用场景: 1、对线性表的长度或者规模难以估计; 2、频繁做插入删除操作; 3、构建动态性比较强的线性表。
算法
Lee code 746746. 使用最小花费爬楼梯
z字型打印二叉树剑指 Offer 32 - III. 从上到下打印二叉树 III
leetcode67 剑指 Offer 67. 把字符串转换成整数todo
leecode5353. 最大子序和
题目是:给定一个包含大写英文字母和数字的句子,找出这个句子所包含的最大的十六进制整数,返回这个整数的值。数据保证该整数在int表示范围内。
样例:输入:012345BZ16 输出:1193051 说明:12345B对应的十进制为1193051