并发

以下哪种JAVA得变量声明方式可以避免程序在多线程竞争情况下读到不正确的值(  )
A。volatile
B。static volatile
C。synchronized
D. static

答案:AB
– synchronized不是修饰变量的 它修饰方法或代码块或对象,

  • static修饰的变量属于类,线程在使用这个属性的时候是从类中复制拷贝一份到线程工作内存中的,如果修改线程内存中的值之后再写回到原先的位置,就会有线程安全问题。用static修饰的变量可见性是无法确保的。
  • 避免程序在多线程竞争情况下读到不正确的值需要保证内存可见性,当一个线程修改了volatile修饰的变量的值,volatile会保证新值立即同步到主内存,以及每次使用前立即从主内存读取。

当一个变量定义为 volatile 之后,将具备两种特性:
1.保证此变量对所有的线程的可见性,即当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile到底做了什么:

  • 禁止了指令重排

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量值,这个新值对其他线程是立即可见的

  • 不保证原子性(线程不安全)

synchronized关键字和volatile关键字比较:

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。

  • 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞

  • volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。

  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性

基本数据类型

下面四行代码错在哪里

public static void main(String[] args){
    Double x=1.2; 
    long l = 1.2; 
    float f =  x/l;
    System.out.println(f);
}

解析:

1、Double为包装类,发生了自动装箱 2、long处高精度转低精度应强制转换 3、float处,无法强制转换,应该有个拆箱的过程,即 (float)x.doubleValue()/1; 注意包装类和基本数据类型区别
这里Doule包装类,当与数值进行比较或者计算的时候,会自动拆箱成数值在进行计算或比较,最后的结果是double类型,不能自动转换成float类型

Java基本数据类型转换:byte、short、char—>int—>long—>float—>double
由低精度到到精度可以自动转换,而高精度到低精度会损失精度,故需要强制转换。
故第三行,小数默认为double型(float型需要在数字后面加f),不强转编译出错。
第四行,Double到double可以自动拆箱,但向下还是需要强转,再加上 l 的类型错误,故编译出错。
第二行,包装类自动装箱,没问题。

线程实现的三种方式,

实现线程虽然说常用的有两种,但是实际为三种
1 继承的Thread类,重写run方法,但是这种是有局限性的,因为此方式就不能继承其它的类了
2 实现Runnable接口,重写run方法
3 实现Callable接口,

各个锁

4.下列关于Java并发的说法中正确的是()
A. CopyOnWriteArrayList适用于写多读少的并发场景
B. ReadWriteLock适用于读多写少的并发场景
C. ConcurrentHashMap的写操作不需要加锁,读操作需要加锁
D. 只要在定义int类型的成员变量i的时候加上volatile关键字,那么多线程并发执行i++这样的操作的时候就是线程安全的了

解析:
A. CopyOnWriteArrayList适用于写少读多的并发场景。 B
ReadWriteLock即为读写锁,他要求写与写之间互斥,读与写之间互斥, 读与读之间可以并发执行。在读多写少的情况下可以提高效率 C,ConcurrentHashMap是同步的HashMap,读写都加锁 D,volatile只保证多线程操作的可见性,不保证原子性

数组:

java语言的下面几种数组复制方法中,哪个效率最高?
A. for 循环逐一复制
B. System.arraycopy
C. Array.copyOf
D. 使用clone方法

答案:B
解析:
复制的效率System.arraycopy>clone>Arrays.copyOf>for循环

在System类源码中给出了arraycopy的方法,是native方法,也就是本地方法,肯定是最快的。而Arrays.copyOf(注意是Arrays类,不是Array)的实现,在源码中是调用System.arraycopy的,多了一个步骤,肯定就不是最快的。

抽象类和接口

抽象类和接口

  • 不能new抽象类,只能靠子类去实现他
  • 抽象类中可以写普通的方法
  • 抽象方法必须在抽象类中
  • 抽象类存在构造函数 虽然抽象类没有提供任何构造函数,编译器将为抽象类添加默认的无参数的构造函数,如果没有的话抽象类的子类将无法编译,因为在任何构造函数中的第一条语句隐式调用super()

抽象类存在的意义是被子类继承,否则抽象类将毫无意义,抽象类体现的是模板思想,模板是通用的东西 抽象类中已经是具体的实现(抽象类中可以有成员变量和实现方法),而模板中不能决定的东西定义成抽象方法,让使用模板(继承抽象类的类)的类去重写抽象方法实现需求,这是典型的模板思想。

题目

关于抽象类与接口,下列说法正确的有?
A。为了降低模块的耦合性,应优先选用接口,尽量少用抽象类
B。抽象类可以被声明使用,接口不可以被声明使用
C。抽象类和接口都不能被实例化。

答案:QUM= (使用base64解密查看答案)

