测试

重载,重写
答:重载:
• 编译时的多态性,
• 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载,重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
重写:
• 运行时的多态性。
• 重写发生在子类与父类之间,子类与父类具有相同的方法名、返回类型和参数列表
• 子类函数的访问修饰权限不能少于父类的,不能比父类被重写方法声明更多的异常(里氏代换原则:子类替代父类)。
• (1)子类中不能重写父类中的final方法
• (2)子类中必须重写父类中的abstract方法
静态方法、静态属性
• 静态方法和属性是属于类的,调用的时候直接通过类名.方法名
• 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能够被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态的方法可以被继承和重写,因此可以实现多态。
• 静态属性和方法可以被继承
• 静态属性,非静态属性,静态方法都不能被重写,不能实现多态
程序绑定
csdn
静态绑定:在程序执行前就已经绑定完成,可以简单理解成编译期的绑定
• java只有final、static、private、和构造方法是静态绑定的,这些方法都会被编译成invokestatic指令
• 所有类的初始化方法方法会被编译成invokespecial指令
• invokestatic的执行过程:
• jvm找到类的全限定名;
• jvm加载连接初始化类;
• 在类中找到静态方法的直接地址,并记录到常量池中,也就是常量池解析;
• jvm执行方法
动态绑定:在运行时根据对象的类型进行绑定,在程序运行的过程中,通过动态创建的对象的方法表来定位方法
• 方法表:jvm加载类的同时会在方法区中为这个类存储很多的信息:
• 全限定名、直接超类的全限定名、类型信息(类或接口)、访问修饰符
• 任何直接超接口的全限定名的有序列表
• 常量池(类型,方法或字段的符号,基本数据类型的数值)
• 字段信息(字段名,类型,修饰符)
• 方法信息(方法名、返回类型、参数、修饰符、字节码、操作数栈和栈帧中局部变量的大小、异常表)
• 类静态变量
• 指向classloader的引用
• 指向class类的引用(java.lang.class对象)
• 方法表(存储该类型对象可以调用的方法的直接引用,包括从父类中继承的)
• invokevirtual
• 合适的方法(参数的自动转型)
• 属性是动态绑定的么?
• 不是,动态绑定针对的只是对象的方法
• 可以通过将属性封装成getter方法实现动态绑定
String为什么设计成final?
主要是为了效率和安全
• 实现了字符串常量池,同值的不同的字符串变量都指向池中的同一个字符串
• 不可变所以线程安全,可以共享,提高性能
• HashCode不可变性,在创建的时候就缓存了,不需要重新计算,适合做hashmap中的键
底层是 private final char vlaue[]
如果不设计成final 的坏处:安全问题,数据库用户名密码,socket编程中的主机名,端口,如果可变,不安全
java异常
Throwable: Error exception(RuntimeException、checked)
error:
严重,程序无法处理,一般表示代码运行时jvm出现问题,通常有Vurtual MatchineError(虚拟机运行错误),NoClassDefError(类定义错误)oom(jvm耗完了可用内存)
oom模拟
List<byte[]> demoList = new ArrayList<>();
while (true){
demoList.add(new byte[1024*1024]);
}
exception:程序本身可以捕获的
运行时异常(不受检异常):jvm运行期间可能出现的错误,编译器不会检查,如空指针,下标越界,在程序中可以选择捕获处理也可以不处理。ArithmeticException
非运行时异常(受检异常):编译器会检查,必须对此类异常进行处理,try-catch,throws
比如IOException
throw与throws的区别:
throws用于方法名称后
throw用于方法内部:
如:

  1. if(b==0)
  2. throw new ArithmeticException("异常信息:除数不能为0");//抛出具体问题,编译时不检测
  3. return a/b;
    自定义异常
    继承Exception类
    public class ScoreException extends Exception{
    public ScoreException(){
    }
    public ScoreException(String message){
    super(message);
    }
    }
    public class Teacher {
    public void checkScore(int score) throws ScoreException{
    if (score <0 || score >100){
    //throw new ScoreException();
    throw new ScoreException("您给的分数有误,分数应该在0-100之间");
    }else{
    System.out.println("分数正常");
    }
    }
    }
    public class TeacherTest {
    public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int score = sc.nextInt();
    Teacher t = new Teacher();
    try {
    t.checkScore(score);
    } catch (ScoreException e) {
    e.printStackTrace();
    }
    }
    }
    创建线程的方式
    csdn
    • 继承Thread类,并重写run方法
    • 实现Runnable接口,并重写run方法
    • 使用Callable和FutureTask
    • 实现Callable接口
    callable
    callable:实现call方法,可以抛异常
    future接口:代表了一个异步计算的结果
    futuretask:实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和 Future接口,FutureTask是一个提供异步计算的结果的任务
    执行方式:借助futuretask
    //定义实现Callable接口的的实现类重写call方法。
    public class MyCallableTask implements Callable{
    @Override
    public Integer call() throws Exception {
    //TODO 线程执行方法
    }
    }

