找工作一定要做好Homework,认准准备笔试和面试,认真研究你投简历的公司
如果你能给应聘公司提出建议,指出他们公司产品的不足,可以改进的地方,那应聘成功的几率就回提高
之前有朋友分析了某个公司的Android客户端App,写了一份邮件发送给了那个公司,然后有了一次面试机会,那家公司没有在招聘,竟然成功了
心态很重要!
心态很重要!
心态很重要!
找工作之前,有一点你必须清楚,就是找工作是一件看缘分的事情,不是你很牛逼,你就一定能进你想进的公司,都是有一个概率在那。如果你基础好,项目经验足,同时准备充分,那么你拿到offer的概率就会比较高;相反,如果你准备不充分,基础也不好,那么你拿到offer的概率就会比较低,但是你可以多投几家公司,这样拿到offer的几率就要大一点,因为你总有运气好的时候。所以,不要惧怕面试,刚开始失败了没什么的,多投多尝试,面多了你就自然能成面霸了。得失心也不要太重,最后每个人都会有offer的。
还有一个对待工作的心态,有些人可能觉得自己没有动力去找一个好工作。其实你需要明白一件事情,你读了十几二十年的书,为的是什么,最后不就是为了找到一个好工作么。现在到了关键时刻,你为何不努力一把呢,为什么不给自己一个好的未来呢,去一个自己不满意的公司工作,你甘心吗?
想清楚这一点,我相信大多数人都会有一股干劲了,因为LZ刚刚准备开始找实习的时候,BAT这种公司想都不敢想,觉得能进个二线公司就很不错了,后来发现自己不逼自己一把,你真不知道自己有多大能耐,所以请对找工作保持积极与十二分的热情,也请认真对待每一次笔试面试。
J2SE基础
一、八种基本数据类型的大小,以及他们的封装类?
Java提供了一组基本数据类型,包括 boolean, byte, char, short, int, long, float, double.
同时,java也提供了这些类型的封装类,分别为 Boolean, Byte, Character, Short, Integer, Long, Float, Double
为什么Java会这么做?在java中使用基本类型来存储语言支持的基本数据类型,这里没有采用对象,而是使用了传统的面向过程语言所采用的基本类在型,主要是从性能方面来考虑的:因为即使最简单的数学计算,使用对象来处理也会引起一些开销,而这些开销对于数学计算本来是毫无必要的。但是在java中,泛型类包括预定义的集合,使用的参数都是对象类型,无法直接使用这些基本数据类型,所以java又提供了这些基本类型的包装器。
区别:1、基本数据类型只能按值传递,而封装类按引用传递,2、基本类型在堆栈中创建;而对于对象类型,对象在堆中创建,对象的引用在堆栈中创建。基本类型由于在堆栈中,效率会比较高,但是可能会存在内存泄漏的问题。
二、Switch能否用string做参数?
在 Java 7之前,switch 只能支持 byte、short、char、int或者其对应的封装类以及 Enum 类型。在 Java 7中,String支持被加上了。
三、equals与==的区别?
“==”比较的是值【变量(栈)内存中存放的对象的(堆)内存地址】
equal用于比较两个对象的值是否相同【不是比地址】
【特别注意】Object类中的equals方法和“==”是一样的,没有区别,而String类,Integer类等等一些类,是重写了equals方法,才使得equals和“==不同”,所以,当自己创建类时,自动继承了Object的equals方法,要想实现不同的等于比较,必须重写equals方法。”==”比”equal”运行速度快,因为”==”只是比较引用.
四、 Object有哪些公用方法?
Object o = new Object();
/**
* 比较当前对象和是否等于另一个对象,指向的对象是否相同
*/
System.out.println(o.equals(new Object()));
/**
* 返回hashCode
*/
System.out.println(o.hashCode());
/**
* 返回包名+类名+Integer.toHexString(hashCode())
*/
System.out.println(o.toString());
/**
* 返回class对象
*/
System.out.println(o.getClass());
try {
/**
* 线程等待,Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
*/
o.wait();
o.wait(1000);
o.wait(1000,1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:
* notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
* notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
*/
o.notify();
o.notifyAll();
五、Java的四种引用,强弱软虚,用到的场景。
强引用
最普遍的一种引用方式,如String s = “abc”,变量s就是字符串“abc”的强引用,只要强引用存在,则垃圾回收器就不会回收这个对象。
软引用(SoftReference)
用于描述还有用但非必须的对象,如果内存足够,不回收,如果内存不足,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue联合使用,如果软引用的对象被垃圾回收,JVM就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
弱引用和软引用大致相同,弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用(PhantomReference)
就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中
六、Hashcode的作用
hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable
七、ArrayList、LinkedList、Vector的区别
这三者都是实现了List接口,都拥有List接口里面定义的方法,并且同时拥有Collection接口的方法;
ArrayList:采用的是数组的方式进行存储数据的,查询和修改速度快,但是增加和删除速度慢;线程是不同步的
LinkedList:采用的是链表的方式进行存储数据的,查询和修改速度慢,但是增加和删除速度快;线程是不同步的
Vector:也采用的是数组的方式进行存储的,Vector在java1.0以前用,但是ArrayList是在java1.2版本后使用的,线程是同步的,效率相比ArrayList来说慢一点;同时Vector查询数据有迭代器,有枚举,有get(int index),有indexOf(int index)四种方式,而ArrayList却没有枚举
八、 String、StringBuffer与StringBuilder的区别
1、可变与不可变
String类中使用字符数组保存字符串,如下就是,因为有“final”修饰符,所以可以知道string对象是不可变的。
private final char value[];
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。
char[] value;
2、是否多线程安全
String中的对象是不可变的,也就可以理解为常量,显然线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的
九、Map、Set、List、Queue、Stack的特点与用法
Map
Map是键值对,键Key是唯一不能重复的,一个键对应一个值,值可以重复。
TreeMap可以保证顺序,HashMap不保证顺序,即为无序的。
Map中可以将Key和Value单独抽取出来,其中KeySet()方法可以将所有的keys抽取正一个Set。而Values()方法可以将map中所有的values抽取成一个集合。
Set
不包含重复元素的集合,set中最多包含一个null元素
只能用Lterator实现单项遍历,Set中没有同步方法
List
有序的可重复集合。
可以在任意位置增加删除元素。
用Iterator实现单向遍历,也可用ListIterator实现双向遍历
Queue
Queue遵从先进先出原则。
使用时尽量避免add()和remove()方法,而是使用offer()来添加元素,使用poll()来移除元素,它的优点是可以通过返回值来判断是否成功。
LinkedList实现了Queue接口。
Queue通常不允许插入null元素
Stack
Stack遵从后进先出原则。
Stack继承自Vector。
它通过五个操作对类Vector进行扩展,允许将向量视为堆栈,它提供了通常的push和pop操作,以及取堆栈顶点的peek()方法、测试堆栈是否为空的empty方法等
如果涉及堆栈,队列等操作,建议使用List
对于快速插入和删除元素的,建议使用LinkedList
如果需要快速随机访问元素的,建议使用ArrayList
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
十、 HashMap和HashTable的区别。
1.HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
2.HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
3.另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
4.由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
5.HashMap不能保证随着时间的推移Map中的元素次序是不变的
十一、HashMap和ConcurrentHashMap的区别,HashMap的底层源码。
Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。
ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment(类似hashtable),默认16个(concurrency level),然后在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好
十二、TreeMap、HashMap、LindedHashMap的区别
1.HashMap里面存入的键值对在取出的时候是随机的,也是我们最常用的一个Map.它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
2.TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
3. LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现. (应用场景:购物车等需要顺序的)
十三、Collection包结构,与Collections的区别。
Collection是集合类的上级接口,子接口主要有Set 和List、Map。
Collections是针对集合类的一个帮助类,提供了操作集合的工具方法:一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
十四、try catch finally,try里有return,finally还执行么?
1、不管有木有出现异常,finally块中代码都会执行;
2、当try和catch中有return时,finally仍然会执行;
3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;
4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
情况1:try{} catch(){}finally{} return;
显然程序按顺序执行。
情况2:try{ return; }catch(){} finally{} return;
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,最后执行try中return;
finally块之后的语句return,因为程序在try中已经return所以不再执行。
情况3:try{ } catch(){return;} finally{} return;
程序先执行try,如果遇到异常执行catch块,
有异常:则执行catch中return之前(包括return语句中的表达式运算)代码,再执行finally语句中全部代码,
最后执行catch块中return. finally之后也就是4处的代码不再执行。
无异常:执行完try再finally再return.
情况4:try{ return; }catch(){} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况5:try{} catch(){return;}finally{return;}
程序执行catch块中return之前(包括return语句中的表达式运算)代码;
再执行finally块,因为finally块中有return所以提前退出。
情况6:try{ return;}catch(){return;} finally{return;}
程序执行try块中return之前(包括return语句中的表达式运算)代码;
有异常:执行catch块中return之前(包括return语句中的表达式运算)代码;
则再执行finally块,因为finally块中有return所以提前退出。
无异常:则再执行finally块,因为finally块中有return所以提前退出。
最终结论:任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话。
如果finally中有return语句,那么程序就return了,所以finally中的return是一定会被return的,
编译器把finally中的return实现为一个warning。
十五、Excption与Error包结构。OOM你遇到过哪些情况,SOF你遇到过哪些情况
(一)Throwable
Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类或其子类之一的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出,才可以是 catch 子句中的参数类型。
Throwable 类及其子类有两个构造方法,一个不带参数,另一个带有 String 参数,此参数可用于生成详细消息。
Throwable 包含了其线程创建时线程执行堆栈的快照。它还包含了给出有关错误更多信息的消息字符串。
Java将可抛出(Throwable)的结构分为三种类型:
错误(Error)
运行时异常(RuntimeException)
被检查的异常(Checked Exception)
1.Error
Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题。大多数这样的错误都是异常条件。
和RuntimeException一样, 编译器也不会检查Error。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误,程序本身无法修复这些错误的。
2.Exception
Exception 类及其子类是 Throwable 的一种形式,它指出了合理的应用程序想要捕获的条件。
对于可以恢复的条件使用被检查异常(Exception的子类中除了RuntimeException之外的其它子类),对于程序错误使用运行时异常。
① ClassNotFoundException
当应用程序试图使用以下方法通过字符串名加载类时:
Class 类中的 forName 方法。
ClassLoader 类中的 findSystemClass 方法。
ClassLoader 类中的 loadClass 方法。
但是没有找到具有指定名称的类的定义,抛出该异常。
② CloneNotSupportedException
当调用
Object 类中的 clone 方法复制对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。重写 clone 方法的应用程序也可能抛出此异常,指示不能或不应复制一个对象。
③ IOException
当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。
-EOFException
当输入过程中意外到达文件或流的末尾时,抛出此异常。
此异常主要被数据输入流用来表明到达流的末尾。
注意:其他许多输入操作返回一个特殊值表示到达流的末尾,而不是抛出异常。
-FileNotFoundException
当试图打开指定路径名表示的文件失败时,抛出此异常。
在不存在具有指定路径名的文件时,此异常将由 FileInputStream、FileOutputStream 和 RandomAccessFile 构造方法抛出。如果该文件存在,但是由于某些原因不可访问,比如试图打开一个只读文件进行写入,则此时这些构造方法仍然会抛出该异常。
-MalformedURLException
抛出这一异常指示出现了错误的 URL。或者在规范字符串中找不到任何合法协议,或者无法解析字符串。
-UnknownHostException
指示主机 IP 地址无法确定而抛出的异常。
④ RuntimeException
是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明。
Java编译器不会检查它。当程序中可能出现这类异常时,还是会编译通过。
虽然Java编译器不会检查运行时异常,但是我们也可以通过throws进行声明抛出,也可以通过try-catch对它进行捕获处理。
-ArithmeticException
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
-ClassCastException
当试图将对象强制转换为不是实例的子类时,抛出该异常。
例如:Object x = new Integer(0);
-LllegalArgumentException
抛出的异常表明向方法传递了一个不合法或不正确的参数。
-IllegalStateException
在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
-IndexOutOfBoundsException
指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
应用程序可以为这个类创建子类,以指示类似的异常。
-NoSuchElementException
由 Enumeration 的 nextElement 方法抛出,表明枚举中没有更多的元素。
-NullPointerException
当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括:
调用 null 对象的实例方法。
访问或修改 null 对象的字段。
将 null 作为一个数组,获得其长度。
将 null 作为一个数组,访问或修改其时间片。
将 null 作为 Throwable 值抛出。
应用程序应该抛出该类的实例,指示其他对 null 对象的非法使用。
(二) SOF (堆栈溢出 *)
*Error 的定义:
当应用程序递归太深而发生堆栈溢出时,抛出该错误。
因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
栈溢出的原因:
1.大量循环或死循环
2.全局变量是否过多
3.数组、List、map数据过大
(三)OOM
1.资源对象没关闭造成的内存泄露,try catch finally中将资源回收放到finally语句可以有效避免OOM。资源性对象比如:
1-1,Cursor
1-2,调用registerReceiver后未调用unregisterReceiver()
1-3,未关闭InputStream/OutputStream
1-4,Bitmap使用后未调用recycle()
2.作用域不一样,导致对象不能被垃圾回收器回收,比如:
2-1,非静态内部类会隐式地持有外部类的引用,
2-2,Context泄露,概括一下,避免Context相关的内存泄露,记住以下事情:
1、 不要保留对Context-Activity长时间的引用(对Activity的引用的时候,必须确保拥有和Activity一样的生命周期)
2、尝试使用Context-Application来替代Context-Activity
3、如果你不想控制内部类的生命周期,应避免在Activity中使用非静态的内部类,而应该使用静态的内部类,并在其中创建一个对Activity的弱引用。这种情况的解决办法是使用一个静态的内部类,其中拥有对外部类的weakReference。
2-3,Thread 引用其他对象也容易出现对象泄露。
2-4,onReceive方法里执行了太多的操作
3.内存压力过大
3-1,图片资源加载过多,超过内存使用空间,例如Bitmap 的使用
3-2,重复创建view,listview应该使用convertview和viewholder
十六、Java面向对象的三个特征与含义。
三大特征:封装、继承、多态
封装:
首先,属性可用来描述同一类事物的特征, 行为可描述一类事物可做的操作,封装就是要把属于同一类事物的共性(包括属性与行为)归到一个类中,以方便使用.比如人这个东东,可用下面的方式封装:
人{
年龄(属性一)
身高(属性二)
性别(属性三)
做事(行为之一)
走路(行为之二)
说话(行为之三)
}
继承:
由于封装,使得有共同特征的一类事物的所有描述信息都被归于一类之中,但我们知道,这并不是万能的,有些事物有共性,但还存在区别,比如教师,简单封装起来如下:
教师{
年龄(属性一)
身高(属性二)
性别(属性三)
做事(行为之一)
走路(行为之二)
说话(行为之三)
教书(行为之四)
}
上面对”教师”的封装,与对”人”的封装基本上差不多,只是多了一个特征行为:教书,
教师有与人一样的共性, 但我们不能说”人教书”,也就是不能把教书封装到”人”之中去,教书是教师的特征行为之一. 为了省事地封装教师(代码的复用,这只是继承存在的原因之一), 可以让教师去继承人,如:
教师 extends 人{
教书(行为之三)
}
这样,我们就不用重新定义那些已经被”人”这一个类所封装的那些属性与行为了,而只需要使用继承的方式,在人的基础上拓展教师专有的行为,即”教书”即可把教师描述出来;这样的结果, 即是教师也同时拥有”人”之中所封装的一切属性与行为, 还拥有自己的特征行为”教书”.
多态:
多态的概念发展出来,是以封装和继承为基础的(其实我觉得抽象也应该算是面向对象的大特征之一,要封装,抽象是必须的)
简单的理解一下多态,比如:
人这个类,封装了很多人类共有的特性,
教师是人的子类,继承了人的属性与行为,当然教师有自己的特征行为,比如教书授课;
学生是人的子类,继承了人的属性与行为,当然学生有自己的特征行为,比如学习做作业;
现在,当我们需要去描述教师与学生各自的行为的时候, 我们可以分开来说”教师在授课”, “学生做作业”, 但如果我们要站在抽象的角度, 也就是从教师与学生的父类”人”的角度, 来同时描述他们各自的行为时,我们怎么描述?”人在授课”?”人在做作业”?这是不是怪怪的很不合适?不合适的问题就在于, 对于行为主体,我们使用了抽象层次的东东”人”,而对于行为本身, 我们却使用了具体的东东”授课”与”教书”. 怎么解决呢? 那就需要解决抽象与具体的矛盾问题.
既然是站在抽象在角度来描述,那我们把行为抽象一下,不就能同时描述了吗?比如”人在做事”(教师授课与学生做作业都可以说成人在做事),这样就解决了抽象层次与具体层次之间的矛盾.
到了这一步, 我们可以把两个描述: “教师在做事”, “学生在做事” 两者统一为”人在做事”,
然后, 我们可以在”教师”的”做事”行为中去调用教师自己的特征行为”授课”,在”学生”的”做事”行为中去调用学生自己的特征行为”做作业”,
所以,当调用”人”去”做事”的时候,如果这个人是教师,那他所做的事实际上就是”教书”,
如果这个人是学生,那他所做的事实际上就是”做作业”.
也就是说在这里”人”是多态的, 在不同的形态时,特征行为是不一样的, 这里的”人”, 同时有两种形态,一种是教师形态,一种是学生形态,所对应的特征行为分别是”授课”与”做作业”.
完成上述的描述过程, 其实就是多态机制的体现.
多态, 就是站在抽象的层面上去实施一个统一的行为,到个体(具体)的层面上时, 这个统一的行为会因为个体(具体)的形态特征而实施自己的特征行为.
多态比起封装与继承来说要复杂很多, 上面的描述很简单, 不用去死抠多态两个字,其实只要明白: 能站在抽象的角度去描述一件事, 而针对这件抽象的事, 对于每个个体(具体)又能找到其自身的行为去执行, 这就是多态.
十七、Override和Overload的含义去区别。
override是方法的重写,通常发生在子类与父类之中,指的是子类中定义了一个与父类返回值类型,参数类型完全相同的方法
overload是方法的重载,通常在同一个类中,定义了一堆方法名相同,但返回值可能不同,参数也可能不同的方法
对于重载而言:
1、参数类型、个数、顺序至少有一个不相同。
2、不能重载只有返回值不同的方法名。
3、存在于父类和子类、同类中。
方法的重写(Overriding)和重载(Overloading)是Java多态性的不同表现。
重写(Overriding)是父类与子类之间多态性的一种表现,而重载(Overloading)是一个类中多态性的一种表现。
十八、Interface与abstract类的区别
abstract类不能创建的实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。
abstract class类中定义抽象方法必须在具体(Concrete)子类中实现
如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为
public static final。
下面比较一下两者的语法区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4.抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5.抽象类中可以包含静态方法,接口中不能包含静态方法
6.抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为一个类可以实现多个接口,但只能继承一个抽象类。
十九、Static class 与non static class的区别。
在java中我们可以有静态实例变量、静态方法、静态块。类也可以是静态的。
java允许我们在一个类里面定义静态类。比如内部类(nested class)。把nested class封闭起来的类叫外部类。在java中,我们不能用static修饰*类(top level class)。只有内部类可以为static。
静态内部类和非静态内部类之间到底有什么不同呢?下面是两者间主要的不同。
(1)内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
(2)非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
(3)一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
根据Oracle官方的说法:
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
从字面上看,一个被称为静态嵌套类,一个被称为内部类。
从字面的角度解释是这样的:
什么是嵌套?嵌套就是我跟你没关系,自己可以完全独立存在,但是我就想借你的壳用一下,来隐藏一下我自己(真TM猥琐)。
什么是内部?内部就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
简单理解就是:如果把类比喻成鸡蛋,内部类为蛋黄,外部类是蛋壳。那么静态类相当于熟鸡蛋,就算蛋壳破碎(外部类没有实例化),蛋黄依然完好(内部类可以实例化);而非静态类相当于生鸡蛋,蛋壳破碎(无实例化),蛋黄也会跟着xx(不能实例化)。
二十、 java多态的实现原理。
多态的概念:同一操作作用于不同对象,可以有不同的解释,有不同的执行结果,这就是多态,简单来说就是:父类的引用指向子类对象。下面先看一段代码
package polymorphism;
class Dance {
public void play(){
System.out.println("Dance playing");
}
}
class Latin extends Dance {
public void play(){
System.out.println("Latin playing");
}
}
class Tango extends Dance {
public void play(){
System.out.println("Tango playing");
}
}
public class Test {
public void perform(Dance dance){
dance.play();
}
public static void main(String[] args){
new Test().perform(new Latin()); // 向上类型转换
}
}
执行结果:Latin playing。这个时候你可能会发现perform()方法里面并没有参数类型的判断,其实这就是多态的特性,它消除了类型之间的耦合关系,令我们可以把一个对象不当做它所属的特定类型来对待,而是当做其基类的类型来对待。因为上面的Test代码完全可以这么写:
public class Test {
public void perform(Latin dance){
dance.play();
}
public void perform(Tango dance){
dance.play();
}
public static void main(String[] args){
new Test().perform(new Latin());
}
}
但是这样你会发现,如果增加更多新的类似perform()或者自Dance导出的新类,就会增加大量的工作,而通过比较就会知道第一处代码会占优势,这正是多态的优点所在,它改善了代码的组织结构和可读性,同时保证了可扩展性。
二十一、实现多线程的两种方法:Thread与Runable
Java中有两种实现多线程的方式。一是直接继承Thread类,二是实现Runnable接口。那么这两种实现多线程的方式在应用上有什么区别呢?
先看第一种:
class MyThread extends Thread{
private int ticket = 100;
public void run(){
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket" + ticket--);
}else{
break;
}
}
}
}
public class ThreadDemo1{
public static void main(String[] args){
new MyThread().start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
这下达到目的了吗?
没有达到,每个线程打印100张票,而不去卖共同的100张票。这种情况是怎么造成的呢?我们需要的是,多个线程去处理同一个资源,一个资源只能对应一个对象,在上面的程序中,我们创建了四个MyThread对象,就等于创建了四个资源,每个资源都有100张票,每个线程都在独自处理各自的资源。
第二种情况:
经过这些实验和分析,可以总结出,要实现这个铁路售票程序,我们只能创建一个资源对象,但要创建多个线程去处理同一个资源对象,并且每个线程上所运行的是相同的程序代码。
class MyThread implements Runnable{
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName() +
" is saling ticket " + tickets--);
}
}
}
}
public class ThreadDemo1{
public static void main(String[] args){
MyThread t = new MyThread ();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
上面的程序中,创建了四个线程,每个线程调用的是同一个MyThread对象中的run()方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的需求。在Windows上可以启动多个记事本程序一样,也就是多个进程使用同一个记事本程序代码。
可见,实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
二十二、线程同步的方法:sychronized、lock、reentrantLock等。
如果你向一个变量写值,而这个变量接下来可能会被另一个线程所读取,或者你从一个变量读值,而它的值可能是前面由另一个线程写入的,此时你就必须使用同步。
一、什么是sychronized
sychronized是Java中最基本同步互斥的手段,可以修饰代码块,方法,类.
在修饰代码块的时候需要一个reference对象(默认可以使用this,也可以创建一个Object对象作为锁)作为锁的对象.
public class MyThread implements Runnable{
int ticket = 100;
public void run() {
while(true){
synchronized (this) {//使用当前对象作为锁对象
if(ticket>0){
System.out.println(Thread.currentThread()+", 售出第 "+ticket+"票");
ticket--;
}
}
}
}
}
/////////////////////////////////////////////////////////
public class MyThread implements Runnable{
int ticket = 100;
Object obj = new Object();
public void run() {
while(true){
synchronized (obj) {//自己创建实例作为所对象
if(ticket>0){
System.out.println(Thread.currentThread()+", 售出第 "+ticket+"票");
ticket--;
}
}
}
}
}
在修饰方法的时候默认是当前对象作为锁的对象.
在修饰类时候默认是当前类的Class对象作为锁的对象.
synchronized会在进入同步块的前后分别形成monitorenter和monitorexit字节码指令.在执行monitorenter指令时会尝试获取对象的锁,如果此没对象没有被锁,或者此对象已经被当前线程锁住,那么锁的计数器加一,每当monitorexit被锁的对象的计数器减一.直到为0就释放该对象的锁.由此synchronized是可重入的,不会出现自己把自己锁死.
二、什么ReentrantLock
以对象的方式来操作对象锁.相对于sychronized需要在finally中去释放锁
class RunIt3 implements Runnable{
private static Lock lock = new ReentrantLock();
public void run(){
try{
lock.lock();
System.out.println(Thread.currentThread().getName() + " running");
Thread.sleep(20);
System.out.println(Thread.currentThread().getName() + " finished");
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName() + " interrupted");
}finally{
lock.unlock();
}
}
}
三、synchronized和ReentrantLock的区别
除了synchronized的功能,多了三个高级功能.
等待可中断,公平锁,绑定多个Condition.
1.等待可中断
在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待. tryLock(long timeout, TimeUnit unit)
2.公平锁
按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁. new RenentrantLock(boolean fair)
3.绑定多个Condition
通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal();
分析理解:
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定几种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
二十三、 锁的等级:方法锁、对象锁、类锁。
对象锁(方法锁),是针对一个对象的,它只在该对象的某个内存位置声明一个标识该对象是否拥有锁,所有它只会锁住当前的对象,一般一个对象锁是对一个非静态成员变量进行synchronized修饰,或者对一个非静态成员方法进行synchronized进行修饰,对于对象锁,不同对象访问同一个被synchronized修饰的方法的时候不会阻塞
类锁是锁住整个类,当有多个线程来声明这个类的对象时候将会被阻塞,直到拥有这个类锁的对象呗销毁或者主动释放了类锁,这个时候在被阻塞的线程被挑选出一个占有该类锁,声明该类的对象。其他线程继续被阻塞住。
无论是类锁还是对象锁,父类和子类之间是否阻塞没有直接关系。当对一个父类加了类锁,子类是不会受到影响的,相反也是如此。因为synchronized关键字并不是方法签名的一部分,它是对方法进行修饰的。当子类覆写父类中的同步方法或是接口中声明的同步方法的时候,synchronized修饰符是不会被自动继承的,所以相应的阻塞问题不会出现。但是,当一个子类没有覆盖父类的方法的时候,这时候通过子类访问方法则会产生阻塞。
插入一句:构造方法不可能是真正同步的(尽管可以在构造方法中使用同步块)。
当同一个对象在线程1中访问一个方法,在线程2中再去访问另外一个加锁方法,则同样也会被阻塞.
对于类锁,则会把整个类锁住,也就说只能有一个对象拥有当前类的锁。当一个对象拥有了类锁之后,另外一个对象还想竞争锁的话则会被阻塞。两个对象A,B,如果A正在访问一个被类锁修饰的方法function,那么B则不能访问。因为类锁只能在同一时刻被一个对象拥有。相对于对象锁,则是不同。还是A,B两个对象,如果A正在访问对象锁修饰的function,那么这个时候B也可以同时访问。
对于对象锁,当一个对象拥有锁之后,访问一个加了对象锁的方法,而该方法中又调用了该类中其他加了对象锁的方法,那么这个时候是不会阻塞住的。这是java通过可重入锁机制实现的。可重入锁指的是当一个对象拥有对象锁之后,可以重复获取该锁。因为synchronized块是可重入的,所以当你访问一个对象锁的方法的时候,在该方法中继续访问其他对象锁方法是不会被阻塞的。
二十四、 死锁。
/**
* 一个简单的死锁类
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒
* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被td2锁定;
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被td1锁定;
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执行,从而死锁。
*/
public class DeadLock implements Runnable {
public int flag = 1;
//静态对象是类的所有对象共享的
private static Object o1 = new Object(), o2 = new Object();
@Override
public void run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
//td2的run()可能在td1的run()之前运行
new Thread(td1).start();
new Thread(td2).start();
}
}
二十五、 写出生产者消费者模式
public class ProducerConsumer {
public static void main(String[] args) {
//建立一个存储区
ProductStack ss = new ProductStack();
//添加生产者
Producer p1 = new Producer(ss);
p1.setName("NO.1P");
Producer p2 = new Producer(ss);
p2.setName("NO.2P");
Producer p3 = new Producer(ss);
p3.setName("NO.3P");
Producer p4 = new Producer(ss);
p4.setName("NO.4P");
Producer p5 = new Producer(ss);
p5.setName("NO.5P");
//添加消费者
Consumer c1 = new Consumer(ss);
c1.setName("NO.1C");
Consumer c2 = new Consumer(ss);
c2.setName("NO.3C");
Consumer c3 = new Consumer(ss);
c3.setName("NO.3C");
Consumer c4 = new Consumer(ss);
c4.setName("NO.4C");
Consumer c5 = new Consumer(ss);
c5.setName("NO.5C");
//打开存储区
ss.openStack();
//各角色开始进行生产或消费活动
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
//活动100ms后停止
try {
Thread.sleep(100);
ss.closeStack();
} catch (InterruptedException e) {
e.printStackTrace();
}
//1秒后重新开始活动
try {
Thread.sleep(1000);
ss.openStack();
} catch (InterruptedException e) {
e.printStackTrace();
}
p4.start();
p5.start();
c3.start();
c4.start();
c5.start();
//活动100ms后再次停止
try {
Thread.sleep(100);
ss.closeStack();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
生产者、消费者以及产品对象
//生产者
class Producer extends Thread{
ProductStack ss;
public Producer(ProductStack ss) {
this.ss = ss;
}
/**
* 生产产品
*/
public void run() {
while (ss.isStackOpen()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product pt = new Product();
System.out.println(Thread.currentThread().getName() + "生产了:第"
+ pt.getId() + "号");
ss.push(pt);
}
System.out.println(Thread.currentThread().getName() +"已停止生产!");
}
}
//消费者
class Consumer extends Thread{
ProductStack ss;
public Consumer(ProductStack ss) {
this.ss = ss;
}
/**
* 消费产品
*/
public void run() {
while (ss.isStackOpen()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Product pt = ss.pop();
if(pt != null){
System.out.println("----------" + Thread.currentThread().getName() + "消费了:第"
+ pt.getId() + "号");;
}
}
System.out.println(Thread.currentThread().getName() + "已停止消费");
}
}
//产品
class Product {
//已生产的产品总数
private static Integer totalProduct = 0;
//产品id(生产日期+生产总数)
private String id = null;
public Product() {
this.id = generateId();
}
private String generateId(){
//类锁 由于所有线程的Product对象不同,故只能用类锁使任何使用该类对象的线程在此处进行同步
synchronized(Product.class){
++totalProduct;
return String.valueOf(totalProduct);
}
}
public String getId() {
return id;
}
}
产品存储区 所有生产者和消费者共享
class ProductStack {
//标志存储区是否打开
private boolean StackOpen = false;
//存储区能容纳的最大产品数
private int capacity = 10;
//当前的产品数
private int Current = 0;
//存放产品的容器
private Product[] ProductArray = new Product[capacity];
//存储区关闭后使用的外部(备用)存储区
private Stack<Product> externalStack = new Stack<Product>();
/**
* 默认构造方法
*/
public ProductStack(){
}
/**
* 构造方法
* @param capacity 存储区容量
*/
public ProductStack(int capacity){
ProductArray = new Product[capacity];
}
/**
* 存储产品
* @param pt 传入生产出的产品
*/
/*
* 对象锁,相当于方法中加synchronized(this){方法体}
* 所有继承object的类都有一个锁标记(lock flag),当多个线程对同一个对象进行访问时,
* 如果遇到同步代码块,会先检查锁标记是否已经打开:如果已打开,线程就被放到锁池中等待,
* 等其他同步代码块释放了锁标记后才继续执行;如果未打开,则为对象添加一个锁标记,然后再执行。
*/
public synchronized void push(Product pt) {
while (StackOpen && Current == ProductArray.length) {
try {
/*
* 线程释放锁标记,被放入等待池, 当同一对象中的其他同步代码块调用
* notify/notifyAll时,线程被放到锁池中,等其他同步代码块释放锁标记后执行。
*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!StackOpen){
externalStack.push(pt);
System.out.println("由于存储区已关闭 ,第" + pt.getId() + "号导出至外部存储区!");
return;
}
ProductArray[Current] = pt;
String threadName = Thread.currentThread().getName();
if(threadName.equals("externalStackImportThread")){
System.out.println("第" + pt.getId() + "号已从外部存储区导入!");
}else{
System.out.println(threadName + "生产的:第" + pt.getId() + "号已入库!");
}
++Current;
/* 释放本对象等待池中的所有线程,进入锁池,等push释放锁标记后,共同竞争以进入running状态
* 此时,存储区至少有一个产品,所以通知在pop中等待的线程,等push结束后,可以相互竞争以继续执行
*/
this.notifyAll();
}
/**
* 取出产品
* @return 返回从库中取出的产品
*/
public synchronized Product pop() {
while (StackOpen && Current == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!StackOpen){
return null;
}
--Current;
//此时,存储区至少还有一个产品可存储,所以通知在push中等待的线程,等pop结束后,可以相互竞争以继续执行
this.notifyAll();
Product pt = ProductArray[Current];
System.out.println("----------" + Thread.currentThread().getName() + "消费的:第" + pt.getId() + "号已出库!");
return pt;
}
/**
* 打开存储区
*/
public void openStack(){
System.out.println("-----------------------存储区已打开!现有产品数: " + getCurrent() + "-----------------------");
StackOpen = true;
//导入外部存储区中的产品
if(!externalStack.isEmpty()){
ImportExternalStack();
}
}
/**
* 导入外部存储区
*/
private void ImportExternalStack(){
//使用Runnable匿名类建立一个导入线程
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
while(!externalStack.isEmpty()){
Product pt = externalStack.pop();
push(pt);
}
}
});
thread.setName("externalStackImportThread");
thread.start();
}
/**
* 关闭存储区
*/
public synchronized void closeStack(){
StackOpen = false;
//通知所有正在等待的线程“查看”当前库状态
this.notifyAll();
System.out.println("-----------------------存储区已关闭!现有产品数: " + getCurrent() + "-----------------------");
}
/**
* 查询存储区是否打开
* @return
*/
public boolean isStackOpen() {
return StackOpen;
}
/**
* 查询存储区产品的最大存储数量
* @return
*/
public int getMaxProduct() {
return capacity;
}
/**
* 获得当前产品数量
* @return
*/
public int getCurrent() {
return Current;
}
/**
* 获得外部存储区产品数量
* @return
*/
public int getExternalStackCount() {
return externalStack.size();
}
}
二十六、 ThreadLocal的设计理念与作用
Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。
不使用ThreadLocal 代码运行结果是这样:
产生随机数: 小花82.56994727574384
产生随机数: 小花82.56994727574384
public class ThreadLocalDemo {
public static void main(String[] args){
ThreadLocalOne threadLocalOne = new ThreadLocalOne();
Thread threadOne = new Thread(threadLocalOne);
Thread threadTwo = new Thread(threadLocalOne);
threadOne.start();
threadTwo.start();
}
public static class ThreadLocalOne implements Runnable{
Num num = new ThreadLocalDemo().new Num();
@Override
public void run() {
num.setName("小花"+Math.random()*100);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("产生随机数: "+num.getName());
}
}
public class Num {
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
使用ThreadLocal运行结果如下:
ThreadLocal产生随机数是: 小花25.043061264521583
ThreadLocal产生随机数是: 小花27.299417751892697
运行结果是不同的
public class ThreadLocalDemo {
public static void main(String[] args){
ThreadLocalOne threadLocalOne = new ThreadLocalOne();
Thread threadOne = new Thread(threadLocalOne);
Thread threadTwo = new Thread(threadLocalOne);
threadOne.start();
threadTwo.start();
}
public static class ThreadLocalOne implements Runnable{
ThreadLocal<String> threadLocal = new ThreadLocal<>();
// Num num = new ThreadLocalDemo().new Num();
@Override
public void run() {
threadLocal.set("小花"+Math.random()*100);
// num.setName("小花"+Math.random()*100);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("产生随机数: "+num.getName());
System.out.println("ThreadLocal产生随机数是: "+threadLocal.get());
}
}
public class Num {
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
二十七、ThreadPool用法与优势
在java.util.concurrent包下,提供了一系列与线程池相关的类。合理的使用线程池,可以带来多个好处:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池可以应对突然大爆发量的访问,通过有限个固定线程为大量的操作服务,减少创建和销毁线程所需的时间。
ThreadPool的创建,我们一般通过Executors类下的四个成员函数创建相应的线程池:
//创建一个定时任务的线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
//创建单线程的线程池
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
//创建缓存线程池(重用先前的线程)
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//创建一个带有固定线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(4);
下面再介绍下ThreadPoolExecutor函数,以便对线程池有进一步认识:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime: 线程池维护线程所允许的空闲时间
unit: 线程池维护线程所允许的空闲时间的单位
workQueue: 线程池所使用的缓冲队列
threadFactory:threadFactory the factory to use when the executor creates a new thread
handler: 线程池对拒绝任务的处理策略
当一个任务通过execute(Runnable)方法欲添加到线程池时:
1、如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2、如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
4、如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。
也就是说,处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
简单案例如下代码:
public class FixedThreadPoolTest {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
fixedThreadPool.execute(new Runnable() {
public void run() {
System.out.println("当前线程是: "+Thread.currentThread().getId());
}
});
}
}
}
执行结果是:
当前线程是: 11
当前线程是: 12
当前线程是: 13
当前线程是: 11
当前线程是: 12
当前线程是: 13
当前线程是: 11
当前线程是: 12
当前线程是: 13
当前线程是: 11
二十八、Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等
CountDownLatch: 允许线程集等待直到计数器为0。适用场景: 当一个或多个线程需要等待指定数目的事件发生后再继续执行。
ArrayBlockingQueue: 一个基于数组实现的阻塞队列,它在构造时需要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。通过阻塞队列,我们可以按以下模式来工作:工作者线程可以周期性的将中间结果放入阻塞队列中,其它线程可以取出中间结果并进行进一步操作。若工作者线程的执行比较慢(还没来得及向队列中插入元素),其他从队列中取元素的线程会等待它(试图从空队列中取元素从而阻塞);若工作者线程执行较快(试图向满队列中插入元素),则它会等待其它线程取出元素再继续执行
从代码来看会发现使用ArrayBlockingQueue生产者消费者模式代码简化了很多,一些代码包括判空,线程挂起等操作都封装在ArrayBlockingQueue中。这样具体的应用者的代码会少很多。生产者只需要关心生产,消费者只需要关心消费。从这里可以看出ArrayBlockingQueue是一种比较好的实现方式,高度的内聚,和我的实现有着天壤之别。
代码如下:
/**
* 消费者
*/
public class Consumer implements Runnable{
//容器
private final ArrayBlockingQueue<Bread> queue;
public Consumer(ArrayBlockingQueue<Bread> queue){
this.queue = queue;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
consume();
}
}
public void consume(){
/**
* take()方法和put()方法是对应的,从中拿一个数据,如果拿不到线程挂起
* poll()方法和offer()方法是对应的,从中拿一个数据,如果没有直接返回null
*/
try {
Bread bread = queue.take();
System.out.println("consumer:"+bread);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//////////////////////////////////////////////////////////
/**
* 生产者
*/
public class Producer implements Runnable{
//容器
private final ArrayBlockingQueue<Bread> queue;
public Producer(ArrayBlockingQueue<Bread> queue){
this.queue = queue;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
produce();
}
}
public void produce(){
/**
* put()方法是如果容器满了的话就会把当前线程挂起
* offer()方法是容器如果满的话就会返回false,也正是我在前一篇中实现的那种策略。
*/
try {
Bread bread = new Bread();
bread.setName("玉米面包 " +(int)(Math.random()*100));
queue.put(bread);
System.out.println("Producer:"+bread);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//////////////////////////////////////////////////
public class Bread {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Bread [name=" + name + "]";
}
}
///////////////////////////////////////////////////
public class Client {
public static void main(String[] args) {
int capacity = 10;
ArrayBlockingQueue<Bread> queue = new ArrayBlockingQueue<Bread>(capacity);
new Thread(new Producer(queue)).start();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}
二十九、 wait()和sleep()的区别
wait(): Object类中定义的实例方法。在指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。
sleep(): Thread类中的静态方法,作用是让当前线程进入休眠状态,以便让其他线程有机会执行。进入休眠状态的线程不会释放它所持有的锁。
三十、foreach与正常for循环效率对比
for-each能够让代码更加清晰,并且减少了出错的机会。下面的惯用代码适用于集合与数组类型:
for (Element e : elements) {
doSomething(e);
}
使用for-each循环与常规的for循环相比,并不存在性能损失,即使对数组进行迭代也是如此。实际上,在有些场合下它还能带来微小的性能提升,因为它只计算一次数组索引的上限。