面试考点随笔——牛客

在不久前,我参加了腾讯的线上笔试,可是做得很不理想。其实,在此之前,我是认真复习过的,也很认真的研究了java,计算机网络,数据库..等等各方面的知识原理。但是,做的却感觉完全没发挥出自己的付出。我考完后,有点炸的感觉,就玩了两天,静下心来了,总结了下,感觉这次的失败原因有两个,一是,自己的知识面真的不广,至少腾讯有些偏的东西感觉很陌生,第二,没有适应这种笔试模式的考核。第一次参加这种线上笔试,之前也没有做过类似的题目,所以复习的并没有中。


因此,尽管系统性的知识很重要,但是,这种斑驳的零星题目,也是要重视,可能透出来的就是背后牵扯的一大堆原理了。以题究全,有一定可取性




持续更新


 **************************************************************

Socket的几种模式分析:

同步:这种Socket就是在得到结果前,不会做其它事,就像浏览器在请求一个网页的时候,如果未得到网页内容,就不会做任何事

异步:这种Socket在未得到结果前,可以做其他事,如Ajax部分更新,当发出一个请求验证码的时候,我们还可以继续浏览网页的其它内容

阻塞:当未得到结果的时候,就把线程阻塞,当结果返回了,发出唤醒信号,将阻塞的线程编程就绪状态,在某一刻继续执行

非阻塞:当未得到结果前,返回一些提示性的进度信息,知道结果返回



 **************************************************************


Q:windows系统中,InvalidateRect(NULL,Fasle)的作用

A:这个方法的作用是,使整个用户区重绘,并且判断是否在重绘前擦出背景


 **************************************************************

要明白一个问题。进程是资源的管理者和分配者,而线程是资源的拥有者。而一个线程拥有很多线程,共享着进程的资源。尤其是,每个进程都有一个堆(heap)和一个栈(stack)。我称为对象堆,方法栈,变量栈,对象引用栈,基础数据类型栈。上述可知,堆存放的是对象,而GC正是对这个存储容器进行清扫的。而GC最基本的清扫限制是,如果栈中没有引用变量指向堆中的对象,就会将它回收。当然,实际上,过程并没有这么简单。

这篇博客,就详细地讲述了Java中GC的原理。我就不再赘述了

http://www.hollischuang.com/archives/76

然后,在线程中还有一个静态区,用来存放静态变量,方法和类

**************************************************************

Java中的异常处理机制类是Throwable
Exception,Error都是继承自Throwable;一般来说,Exception是程序可处理的,Error是程序不可处理的

Exception的子类分为运行异常(非检查异常)和非运行异常(检查异常)。前者在无论异常与否,编译器不会发现,只有在运行到相应代码的时候,才会报错,而后者在代码编译时,就要求加上异常处理;下面这篇博客讲的很详尽:

其实,异常中最巧的就是try,catch,finally机制的return返回。在上述博客中,有提到。我想补充的是

public int test1() {  
        try {  
            return 1;  
        } finally {  
            return 2;  
        }  
    }  


其实在方法的调用上,是用栈来实现的,在方法调用的源点,会把源点所在的方法压栈,包括方法内的临时变量。然后执行调用方法。当遇到上述代码的情况时,我们会先返回1,此时,是将结果压栈,但并未马上就用到这个结果。然而,异常处理机制保证了,不管结果返回与否,我们都将执行finally代码块。所以,结果只2又再次被压栈。当源点方法调用结果时,将会取出的是后压入栈的结果。而1则被废弃

**************************************************************

Q:以下关于TCP/IP协议的描述中,不正确的是()

A、TCP负责将信息拆分为数据包,并在数据包达到目的地后对其进行装配

B、IP负责为数据包选择路由以使将其传递到正确的目的地

C、.TCP协议是可靠的服务,当客户端第一次向服务端发送会话请求的时候,就会把数据传输过去

D、IP、ICMP和IGMP都是网络层的协议

A:A、C

解: 是否拆包的产生的原因有三个,分别如下.

1、应用程序 write 写入的字节大小大于套接字接口发送缓冲区大小;

2、进行 MSS 大小的 TCP 分段;

3、以太网帧的 payload 大于 MTU 进行 IP 分片;

 

 

 **************************************************************

Q:一个以". java"为后缀的源文件,哪些说法是正确的?

A、只能包含一个类,类名必须与文件名相同

B、只能包含与文件名相同的类,以及其中的内部类

C、只能有一个与文件名相同的类,可以包含其他类

D、可以包含任意类

 

A:C

在一个.java的源文件中,只能包含一个public的class,并且这个class的名字须和.java的文件名一致,可以包含任意其他类

 

 

 **************************************************************

如何证明线程安全的?

