标签:线程 返回值 队列实现 指令 应该 使用 获取 cep let
摘要:最近和女友聊天,说我的工作需要作出调整,当前状态下压力太大,急需通过提供自身的专业技能来作出改变,所以便有了这个基础知识的整理。本来这个帖子是发布在简书的,因为考虑到简书比较好编辑和阅览,但是当我发布到简书后,女友竟然惊讶和肯定我终于开始写博客了,于是比较汗颜,就还是回归发布到这里来吧。后续针对这些基础知识做更深次,更全面的研究,本基础的一些问题来源:http://blog.csdn.net/exceptional_derek/article/details/69525715
答:HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变,HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。对HashMap的操作不是线程安全的,通过观察源码发现,当多个线程在某一个时刻同时对HashMap做结构性的修改,我们可以看到整个方法实现中没有任何的同步机制,那么存在一个线程获取或者修改数据结构时,存在另外一个线程获取了一个错误的结果。jdk8对hashMap的数据结构的改变有个调整,当数组达到一定的阈值时,bucket就会由链表转换为红黑树的方式进行存储,而不是进行table的扩容。
答:首先String类是用final关键字修饰,这说明String不可继承,String类的成员字段value是个char[]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。不可变的好处:1.1.参考java字符串池的设计模式。比如两个字符串值相等的变量,他们只会生成一个对象放到常量池中,然后两个变量都指向它,提升效率。1.2.安全性,如果String类可以被修改,那么在多线程的情况下会造成安全漏洞。2.1 StringBuilder和StringBuffer的区别:他们都是创建字符串的常用类,长度都是可以扩充的,实现了CharSequence接口。StringBuilder非线程安全,StringBuffer线程安全,所以通常在单线程环境下可以考虑是用StringBuilder来提升速度和效率,而在多线程的环境下则应该使用SringBuffer来保证线程安全。
答:反射的机制是在编译时并不确定的哪个类被jvm加载,在程序运行的时候才加载、探知、自审。动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。两者区别:jdk的代理是利用反射生成字节码,并生成对象,前提是只能代理实现了接口的类,cglib是直接修改目标类的字节码生成对象,因为原理是继承,所以不能对final修饰的类进行代理。http://rejoy.iteye.com/blog/1627405https://my.oschina.net/tearsky/blog/635321
答:自动装箱是将内置类型转换为对应的包装类型,在自动装箱的过程中,程序会创建一个包装类型的对象,然后将该变量指向这个新创建的对象,完成装箱操作。
答:
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现。
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果。
7、抽象类里可以没有抽象方法。
8、如果一个类里有抽象方法,那么这个类只能是抽象类。
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
答:ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
改进一:取消segments字段,直接采用transient volatile HashEntry[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。
答:HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。
答:对传统的、基本的GC实现来说,由于它们在GC的整个工作过程中都要“stop-the-world”,如果能想办法缩短GC一次工作的时间长度就是件重要的事情。如果说收集整个GC堆耗时太长,那不如只收集其中的一部分?于是就有好几种不同的划分(partition)GC堆的方式来实现部分收集,而分代式GC就是这其中的一个思路。
-Xmx2g //JVM最大允许分配的堆内存,按需分配
-Xms2g //JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存。-Xmn256m //年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代年轻代分三个区, 分别是enden区和两个survivor区。
1.可见性:JMM提供了volatile变量定义、final、synchronized块来保证可见性。
2.有序性:这个概念是相对而言的,如果在本线程内,所有的操作都是有序的,如果在一个线程观察另一个线程,所有的操作都是无序的,前句是“线程内表现为串行行为”,后句是“指令的重排序”和“工作内存和主内存同步延迟”现象,模型提供了volatile和synchronized来保证线程之间操作的有序性。
3.重排序:在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序(编译器、处理器),就是因为这些重排序,所以可能会导致多线程程序出现内存可见性问题(数据安全问题)和有序性问题。可见性、原子性、有序性.
11、什么是跳表?
答:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。主要是通过获取锁标记来获取对该资源的使用权限,当对象调用了start()进入到就绪状态,进入就绪后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;接下来的状态切换就会比较复杂,主要通过线程调用不同的方法,就会切换不同的运行状态。
答:volatile让变量每次在使用的时候,都从主存中取。(1.将当前处理器缓存行的数据会写回到系统内存,2.这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。)而不是从各个线程的“工作内存”。volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。
答:类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
答: java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。
同步的实现方式总共分为七种:
1.同步方法 : 即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
2.同步代码块:即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
3.使用特殊域变量(volatile)实现线程同步
a.volatile关键字为域变量的访问提供了一种免锁机制。
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新。
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值。
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
4.使用重入锁实现线程同步.
java.util.concurrent包下的ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力.
5.使用局部变量实现线程同步,如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响
6.使用阻塞队列实现线程同步,前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。使用LinkedBlockingQueue来实现线程的同步, LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue。队列是先进先出的顺序(FIFO)。
7.使用原子变量实现线程同步,需要使用线程同步的根本原因在于对普通变量的操作不是原子的。原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作,即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
答:ThreadLocal提供了set和get访问器用来访问与当前线程相关联的线程局部变量。当线程中的threadlocalmap是null的时候,会调用createmap创建一个map。同时根据函数参数设置上初始值。也就是说,当前线程的threadlocalmap是在第一次调用set的时候创建map并且设置上相应的值的。在ThreadLocal的set函数中,可以看到,其中的map.set(this, value);把当前的threadlocal传入到map中作为键,也就是说,在不同的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal作为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对。
答:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
对于方法的同步,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
答:sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
答:1.newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
2.newCachedThreadPool创建一个可缓存的线程池。这种类型的线程池特点是:
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3.newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4.newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。
总结:
一.FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
二.CachedThreadPool的特点就是在线程池空闲时,即线程池中没有可运行任务时,它会释放工作线程,从而释放工作线程所占用的资源。但是,但当出现新任务时,又要创建一新的工作线程,又要一定的系统开销。并且,在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
答:
减少在创建和销毁线程上所花的时间以及系统资源的开销。
如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
答:
答:execute(Runnable x) 没有返回值。可以执行任务,但无法判断任务是否成功完成。——实现Runnable接口
submit(Runnable x) 返回一个future。可以用这个future来判断任务是否成功完成。——实现Callable接口。
答:
答:1.继承Thread类创建线程类
2.通过Runnable接口创建线程类
3.通过Callable和Future创建线程
答:
1.文件很大,不可能全部存储在内存中,故要存储到磁盘上,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,
答:,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable,
答:共享读锁,独占写锁。根据数据引擎的不同,锁的类型也不一样,对于innodb
原子性、一致性、隔离性、持久性。
答:1.查询条件带上索引,
这部分主要根据简历以及项目的实际情况来问。
标签:线程 返回值 队列实现 指令 应该 使用 获取 cep let
原文地址:http://www.cnblogs.com/_popc/p/7228149.html