本文共 28671 字,大约阅读时间需要 95 分钟。
JDK和JRE有什么区别:JDK(Java Development Kit)提供了Java开发环境(Javac、调试分析工具)和Java运行环境(JRE)
==对于基本类型来说是值比较,对于引用类型来说是引用比较;而equals默认情况下是引用比较,只是很多类重写了equals方法,比如String、Integer等把它变成了值比较,所以一般情况下equals比较的是值是否相等
String x = "string";String y = "string";String m = "str" + "ing";String n = new String("str") + "ing";String z = new String("string");System.out.println(x == y); //trueSystem.out.println(x == m); //trueSystem.out.println(x == n); //falseSystem.out.println(x == z); //falseSystem.out.println(x.equals(y)); //trueSystem.out.println(x.equals(z)); //true
两个对象的hashCode()相同,equals()不一定为true:哈希表存在哈希碰撞,不同的对象哈希值也可能是相同的
final修饰的类叫最终类,该类不能被继承;final修饰的方法不能被重写;final修饰的变量叫常量,常量必须初始化,初始化之后的值就不能被修改
Math.round(-1.5)等于-1,因为四舍五入的原理是在参数上加0.5然后进行向下取整
String不属于基础类型,基础类型有8种:byte(1)、boolean、char(2)、short(2)、int(4)、float(4)、long(8)、double(8)
String str = "i"与String str = new String("i")一样吗?不一样,因为分配内存的方式不一样,前者会将其分配到常量池中,而后者会被分配到堆内存中
String类常用方法:
String#valueOf | 其他类型转String,可以传好多参数类型 |
String#toCharArray | String类型转char数组 |
String#indexOf | 返回指定字符(串)的索引 |
String#charAt | 返回指定索引处的字符 |
String#replace、String#replaceAll | 字符串替换 |
String#split | 分割字符串(参数是正则表达式) |
String#getBytes | 返回字符串的byte类型数组 |
String#length | 返回字符串的长度 |
String#toLowerCase、String#toUpperCase | 大小写转换 |
String#subString | 截取字符串 |
String#equals | 字符串比较 |
抽象类不一定非要有抽象方法
普通类和抽象类的区别:普通类不能包含抽象方法,抽象类可以包含抽象方法;抽象类不能直接实例化,普通类可以直接实例化
抽象类不能使用final修饰:定义抽象类就是让其他类继承的,如果定义为final该类就不能被继承,编译器也会提示错误信息
抽象类和接口的区别:子类通过extends来继承抽象类,通过implements来实现接口;子类可以实现多个接口,但只能继承一个抽象类;抽象类可以有内部方法实现;接口方法默认都是public,成员变量默认是static final的;
Java中IO流分为几种?按功能来分:输入流、输出流;按类型来分:字节流(InputStream、OutputStream)、字符流(Reader、Writer)
是:字节流按8位传输以字节为单位输入输出数据,字符流按16位传输以字符为单位输入输出数据
public static void main(String[] args) throws IOException { File f = new File("d:" + File.separator+"test.txt"); OutputStream out=new FileOutputStream(f); String str="Hello World"; byte[] b=str.getBytes(); out.write(b); out.close(); File f = new File("d:" + File.separator+"test.txt"); InputStream in=new FileInputStream(f); byte[] b=new byte[1024]; int len=in.read(b); in.close(); File f = new File("d:" + File.separator+"test.txt"); Writer out=new FileWriter(f,true); String str="\r\nHello World"; out.write(str); out.close(); File f = new File("d:" + File.separator+"test.txt"); Reader input=new FileReader(f); char[] c=new char[1024]; int len=input.read(c); input.close();}
?
BIO:同步阻塞式IO,模式简单实用方便,并发处理能力低(Socket、ServerSocket)
ServerSocket server = new ServerSocket(port);//通过无线循环监听客户端连接,如果没有客户端接入,将阻塞在accept操作上while(true){ Socket socket = server.accept(); //当有新的客户端接入时,会创建一个新的线程处理这条Socket链路 new Thread(new ServerHandler(socket)).start();}
NIO:同步非阻塞IO,客户端和服务器端通过Channel通讯,套接字通过Selector进行管理,实现了多路复用(Selector、ServerSocketChannel、SocketChannel)
//无论是否有读写事件发生,selector每隔1s被唤醒一次selector.select(1000);//阻塞,只有当至少一个注册的事件发生的时候才会继续.//selector.select();Setkeys = selector.selectedKeys();
AIO:真正实现了异步非阻塞IO,主要基于事件和回调机制(AsynchronousServerSocketChannel、CompletionHandler)
可以支持文件的随机访问,程序快可以直接跳转到文件的任意地方来读写数据。RandomAccessFile的一个重要使用场景就是网络请求中的多线程下载及断点续传(一个文件分成N份)
public void download() throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Connection", "Keep-Alive"); // 得到文件大小 fileSize = conn.getContentLength(); conn.disconnect(); int currentPartSize = fileSize / threadNum + 1;//这里不必一定要加1,不加1也可以 RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); // 设置本地文件的大小 file.setLength(fileSize); file.close(); for (int i = 0; i < threadNum; i++) { // 计算每条线程的下载的开始位置 int startPos = i * currentPartSize; // 每个线程使用一个RandomAccessFile进行下载 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); // 定位该线程的下载位置 currentPart.seek(startPos); // 创建下载线程 threads[i] = new DownThread(startPos, currentPartSize, currentPart); // 启动下载线程 threads[i].start(); } }
HashMap、LinkedHashMap、TreeMap、ConcurrentHashMap、HashTable、WeakHashMap有啥区别?
HashMap是用哈希表实现的,内部是无序的,键值对可以为空,线程不安全;
TreeMap是用红黑树实现的,会根据Key值进行排序,Key不能为空;
LinkedHashMap是用双向链表实现的,Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,可以保证插入顺序,键值对可以为空;
ConcurrentHashMap底层采用分段数组+链表实现,线程安全,通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍
HashTable是线程安全的,键值对都不允许为空,保留类,基本不用;
WeakHashMap的键值是通过弱引用+弱引用队列实现的,可以自动回收键值对
Collection和Collections的区别:
Collection是一个集合接口,提供了集合类的基本方法;Collections是一个工具类,包含了很多静态方法,比如:Collections.sort
HashMap原理:根据key.hashCode计算哈希值,根据哈希值将value保存在哈希桶里,当哈希冲突比较少的情况,采用链表存储,哈希冲突比较多时,采用红黑树存储
HashSet底层基于HashMap实现,value可以为空
ArrayList和LinkedList:ArrayList采用动态数组来实现,且仅实现了List功能;LinkedList采用双向链表来实现,且实现了List、Deque、Queue功能
数组转List:使用Arrays.asList(array)
List转数组:使用List自带的toArray()方法
ArrayList和Vector区别:Vertor使用了Synchronized来实现线程同步,是线程安全的,而ArrayList是非线程安全的,ArrayList和Vector都可以动态扩容,只不过Vector每次增加一倍,ArrayList只会增加一半
Array和ArrayList区别:Array可以存储基本类型和对象,ArrayList只能存储对象,Array是固定大小的,ArrayList可以动态扩容,Array内置方法没有ArrayList多
在Queue中poll和remove的区别:如果没有元素poll会返回null,而remove会抛出异常
Vector、Hashtable、Stack、ConcurrentHashMap都是线程安全的,而像HashMap则是非线程安全的
Iterator:在遍历的集合时,内部也是通过iterator实现的,这时如果没有通过iterator更改集合,就会抛出ConcurrentModificationException异常
HashSethashSet = new HashSet<>();hashSet.add("1");hashSet.add("2");hashSet.add("3");//rightIterator iterator = hashSet.iterator();while(iterator.hasNext()) { iterator.next(); iterator.remove();}//wrongfor (String s : hashSet) { hashSet.remove(s);}
Iterator和ListIterator区别:Iterator可以遍历Set和List,而ListIterator只能遍历List;Iterator只能单向遍历,而ListIterator可以双向遍历;ListIterator从Iterator接口继承,然后添加了一些额外的功能
可以通过Collections.unmodifiableCollection(Collection c)方法来创建一个只读集合,任何改变集合的操作都会抛出异常
创建线程的三种方法:继承Thread重写run方法、实现Runnable接口、实现Callable接口
Runnable没有返回值,Callable可以拿到返回值
线程状态:
sleep、wait、yield的区别:sleep和yield来自Thread,wait来自Object;sleep和yield不释放锁,wait释放锁,必须在同步控制块中调用;sleep和wait能指定时间,yield可以给同优先级或高优先级的线程让出CPU占有权,但让出的时间是不可设定的
Thread.join方法:当调用原生的join方法时,也是调用了wait方法,也就是会阻塞调用的线程(isAlive是线程对象控制的状态,而wait和线程对象无关,总是拿到当前运行的线程)
public final void join(long millis) throws InterruptedException { synchronized(lock) { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { lock.wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } lock.wait(delay); now = System.currentTimeMillis() - base; } } } }
notify和notifyAll区别:notify只会唤醒一个线程,notifyAll会唤醒所有线程,所有线程由等待池进入锁池,参与竞争
Java同步机制:同步方法(synchronized)、同步代码块、Lock锁
创建线程池的几种方法:newSingleThreadExecutor(一个核心线程)、newCachedThreadPool(没有核心线程、Integer.MAX个工作线程)、newFixedThreadPool(固定数量的核心线程)、newScheduledThreadPool(支持周期执行任务,通过封装Runnable实现)、ThreadPoolExecutor(底层实现)
public class Executors { public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue ())); } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue ()); } public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }}
线程池的真正实现是ThreadPoolExecutor,其中核心概念有核心线程数、最大线程数、超时时间等,SingleThreadExecutor只有一个核心线程,FixedThread只有固定的核心线程,CacheThreadPool只有非核心线程,线程数量不固定,并且最大线程数为Integer.MAX_VALUE,ScheduleThreadPool的核心线程数是固定的,非核心线程数是没有限制的
private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); }};private static final BlockingQueuesPoolWorkQueue = new LinkedBlockingQueue (128);ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
线程池中submit和execute方法有什么区别:execute只能执行Runnable类型的任务,submit可以执行Runnable和Callable类型的任务
如何安全的停止线程:废弃stop方法,使用interrupt方法 + catch + isInterrupted()
在Java程序中保证线程安全的几种方法:使用安全类,比如Java.util.concurrent下的类、使用自动锁synchronized、使用手动锁Lock
如何防止死锁:尽量使用tryLock
synchronized底层实现:synchronized是由一对monitorenter/monitorexit指令实现的,monitor对象是同步的基本实现单元,在Java6之前,monitor的实现完全依靠操作系统内部的互斥锁,因为需要进行用户态和内核态的切换,所以同步操作是一个无差别的重量级操作,性能也很低。但是在Java6的时候,Java虚拟机对此进行了大刀阔斧的改革,提供了三种不同的monitor实现,也就是常说的三种不同的锁:偏向锁、轻量级锁和重量级锁,大大改进了性能
synchronized和volatile的区别:
可见性-synchronized
可见性-volatile:通过加入内存屏障和禁止重排序优化来实现
计数变量count前加入了volatile保证变量count的读和写能够及时的更新到内存中,但是运行出来的结果仍然是非线程安全的,原因是在cout++的操作,可以分为三步:1、取得count的值; 2、对count进行加1操作; 3、写count的值到主内存中; 这三步合起来就不是线程安全的,比如,两个线程可能同时取得count的值,然后,同时进行加1操作,并写回主存,这样就丢掉了一次加1的操作
如何实现克隆:实现Cloneable接口并重写Object类的clone方法;实现序列化接口
深拷贝和浅拷贝:浅拷贝只拷贝基本类型和引用值,但是引用的对象并没有复制
throw用来抛出异常,throws用来声明可能会抛出异常
finalize方法:Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作,它最主要的用途是回收特殊渠道申请的内存。由于finalize方法调用时机不确定,而且函数运行没有保障(执行一半被杀死),所以我们需要手动调用销毁逻辑
堆和栈的区别:堆是用来存放对象的,栈是用来存放对象引用和基本类型的;堆是线程共享的,栈是线程私有的;堆空间远远大于栈空间
成员变量存储在堆内存的对象中,所以也叫对象的特有数据。静态变量数据存储在方法区的静态区,所以也叫对象的共享数据。局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放
Java引用类型:
强引用:当内存空间不足,Java虚拟机会抛出OutOfMemoryError错误,使程序异常终止,也不会回收具有强引用的对象
软引用:只有内存不够时才回收,常用于缓存,当内存达到一个阀值,GC就会去回收它
弱引用:在执行垃圾回收的过程中,不管当前内存空间足够与否,都会回收只存在弱引用的对象
虚引用:"虚引用"顾名思义,就是形同虚设,与其他几种引用不同,虚引用不会决定对象的生命周期
public static Class forName(String className)throws ClassNotFoundException { return forName(className, true, VMStack.getCallingClassLoader());}//initialize if {@code true} the class will be initialized.public static Class forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException { //...}public Class loadClass(String name) throws ClassNotFoundException { return loadClass(name, false);}//resolve If {@code true} then resolve the classprotected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { //...}
forName默认会执行初始化的,loadClass默认只执行加载
try { Class.forName("com.lede.train.commondemo.StaticClass");} catch (ClassNotFoundException e) { e.printStackTrace();}public class StaticClass { static { Log.e("zzzz", "static"); }}
Java类机制:加载、验证、准备、解析、初始化、使用、卸载。加载阶段主要是通过类名获取二进制流、将二进制流转化为运行时方法区数据接口,在Java堆中生成Class文件,作为对方法区的访问入口;验证阶段主要验证类的正确性;准备阶段主要是为类的静态成员分配内存,并将其赋值为零值,静态变量存在于方法区中,其中,对于同时被static和final修饰的编译期常量(编译期常量不依赖类,不会引起类的初始化),必须显示为其赋值,且在准备阶段就会被初始化为指定值,对于只有final修饰的变量,准备阶段不会被赋予零值,但是在使用之前必须要赋值(变量声明时或者构造函数中);解析阶段主要是把符号引用转换成直接引用;初始化阶段主要是注意初始化顺序(父类静态成员和静态块、子类静态成员和静态块),Ps:构造函数初始化顺序(父类普通成员和非静态块、父类构造函数、子类普通成员和非静态块、子类构造函数)。初始化触发条件:创建类的实例、访问类的静态变量或静态方法、反射、初始化子类父类也会被初始化,虚拟机标明为启动类的类
class Parent { static { Log.e("offer", "" + "Parent"); } public static int value = 123;}class Child extends Parent { static { Log.e("offer", "" + "Child"); } public static int c_value = 123;}public static void main() { Log.e("offer", "" + Child.c_value); //05-10 03:41:08.957 4250-4250/com.lede.train.commondemo E/offer: Parent //05-10 03:41:08.957 4250-4250/com.lede.train.commondemo E/offer: Child //05-10 03:41:08.957 4250-4250/com.lede.train.commondemo E/offer: 123 Log.e("offer", "" + Child.value); //05-10 03:41:08.957 4250-4250/com.lede.train.commondemo E/offer: Parent //05-10 03:41:08.957 4250-4250/com.lede.train.commondemo E/offer: 123}class Parent{ public static int value = 1; static { value = 2; }}class Child extends Parent{ public static int B = value ;}public static void main() { Log.e("offer", "" + Child.B); //2}
Java类加载机制:当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载(findLoadedClass),如果已经加载则直接返回原来已经加载的类,如果当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载(loadClass),父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrap ClassLoader,当所有的父类加载器都没有加载的时候,再由当前的类加载器加载(重写findClass,findClass调用defineClass,define之后才能load),并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回,双亲委派机制优点:安全(不采用这种模式的话,就可以随时使用自定义的Class来动态替代java核心api中定义的Class类型)、避免重复加载。因此,如果想保证自定义的类加载器符合双亲委派机制,则选择覆写findClass方法,如果想打破双亲委派规则,则覆写loadClass方法。JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。tips:使用so的java类的classloader和so的classloader必须相同,否则会引起错误。ClassLoader.loadClass默认不会进行链接,也不支持重写;Class.forName有两个版本,默认版本会进行初始化,带参数的可以选择是否初始化
观察者模式:Observable(被观察者)、Observer(观察者)
Observable主要方法如下:addObserver、deleteObserver、setChanged、notifyObservers
Observer主要方法如下:update,该方法会在notifyObservers被调用时自动调用
观察者模式的优点:消除硬编码、降低耦合,如果没有Observer模式,则只能采用回调的模式,或者在代码中显示地调用观察者
String常量池:Java方法区中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于方法区的常量池中,则不会创建一个新的对象,而是引用已经存在的对象
当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的对象被保存在堆内存中
public void test(){ String a ="张三"; String b ="张"; String c ="三"; String d = b + c; //d是指向堆内存的 System.out.println(a == d); //false String e ="张"+"三"; //e是指向常量池的 System.out.println(a == e); //true}
Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”,都用于Collections.sort
priorityQueue = new PriorityQueue(k, new Comparator () { @Override public int compare(Integer i1, Integer i2) { return i2.compareTo(i1); }});
Java读取和写入文件时如果不指定字符集,那么都是采用操作系统默认的字符集,JAVA内部采用UTF-16编码格式
String encoding = System.getProperty("file.encoding");
String defaultCharsetName=Charset.defaultCharset().displayName();
assert <boolean表达式> : <错误信息表达式>,boolean表达式为否的情况会抛出assertError错误
Error指系统环境出现了错误,可以捕获但不要捕获,应该交给系统处理,Exception指程序运行中出现了异常,可以被捕获,Error和Exception都继承自Throwable,Exception还分为执行异常(空指针、找不到类)和检查异常两种
Java创建对象的5种方式:
使用new关键字 | 使用Class类的newInstance方法 | 使用Constructor的newInstance方法 | 使用clone方法 | 使用反序列化 |
try catch中遇到return方法后执行finally代码,finally代码中有return则提前返回
访问权限 | 本类 | 本包类 | 子类 | 非子类的外包类 |
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
default | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
String是不可改变的,StringBuffer是线程安全的,StringBuilder是非线程安全的
五种方法: “+”、String concat() 方法、String.join()方法、StringBuffer append() 方法、StringBuilder append() 方法,concat每次都会返回一个String,本质和加号一样,join适用于list类型的拼接,效率和StringBuffer、StringBuilder差不多
String.join底层也是调用的StringBuilder#append,适用于CharSequence集合
String[] tmpStr={abc,def,ghi};String join = String.Join(“-“, tmpStr); //join = ”abc-def-ghi”;
"+"会被编译器优化成StringBuilder#append,但是不适用于复杂逻辑
@Test public void test() { String str = ""; for (int i = 0; i < 10000; i++) { str += "asjdkla"; } } @Test public void test() { String str = null; for (int i = 0; i < 10000; i++) { str = new StringBuilder().append(str).append("asjdkla").toString(); } }
concat和StringBuilder#append原理相同,底层也是采用数组拷贝方式,但是由于StringBuilder的capability默认是16字节,对于少数字符串拼接会导致多次扩容和数组拷贝,效率低于concat
public StringBuilder() { super(16); } public StringBuilder(int capacity) { super(capacity); } public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } public native String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
length对应于数组,length()对应于字符串,size()对应于集合
单例模式:synchronized getInstance,这种模式效率较低、double check + volatile(1.5之前不可用)、懒汉式(占用内存、不能传递参数)、静态内部类(内部类需要是static的,因为非静态内部类不能声明静态变量)
public class Singleton { private Singleton(){ } private static class SingletonHolder{ private static Singleton instance=new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.instance; }}public class Singleton { private volatile static Singleton instance=null; private Singleton(){ } public static Singleton getInstance(){ if(instance==null){ synchronized(Singleton.class){ if(instance==null){ instance=new Singleton(); } } } return instance; }}public class Singleton { private static Singleton instance=null; private Singleton(){ } public static synchronized Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; }}public class Singleton { private static Singleton instance=new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return instance; }}
Thread.Interrupt()方法:对于阻塞状态的线程,调用Interrupt时会清除中断标记,并产生InterruptedException,我们可以捕获该异常来结束线程。对于运行状态的线程,调用Interrupt会设置中断标记,可以通过isInterrupted方法来判断线程状态
synchronized是jvm内置锁,使用方便,可以对类(静态方法)、对象(方法)、代码块进行加锁,而且不需要手动释放锁,但是灵活性相对较差,Lock锁可以调用tryLock进行加锁,而不会死等,ReadWriteLock效率最高,读锁可以同时访问,写锁是互斥的。如果多线程同时访问同一类的但是不同对象的类锁(synchronized 修饰的静态方法)以及对象锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是两种不同的锁,作用域也不同
执行Object.wait需要获取Object的锁,执行notify和notifyAll或者遇到IllegalMonitorStateException,InterruptedException异常,wait会首先获取锁,接着继续执行
Thead的sleep方法会抛出InterruptedException,Object的wait方法会抛出IllegalMonitorStateException,InterruptedException异常
@Overridepublic void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1;}public Looper getLooper() { if (!isAlive()) { return null; } synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper;}
sleep和wait区别:sleep方法没有释放锁,而wait方法释放了锁、这两个方法来自不同的类分别是Thread和Object、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
写出模式(synchronized、wait+notifyAll)
public class ProduceConsumer { private final int MAX_SIZE = 100; private final LinkedList
写出模式(BlockingQueue)
public class ProduceConsumer { private final int MAX_SIZE = 100; private final LinkedBlockingQueue
对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。然而,如果是在堆上创建对象,编译器就会对它的生命周期一无所知
Java虚拟机内存模型:程序计数器、虚拟机栈、本地方法栈、堆、方法区
线程私有的内存区域:
程序计数器:每个线程都有各自独立的程序计数器,如果线程正在执行的是一个Java方法, 那么这个计数器记录的是正在执行的虚拟机字节码指令地址,如果正在执行的是Native方法, 则程序计数器为空
虚拟机栈:虚拟机栈也是线程私有的,它描述的是Java方法执行的内存模型: 每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息, 每一个方法从调用直至完成的过程, 就对应着一个栈帧在虚拟机栈中入栈和出栈的过程,
局部变量表所需的内存空间在编译期间分配完成, 当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的, 在方法运行期间不会改变局部变量表的大小
本地方法栈:本地方法栈与虚拟机栈相似,不过服务于本地方法
线程共享的内存区域:
堆:垃圾回收的主战场
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等数据
new将对象存储在堆里,故用new创建一个小的、简单的变量,往往不是很有效;JAVA中所有数值类型都有正负号;Java消除了“向前引用”的问题
Java会确保类成员在准备阶段获得一个默认值,然而确保初始化的方法并不适用于局部变量,如果没有赋值,Java会在编译时返回一个错误(许多C++编译器会对未初始化变量给予警告,而Java则视为是错误)
final变量是可以反射修改的,但是需要注意的是,对于特定代码,虚拟机做了一定的优化
public class Test { private final String NAME = "亦袁非猿"; public String getName() { return NAME; }}//即使NAME变量被修改了,但是getName还是返回原来值public class Test { private final String NAME = "亦袁非猿"; public String getName() { return "亦袁非猿"; }}
通过@interface关键字进行定义
元注解:@Retention、@Documented、@Target、@Inherited、@Repeatable
@Retention取值如下:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME)public @interface TestAnnotation {}
@Documented:将注解中的元素包含到Javadoc中去
@Target取值如下:ElementType.ANNOTATION_TYPE(可以给一个注解进行注解)、ElementType.CONSTRUCTOR、ElementType.FIELD、ElementType.LOCAL_VARIABLE、ElementType.METHOD、ElementType.PACKAGE、ElementType.PARAMETER、ElementType.TYPE
@Inherited:标签可以继承(富豪标签)
@Repeatable:注解数组必须要有一个value的属性
@interface Persons { Person[] value();}@Repeatable(Persons.class)@interface Person{ String role default "";}@Person(role="artist")@Person(role="coder")@Person(role="PM")public class SuperMan{ }
注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface TestAnnotation { public int id() default -1; public String msg() default "Hi";}@TestAnnotation(id=3,msg="hello annotation")public class Test {}
如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内
public @interface Check { String value();}@Check("hi")int a;
还需要注意的一种情况是一个注解没有任何属性,那么在应用这个注解的时候,括号都可以省略
Java预置的注解:@Deprecated、@Override、@SuppressWarnings
注解通过反射获取:
boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class);TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class);Field a = Test.class.getDeclaredField("a");Check check = a.getAnnotation(Check.class);Method testMethod = Test.class.getDeclaredMethod("testMethod");Annotation[] ans = testMethod.getAnnotations();
反射的作用:获取class的所有成员变量和方法、绕过Java权限检测
Java内部类:内部类可以无条件访问外部类的所有成员属性和成员方法
成员内部类是最普通的内部类,成员内部类可以拥有private、protected、public以及包访问权限,可把它当做是外部类的一个成员变量来看。如果成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要通过:外部类.this.成员变量、外部类.this.成员方法
非静态内部类里面不能定义静态方法、静态成员变量静态初始化块:使用静态方法不用new对象,但是使用非静态内部类又必须要new一个对象,这就自相矛盾了。违背了静态成员的使用不依赖于对象的原则
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); new Inner().draw(); } private void draw() { Log.e("zzzz", "Outer private"); } private void Draw() { Log.e("zzzz", "Outer private"); } private class Inner { private void draw() { Log.e("zzzz", "Inner private private"); MainActivity.this.draw(); Draw(); } }}
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问权限仅限于方法内部
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); draw(); } private void outerMethod() { Log.e("zzzz", "Outer private"); } private void draw() { final class Inner { private void draw() { Log.e("zzzz", "Inner private private"); outerMethod(); } } new Inner().draw(); }}
匿名内部类有以下特点:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); outerMethod(new A() { @Override protected void innerMethod() { super.innerMethod(); outer(); } }); } private void outerMethod(A a) { a.innerMethod(); } private void outer() { Log.e("zzzz", "Outer private"); }}class A { protected void innerMethod() { Log.e("zzzz", "Inner private"); }}
静态内部类
为什么成员内部类可以无条件访问外部类的成员:
为什么局部内部类和匿名内部类只能访问局部final变量:局部内部类和匿名内部类的生命周期可能比外部的类要长,因此访问外部局部变量有可能是访问不到的。 那怎么办呢?Java语言为了实现这种特性, 只好将外部的局部变量偷偷的赋值了一份给匿名内部类。那这样匿名内部类就可以肆无忌惮的访问外部局部变量了。 问题又来了,这种通过赋值的形式有一个缺陷,匿名内部类不可以修改“原来的局部变量”,因为是一份“复制品”,修改复制品对原变量没什么影响啊。 那怎么办? Java语言干脆强制要求被匿名内部类访问的外部局部变量必须是final的,什么意思呢?就是“一刀切”,不让修改了
Java动态代理:关键类有(InvocationHandler、Proxy),Demo如下
//new Class[] {Car.class},如果是实例,需要RobotCar.class.getInterfaces()Car car = (Car)Proxy.newProxyInstance(Car.class.getClassLoader(), new Class[] {Car.class},new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.e("className", proxy.getClass().toString()); // $Proxy0 Log.e("method", method.getName()); // getCarName、getSpeed Log.e("args", "" + args.length); // if(args.length > 0) { Log.e("args_type", "" + args[0].getClass().toString()); // java.lang.Integer return 1; } return null; }});public interface Car { int getSpeed(int unit); String getCarName();}
Java多态实现原理:
Java的方法调用有两类,动态方法调用与静态方法调用。其中,静态方法和私有方法(invokestatic、invokespecial)是静态绑定的;非私有方法和接口方法(invokevirtual、invokeinterface)是动态绑定的
常量池中保存的是一个Java类的一些常量信息,包含字符串常量及对于类的符号引用信息等
方法表是动态调用的核心,也是实现动态调用的主要方式,它被存储于方法区中的类型信息,包含该类型定义的所有方法及指向这些方法代码的指针,注意这些具体的方法代码可能是被覆写的方法,也可能是继承自基类的方法
当某个方法被调用时(Invokevirtual #12),JVM首先通过查找常量池得到方法的符号引用,并查找调用类的方法表以确定该方法的直接引用(方法区的偏移量)
class Person { public String toString() { return "I'm a person."; } public void eat() { } public void speak() { }}class Boy extends Person { public String toString() { return "I'm a boy"; } public void speak() { } public void fight() { }}class Girl extends Person { public String toString() { return "I'm a girl"; } public void speak() { } public void sing() { }}class Party { void happyHour(){ Person girl = new Girl(); girl.speak(); }}
Invokevirtual #12 ,假设该调用代码对应girl.speak(),#12是Party类的常量池的索引。JVM执行该调用指令的过程如下所示:JVM首先查看Party的常量池索引为12的条目,得出要调用的方法是Person的speak方法,查看Person的方法表,得出speak方法在该方法表中的偏移量15(offset),这就是该方法调用的直接引用。当解析出方法调用的直接引用后(方法表偏移量15),JVM执行真正的方法调用,根据实例方法调用的参数this得到具体的对象(即girl所指向的位于堆中的对象),据此得到该对象对应的方法表(Girl的方法表),进而调用方法表中的某个偏移量所指向的方法(Girl的speak()方法的实现)
因为Java允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同样的方法在基类和派生类的方法表的位置就可能不一样了。JVM首先查看常量池,确定方法调用的符号引用(名称、返回值等等),然后利用this指向的实例得到该实例的方法表,进而搜索方法表来找到合适的方法地址。因为每次接口调用都要搜索方法表,所以从效率上来说,接口方法的调用总是慢于类方法的调用的
转载地址:http://pkbzk.baihongyu.com/