首先我们要定义线程安全,我比较认可的是在《Java concurrency in practice》一书中的定义:
一个不论运行时(Runtime)如何调度线程都不需要调用方提供额外的同步和协调机制还能正确地运行的类是线程安全的
多线程的场景很多很复杂,难以穷尽地说那些条件下是或者不是线程安全的,但是有一些常用的肯定线程安全的场景:


1.无状态的一定是线程安全的。这个很好理解,因为所谓线程不安全也就是一个线程修改了状态,而另一个线程的操作依赖于这个被修改的状态。

2.只有一个状态,而且这个状态是由一个线程安全的对象维护的,那这个类也是线程安全的。比如你在数据结构里只用一个AtomicLong来作为计数器,那递增计数的操作都是线程安全的,不会漏掉任何一次计数,而如果你用普通的long做++操作则不一样,因为++操作本身涉及到取数、递增、赋值 三个操作,某个线程可能取到了另外一个线程还没来得及写回的数就会导致上一次写入丢失。

3.3.有多个状态的情况下,维持不变性(invariant)的所有可变(mutable)状态都用同一个锁来守护的类是线程安全的。这一段有些拗口,首先类不变性的意思是指这个类在多线程状态下能正确运行的状态,其次用锁守护的意思是所有对该状态的操作都需要获取这个锁,而用同一个锁守护的作用就是所有对这些状态的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。所以这里的关键在于如何确定不变性,可能你的类的某些状态对于类的正确运行是无关紧要的,那就不需要用和其他状态一样的锁来守护。因此我们常可以看到有的类里面会创建一个新的对象作为锁来守护某些和原类本身不变性无关的状态。



作者:Leo Yang
链接:https://www.zhihu.com/question/26595480/answer/33533759
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

**************************************************************

QArrayListVector主要区别是什么?

AVectorArrayList一样,也是通过数组实现的,不同的是Vector支持线程的同步

BVectorArrayList一样,也是通过数组实现的,不同的是ArrayList支持线程的同步

CVector是通过链表结构存储数据,ArrayList是通过数组存储数据

D上述说法都不正确

 

AA

解:其实java中的数据结构,一般来说,都不会是原子操作,也不会是形如AtmoicLong的数据类型,所以,很多线程安全的的结构,都是通过加锁保护的方法实现现象上的并行,逻辑上的串行。VectorHashTable 都是jdk2中实现的线程安全的数据结构,但是就是因为安全锁的开销,使其效率极低,所以在jdk5中加入了HashMapArrayList这两个线程不安全的结构,但是通过形如


Colletions.synchronizedArrayList(new ArrayList<String>());


这样的方法也可以实现线程安全机制


其次是,我曾经在阿里的一面中遇到的问题,如何实现Set容器呢?

其实,Set的容器有一个显著的特性,那就是值的唯一性。在Set的实现类HashSet中,可以存在空值和唯一的值。而has这整个的数据结构,又是基于HashMap实现的。我在《哈希算法&&Java中的HashMap实现原理》一文中做了分析。


通过源码可知,set其实是一个只有key,而没有value的hashMap


  // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();



    /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null ? e2==null : e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }



每次value插入都是final的一个值,这个意味着这个对象是不变的。通过每次在插入前,判断,插入的值,是否已经存在,是否为空,来严格限定一个容器中,值的唯一性


import java.util.ArrayList;
import java.util.HashSet;

public class Test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		HashSet<String> set = new HashSet<>();
		set.add(null);
		set.add(null);
		set.add("1");
		set.add("2");
		set.add("3");
		set.add("2");
		set.add("1");
		set.add("2");
		set.add("3");
		set.add("2");
		
		
		for(String str : set){
			System.out.println(str);
		}
		
		System.out.println();

		System.out.println();


	}

	
	
	
	
}




结果是:





 **************************************************************


Java并发编程注意点:


**脏读、不可重复读、丢失更新说明


丢失更新:A和B同时对共享资源进行修改;而后A想用自己修改后的值,却用成了B更新的值

脏读:A对数据进行修改了,但是未提交。而B要使用A更新后的数据,由于A未提交,B取到的是A更新前的数据

不可重复读:A读取了数据100,B修改为200,A重新读值变为200

**Java中Thread类一些方法的作用


这张图就很直观地显示了Thread的方法的作用:

start:线程启动。曾遇到一个直接引用形如



Thread t = new Thread(){
           public void run(){
           //do something
      }

}

t.run();

 

当时就懵了,后来想想,是自己傻,这个调用,并未启动一个子线程,只是在原有线程中,执行了一下run方法


run:Thread执行体,也是最主要的内容

yield:得到了CPU的使用权,却放弃,并让自己重新获得就绪状态;在它放弃之后的竞争中,此线程仍然有机会获得CPU使用权

wait:放弃CPU的使用权,让自己进入阻塞状态;直到有其它线程对它进行notify,notifyAll,才能重新进入就绪状态