解析:
接口体现的是一种规范和实现分离的设计哲学,代码编写过程中充分利用接口可以很大程度的降低程序各个模块之间的耦合,从而提高系统的可扩展性和可维护性。基于这一原则,很多软件架构更提倡面向接口编程而不是实现类编程。
接口和抽象类都可以被声明使用,因此B选项错误。
抽象类确实有构造方法,但这个构造方法是用来被子类调用的,因为任何子类都必须调用从Object开始的所有父亲的构造方法,才算完成初始化工作。如果抽象类被实例化,就会报错,编译无法通过。而接口里不包含构造器,自然无法被实例化。

抽象类确实有构造方法,但这个构造方法是用来被子类调用的,因为任何子类都必须调用从Object开始的所有父亲的构造方法,才算完成初始化工作。如果抽象类被实例化,就会报错,编译无法通过。而接口里不包含构造器,自然无法被实例化。

题目2

下面错在哪里

public abstract class Test { 
    abstract void method() { } 
}

解析:

错在{},抽象方法不能有方法实现

访问权限

把内部类理解成类的成员,成员有4种访问权限吧,内部类也是!分别为private、protected、public以及默认的访问权限default

实现接口必须重写其所有的抽象方法,而重写的规则是三同一大一小:method signature相同(返回类型、方法名、参数类型及参数名),访问权限>=被重写前,抛出异常<=被重写前。

类加载器

    1)Bootstrap ClassLoader

    负责加载JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

    2)Extension ClassLoader

    负责加载java平台中扩展功能的一些jar包,包括JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

    3)App ClassLoader

    负责记载classpath中指定的jar包及目录中class

    4)Custom ClassLoader

    属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

    加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

BEAC 快速记

  • 一. 启动类加载器
    • 负责将存放在 < JAVA_HOME > \lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib中也不会被加载)类库加载到虚拟机内存中。
  • 二. 扩展类加载器
    • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载< JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
  • 三. 应用程序类加载器
    • 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$App-ClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(classPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认类加载器。

类加载过程

类加载过程:

1, JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.class加载到方法区

2, 在.class加载到方法区时,会分为两部分加载:先加载非静态内容,再加载静态内容

3, 加载非静态内容:把.class中的所有非静态内容加载到方法区下的非静态区域内

4, 加载静态内容:

4.1、把.class中的所有静态内容加载到方法区下的静态区域内

4.2、静态内容加载完成之后,对所有的静态变量进行默认初始化

4.3、所有的静态变量默认初始化完成之后,再进行显式初始化

4.4、当静态区域下的所有静态变量显式初始化完后,执行静态代码块

5,当静态区域下的静态代码块,执行完之后,整个类的加载就完成了。
类加载过程
类加载过程2

ANSI

A、标准ASCII只使用7个bit,扩展的ASCII使用8个bit。
B、ANSI通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符。超出此范围的使用0x80~0xFFFF来编码,即扩展的ASCII编码。不同 ANSI 编码之间互不兼容。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。
C、ANSI通常使用 0x00~0x7f 范围的1 个字节来表示 1 个英文字符,即ASCII码
D、ASCII码包含一些特殊空字符(所以ascii码不都是可打印字符)

D ascii码不都是可打印字符 : ASCII 表上的数字 0–31 分配给了控制字符,用于控制像打印机等一些外围设备。例如,12 代表换页/新页功能。此命令指示打印机跳到下一页的开头。

‘\0’ 字符串结束符
-1 EOF文件结束符

解决hash冲突

解决哈希冲突的方法有四种,开放地址法、再哈希法、拉链法、公共溢出法,详情参考:连接地址 ,Java8里面,HashMap使用拉链法,ThreadLocal采用开放地址法。

TreeMap:数组加红黑树(有序,线程不安全,不允许空值空键)LinkedHashSet:底层是LinkedHashMap,数组加双向链表。通过哈希值决定存储位置,通过双向链表维持次序。 ThreadLocal:jdbc时保证每个线程开始到结束得到的都是同一个数据库连接(事务的提交和回滚不会发生问题)。底层:ThreadLocal的 get()方法得到的是当前线程的成员变量threadLocals(Thread类的成员变量,所以它对于每个线程都是相互独立的), threadLocals本质 是一个ThreadLocalMap(并没有实现Map接口),Entry(ThreadLocal k,Object v) 底层是一个数组,但是也使用哈希算法确定存储位 置,不过使用开放定址法解决哈希冲突。

super

子类不可以继承父类的构造方法,只可以调用父类的构造方法。子类中所有的构造函数都会默认访问父类中的空参数构造函数,这是因为子类的构造函数内第一行都有默认的super()语句。super()表示子类在初始化时调用父类的空参数的构造函数来完成初始化。一个类都会有默认的空参数的构造函数,若指定了带参构造函数,那么默认的空参数的构造函数,就不存在了。这时如果子类的构造函数有默认的super()语句,那么就会出现错误,因为父类中没有空参数的构造函数。因此,在子类中默认super()语句,在父类中无对应的构造函数,必须在子类的构造函数中通过this或super(参数)指定要访问的父类中的构造函数。

分类: java

0 条评论

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注

ICP备案号: 辽ICP备20003309号