//创建Callable对象
Callable mycallabletask = new MyCallableTask();
//开始线程
FutureTask futuretask= new FutureTask(mycallabletask);
new Thread(futuretask).start();

通过futuretask可以得到MyCallableTask的call()的运行结果:
futuretask.get();
借助线程池
public interface ExecutorService extends Executor {
//提交一个Callable任务,返回值为一个Future类型
Future submit(Callable task);
//other methods...
}
ExecutorService exec = Executors.newCachedThreadPool();
Future future = exec.submit(new MyCallableTask());
线程的状态
初始状态:线程new出来之后就是
就绪状态:调用star()之后就是,此时若获得了时间片就进入了运行状态,调用yeild()又进 入就绪状态,当线程sleep结束,调用join结束,等待用户输入完毕,拿到锁,进入就 绪状态
运行状态:分配到时间片,运行
阻塞:等待锁
等待:调用wait、join、 park()后进入
超时等待:调用sleep、wait、join有参函数后
终止态:执行完成
停止进程的方法
• 退出标识:当run完成后进程终止
• stop:强制,暴力终止,已废弃不安全
• interrupt(中断标志设置为true):
• this.interrupted() 测试当前线程是否已经中断,
• this.isInterrupted() 测试线程是否已经中断,不能清除状态标识

中断机制:
协作机制,通过中断并不能直接中断另外一个线程,而需要被中断的线程自己处理中断
api:
• interrupt()本质上是通过调用java.lang.Thread设置中断flag为true
• interrupted()检测当前线程是否被中断了,并清除中断标注(也就是返回回之前的中断状态,然后重置标志)
• isInterrupted()检查this线程是否设置了中断标志
设置中断标志
interrupt方法是唯一能将中断状态设置为true的方法。
此外,类库中的有些类的方法也可能会调用中断,如
• FutureTask中的cancel方法,如果传入的参数为true,它将会在正在运行异步任务的线程上调用interrupt方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么cancel方法中的参数将不会起到什么效果;
• ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程,所以如果工作线程中正在执行的任务没有对中断做出响应,任务将一直执行直到正常结束。
收到了中断请求后如何结束该进程?
return
抛出InterrupttedException:对于那些阻塞方法(比如 wait() 和 sleep())而言,当另一个线程调用interrupt()中断该线程时,该线程会从阻塞状态退出并且抛出中断异常
java 线程通信方式
• 等待通知机制:
wait() 、notify()、notifyAll()
join
• volatile共享内存
jmm
可见性和禁止指令重排序
不适用的场景:
复合操作,a++
采用synchronized
Lock
原子类(cas)
内存屏障:
执行到内存屏障这句指令时,它前面的操作已经完成;
强制对缓存的修改立即写入主存;
如果是写操作,会导致其他cpu中对应的缓存行无效;
单例模式双重锁为什么要加volatile
public class TestInstance{
private volatile static TestInstance instance;

public static TestInstance getInstance(){
    if(instance ==null){
        synchronized(TestInstance.class){
            if(instance==null){
                instance = new TestInstance();
            }
        }
    }
    return instance;
}

}
instance = new TestInstance();
三步:a.分配内存,b.初始化对象,c.设置instance指向刚分配的地址;
有可能会排序为acb
线程A执行 a c 时,线程B过来判断到不为null,因为c行指向了一个地址,,线程B会返 回一个还未初始化的对象,所以要禁止指令重排序
• CountDownLatch并发工具
• 基于AQS
• 初始化一个CountDownLatch时告诉并发的线程,然后再每个线程处理完毕之后调用counDown()方法,该方法会使AQS内置的state -1;
• 主线程调用await()方法,他会阻塞到state == 0的时候返回
• CyclicBarrier
• 屏障
• 初始化线程参与者,调用await()。所有参与者都调用await()之后,所有线程从await()返回继续执行
• interrupt
• 线程池awaitTermination()方法
• 使用这个 awaitTermination() 方法的前提需要关闭线程池,如调用了 shutdown() 方法。
调用了 shutdown() 之后线程池会停止接受新任务,并且会平滑的关闭线程池中现有的任务。
• 管道通信
public static void piped() throws IOException {
//面向于字符 PipedInputStream 面向于字节
PipedWriter writer = new PipedWriter();
PipedReader reader = new PipedReader();

    //输入输出流建立连接
    writer.connect(reader);


    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            LOGGER.info("running");
            try {
                for (int i = 0; i < 10; i++) {

                    writer.write(i+"");
                    Thread.sleep(10);
                }
            } catch (Exception e) {

            } finally {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            LOGGER.info("running2");
            int msg = 0;
            try {
                while ((msg = reader.read()) != -1) {
                    LOGGER.info("msg={}", (char) msg);
                }

            } catch (Exception e) {

            }
        }
    });
    t1.start();
    t2.start();
}

