博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java基础
阅读量:762 次
发布时间:2019-03-23

本文共 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();Set
keys = 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异常

HashSet
hashSet = 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可以拿到返回值

线程状态:

  • NEW 尚未启动
  • RUNNABLE 正在执行中
  • BLOCKED 阻塞状态(互斥锁或者IO锁阻塞)
  • WAITTING 永久等待状态
  • TIMED_WAITTING 等待制定的时间
  • TERMINATED 执行完成

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 BlockingQueue
sPoolWorkQueue = 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的区别:

  • volatile是变量修饰符,synchronized是修饰类、方法、代码块
  • volatile仅能实现变量的修改可见行,禁止编译器优化(指令重排序),不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  • volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞

可见性-synchronized

  • 线程解锁前,必须把共享变量的最新值刷到主内存
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值

可见性-volatile:通过加入内存屏障和禁止重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
  • 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量

计数变量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 list = new LinkedList<>();    public void produce(String producer) {    synchronized (list) {      while (list.size() == MAX_SIZE) {        System.out.println("仓库已满,【" + producer + "】: 暂时不能执行生产任务!");        try {          list.wait();        } catch (Exception e) {          e.printStackTrace();        }      }             list.add(new Object());      System.out.println("【" + producer + "】:生产了一个产品\t【现仓储量为】:" + list.size());      list.notifyAll();    }  }    public void consume(String consumer) {    synchronized (list) {      while (list.size() == 0) {        System.out.println("仓库已空,【" + consumer + "】: 暂时不能执行消费任务!");        try {          list.wait();        } catch (Exception e) {          e.printStackTrace();        }      }      list.remove();      System.out.println("【" + consumer + "】:消费了一个产品\t【现仓储量为】:" + list.size());      list.notifyAll();    }  }}

写出模式(BlockingQueue)

public class ProduceConsumer {  private final int MAX_SIZE = 100;  private final LinkedBlockingQueue list = new LinkedBlockingQueue(100);  public void produce(String producer) {    if (list.size() == MAX_SIZE) {      System.out.println("仓库已满,【" + producer + "】: 暂时不能执行生产任务!");    }    try {      list.put(new Object());    } catch (Exception e) {      e.printStackTrace();    }    System.out.println("【" + producer + "】:生产了一个产品\t【现仓储量为】:" + list.size());  }    public void consume(String consumer) {    if (list.size() == 0) {      System.out.println("仓库已空,【" + consumer + "】: 暂时不能执行消费任务!");    }    try {      list.take();    } catch (Exception e) {      e.printStackTrace();    }    System.out.println("【" + consumer + "】:消费了一个产品\t【现仓储量为】:" + list.size());  }}

对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。然而,如果是在堆上创建对象,编译器就会对它的生命周期一无所知

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();  }}

匿名内部类有以下特点: 

  • 没有名字(因为没有名字,没有构造函数)
  • 只能被实例化一次
  • 通常被声明在方法或代码块的内部,以一个带有分号的带有花括号结尾
  • 匿名内部类也是不能有访问修饰符和static修饰符的
  • 匿名类的一个重要作用就是简化代码
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");  }}

静态内部类

  • 静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static
  • 静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似
  • 静态内部类不能够访问外部类的非静态成员

为什么成员内部类可以无条件访问外部类的成员:

  • 编译器自动为内部类添加一个成员变量, 这个成员变量就是指向外部类对象的引用
  • 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型
  • 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用
  • 编译器会为外部类智能生成access静态方法,用于访问私有方法

为什么局部内部类和匿名内部类只能访问局部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/

你可能感兴趣的文章