notify:起先,我一直不懂,为什么notify容易产生死锁;在搜索了相关资料后发现,notify只唤醒一个阻塞的线程,至于是哪个,有相关的决策(并不懂)。由于选项只有一个,如果这个选项无法进入运行状态呢?就变成就绪队列中,只有一个有问题的线程。无法获得锁,自然也无法去唤醒下一个线程了

notifyAll:唤醒所有阻塞的线程,让它们去竞争下一次的CPU使用权

join:要指定一个先序线程,然后将本线程变为阻塞状态。当指定线程通过run,结束了线程周期后,调用join的线程将变为就绪状态。这其实是两个线程先后执行的实现

sleep:让线程进入休眠状态,当时不释放CPU的使用权,状态不变;也就是说sleep操作可以看作是run的执行内容




**Java中ConcurrentHashMap的新特性

Java 中ConcurrentHashMap的数据结构实现与HashMap的不同之处在于:HashMap是基于数据加链表的形式,而ConcurrentHashMap在容量较小的时候,与之无异,但是但容器达到一定限制的时候,就会转成数组加红黑树的结构,这样做的目的是在,分域之后,继续优化遍历的效率。

并且,ConcurrentHashMap是线程安全的,他区别与如HashTable这样很老的线程安全数据结构,他的线程锁是分段的,并不是一个HashMap就只能一个线程持有,可以分线程持有不同段的锁



**java中线程锁的种类:

1、自旋锁(SpinLock):

百度百科定义:与互斥锁相似;无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

使用场景:

由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。



2、自旋锁的子类(:TicketLock ,CLHlock 和MCSlock):

这篇博客讲的很清楚:http://ifeve.com/java_lock_see/


3、阻塞锁(synchronized ,ReentrantLock,LockSupport):


**其实,在所有Object的方法中都有一个wait方法。用来挂起当前线程,使其阻塞,释放阻塞锁。当别的线程调用Object.notify()/notifyAll()方法后(说明这个线程已经使用线程占用的资源了,所以释放锁,并发出下一轮争抢信号)才可以使其唤醒,进入就绪状态,开始争用CPU时间,获得锁的使用权


**synchronized和Lock的区别是:synchronized是一种隐式锁,并且它的持有和释放都是自动的,只要不再synchronzied花括号代码块里面的,都不具有持锁性质。而Lock则需要手动加锁和解锁(必须在finally语句中释放)。这就可以使逻辑不再一个代码块中却实现了互斥。同时,手动释放将使代码的不定性因素变得更少了些。


**synchronized锁,可以获取多个锁,但是获得锁的顺序和释放锁的顺序应该相反




**LockSupport:以线程为对象的阻塞锁。每次传入的参数是一个线程对象。我自己对其的理解是,LockSupport是在Object基础上升华的。因为Object的notify方法的唤醒对象是不指定的。

而LockSupport中的park(Thread itselfThread),unpark(Thread otherThred)。

我们模拟一个过程:

当一个线程A执行park(A)后,就相当于车停了,等待再次启动的许可——意味这线程A阻塞。当另一个线程B,执行B.unpark(A)后,相当于,我不想动了,我把这个继续动的权利传递给A;这时park和unpark配对了,所以唤醒线程A。而在这过程中,也可以是B.unpark(A)先执行。A.park(A)后执行。


(这个LockSupport是Java并发的最高体现,我看起来很难受,理解了很久了,不知道对不对)



4、ReentrantLock(可重入锁):

可重入锁指的是在一个线程中可以多次获取同一把锁,比如:
一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁;

这个其实是Lock接口的实现类,所以,他继承了Lock的所有特性。并且,他还能决定,对于释放锁后对于锁的争抢,是公平的还是不公平。上述的synchronized也是可重入锁,SpinLock是不可重入锁


使用场景:对于持锁递归方法的调用



5、读写锁(ReentrantLock,读写锁的典型,可支持多读单写)

6、互斥锁(synchronized,ReentrantLock)

7、悲观锁(synchronized):

我的理解是:悲观锁认为代码是线程不安全的,需要互斥的一种悲观现象

8、乐观锁(CAS操作):
我的理解是:乐观锁认为代码是线程安全的,可执行CAS操作的一种乐观现象

9、公平锁(ReentrantLock):

提供两种锁,公平锁和非公平锁。当锁是公平的时候,如果资源被占有,就会加入队列。按顺序执行,资源占有;如果锁是非公平的,则会在队列中跳跃,尽快的得到资源占有

10、非公平锁(synchronized):

保证不了公平性

11、偏向锁

12、对象锁

13、线程锁

14、锁粗化

15、轻量级锁(volatile):

16、锁消除

17、锁膨胀

18、信号量(LockSupport实现就设计信号量机制和CAS操作)



**************************************************************


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值