为什么volatile不具有原子性,
volatile只能保证最后将更新的数据及时的返回到共享内存中,而不像CAS那样,保存一个历史值,从而在更新的时候也就不能保证数据是否被修改。
synchronized
原子性、可见性、有序性
对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值
可以把任何一个非NULL对象最为“锁”,在hotspot中 锁专门的名字对象监视器(Object Monitor)
当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this);
当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁;
当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例;
Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
monitorenter
monitorexit
AQS
AbstractQueuedSynchronizer
抽象队列式同步器
维护了一个volatile int state 和一个FIFO线程等待队列
定义了两种资源访问方式 exclusive(独占,如ReentrantLock)、share(共享,如Semaphore/CountDownLatch)
ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。
以CountDownLatch为·例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。
AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。
实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物
CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO
①. 业务场景,比如说我们有三个线程A、B、C去银行办理业务了,A线程最先抢到执行权开始办理业务,那么B、C两个线程就在CLH队列里面排队如图所示,注意傀儡结点和B结点的状态都会改为-1
②. 当A线程办理好业务,离开的时候,会把傀儡结点的waitStatus从-1改为0 | 将status从1改为0,将当前线程置为null
③. 这个时候如果B上位,首先将status从0改为1(表示占用),把thread置为线程B | 会执行如下图的①②③④,会触发GC,然后就把第一个灰色的傀儡结点给清除掉了,这个时候原来的B结点重新成为傀儡结点
AQS公平锁与非公平锁
csdn
• 公平锁:
当hasQueuedPredecessors 返回false时,则表示不需要去排队,会直接进行cas操作去修改state状态
当hasQueuedPredecessors返回true:
h和t不相等,表示队列中有>=2数量的节点。
h.next为null时,说明刚好有线程在操作队列把头节点的next节点脱离队列,则表示已经该线程正在修改state,所以需要排队,或者next.thread不为当前线程时说明已经有线程修改完state,把node节点的线程置为自己的线程,则当前线程也需要去排队。
• 非公平锁
当非公平锁调用lock方法时,一开始就去尝试修改state,如果成功则直接占有状态,并且设置当前线程,不管是刚刚进入的直接去拿锁或者后面实现的tryAcquire的方法都没有检查是否需要排队的操作
• 区别:
当线程释放锁成功,公平锁会去检查自己是否要去排队,只要其他线程比当前线程快或者当前有等待队列,则直接去排队;非公平锁则不一样,线程会去尝试拿锁,不管当前有没有正在等待的队列,一旦拿锁成功,表示插队成功,如果拿锁失败,并且当前有队列,则该线程会插入队尾。
谈一谈线程安全?
当多个线程访问同一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的。
实现线程安全的方式:Synchronized
synchronized
用法
原理分析
java对象头和monitor是基础
线程在获取锁的时候,实际上就是获得一个监视器对象(monitor) ,
synchronized同步块使用了monitorenter和monitorexit指令实现同步,线程执行到monitorenter指令时,会尝试获取对象所对应的monitor所有权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的所有权。
对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。
Klass Point:是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
Mark Word:用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.
synchronized 在JDK1.6引入偏向锁,轻量级锁的概念
锁存在四种状态分别是:无锁、偏向锁、轻量级锁、重量级锁; 锁的状态根据竞争激烈的程度从低到高不断升级。
偏向锁:大部分情况下,锁总是由同一个线程多次获得,对象头存储偏向进程的ID,之后这个线程直接比较这个ID,相等的话就不需要在尝试获取锁。
当其他线程竞争偏向锁时,如果偏向进程退出临界区,当前进程cas修改偏向锁ID,
如果偏向进程还在临界区,直接把偏向锁升级成轻量级锁
UseBiasedLocking 参数开启或者关闭偏向锁
轻量级锁:锁升级为轻量级锁之后,对象的 Markword 也会进行相应的的变化。升级为轻量级锁的过程:

  1. 线程在自己的栈桢中创建锁记录 LockRecord。
  2. 将锁对象的对象头中的MarkWord复制到线程的刚刚创建的锁记录中。
  3. 将锁记录中的 Owner 指针指向锁对象。
  4. 将锁对象的对象头的 MarkWord替换为指向锁记录的指针。
    ReentrantLock和synchronized区别
    (1) synchronized 是Java的一个内置关键字,而ReentrantLock是Java的一个类。
    (2) synchronized只能是非公平锁。而ReentrantLock可以实现公平锁和非公平锁两种。
    (3) synchronized不能中断一个等待锁的线程,而Lock可以中断一个试图获取锁的线程。
    (4) synchronized不能设置超时,而Lock可以设置超时。
    (5) synchronized会自动释放锁,而ReentrantLock不会自动释放锁,必须手动释放,否则可能会导致死锁。
    sql 左连接
    垃圾判断
    输入输出
    tcp 无连接 无转态 端口
    keepalive
    cookie session
    谈一谈对String的理解
    java.lang包
    final修饰不可变
    底层是char类型的数组
    引用数据类型,因为大量的使用,被设计成可以直接赋值
    堆中存储实例
    字符串常量池存储引用,
    JAVA规定,你不可以直接改变常量池String 对象的值,因为你一旦在常量池创建了值为 “abc” 的String 类型的对象,那么以后所有值是"abc"的String 类型,都会指向常量池里面的这个对象。
    (1)如果要操作少量的数据用 String;
    (2)多线程操作字符串缓冲区下操作大量数据 StringBuffer;
    (3)单线程操作字符串缓冲区下操作大量数据 StringBuilder。
    JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
    • 为字符串开辟一个字符串常量池,类似于缓存区
    • 创建字符串常量时,首先检查字符串常量池是否存在该字符串
    • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中
    String c = "xx" + "yy " + a + "zz" + "mm" + b; 实质上的实现过程是: String c = new StringBuilder("xxyy ").append(a).append("zz").append("mm").append(b).toString();
    底层通过 StringBuilder实现
    中断
    发生中断意味着需要操作系统介入,开展管理工作,而操作系统的管理工作需要使用特权指令,需要cpu从用户态转为核心态,有了中断才能实现多道程序并发执行。
    用户态到核心态的唯一途径;
    核心-》用户 修改程序状态字
posted @ 2021-10-13 20:54  www小汪  阅读(39)  评论(0)    收藏  举报