java随机笔记二(面试复习用)

一.抽象类与接口在使用中的区别:

一)抽象类:使用格式为:public abstract class A{}html

一、在抽象类中的方法能够定义抽象方法,也能够是具体的成员方法,不能是(static)类方法。java

public  void get(){ System.out.println("ok")};面试

public  abstract void geta(String abc);//ok编程

public  static abstract void geta(String abc);//错误设计模式

二、在抽象类中属性能够是成员属性,也能够是类属性。安全

private String name;//ok
private static String age;//ok多线程

三、在抽象类中有构造方法,默认是无参(没有参数列表)的,也能够有参数的构造方法,可是不能用来实例化对象(不能使用new去建立对象)。并发

四、抽象类是用来充当父类,给子类去继承与发展的,当子类继承抽象类时,方法须要定义访问限定符,并且子类必须重写全部的抽象方法,且子类不能减少可见范围(访问限定符的设定不能减少)。jvm

五、一个类只能继承一个抽象类。ide

继承的方法以下:public class B extends A{}

二)接口

使用格式为:public interface C{}

一、在接口中的方法只能定义抽象方法,不能有方法体,定义时默认为public abstract,能够省略。接口中的方法默认是public,也只能是public。

eg: [public abstract] void I; ----无返回值

[public abstract] int J; ----有返回值

二、接口中属性的定义时固定的:

public static final 数据类型 变量名 =初始值;

eg: public static final int a =0;

public static final String aa =null;

三、接口中不能有构造方法,更加不能建立对象。

四、接口也是用来充当父类的,给子类去实现与扩展的,当子类实现接口时,必须重写接口中全部的方法。且子类不能减少可见范围(访问限定符的设置不能减少)。

五、一个类能够实现多个接口。

public class E inmplements C,D{}

一个类能够先继承一个类,再实现多个接口

public class F extends E implements C,D{}

二.classpath和path的用处和区别

一)计算机编程中classpath和path的用处和区别

通常它们在何时使用

java来举例,咱们想要运行java文件,好比咱们安装jdk的时候,咱们须要将bin指定到path,这样才行,当咱们运行jar包时,须要把jar包放在当前路径,或者classpath下,才能够运行成功,那么大家有没有想过为何要这样作,他们两者的区别是什么?

path,classpath配置好以后有什么用?

  1. path配置好以后,让java jdk\bin目录下的工具,能够在任意目录下运行,缘由是,将该工具所在目录告诉了系统,当使用该工具时,由系统帮咱们去找指定的目录。

  2. 若是没有定义环境变量classpath,java启动jvm后,会在当前目录下查找要运行的类文件,若是指定了classpath,那么会在指定的目录下查找要运行的类文件。还会在当前目录找吗?两种状况:第一种,若是classpath的值结尾处有分号,在具体路径中没有找到运行的类,会默认在当前目录再找一次。第二种,若是classpath的值结果出没有分号,在具体的路径中没有找到运行的类,不会再当前目录找。经常使用:通常不指定分号,若是没有在指定目录下找到要运行的类文件,就报错,这样能够调试程序

3.path配置方式

永久配置方式:JAVA_HOME=%安装路径%\Java\jdk path=%JAVA_HOME%\bin搭配配置

临时配置方式:set path=%path%;C:\Program Files\Java\jdk\bin

4.classpath如何配置

永久配置方式:classpath=.;c:\;e:\

临时配置方式:set classpath=.;c:\;e:\

三.写出满意的单例模式

单例模式是一种经常使用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。经过单例模式能够保证系统中一个类只有一个实例。

今天咱们不谈单例模式的用途,只说一说若是在面试的时候面试官让你敲一段代码实现单例模式的状况下怎样写出让面试官眼前一亮的单例代码。由于笔者学的是Java,因此接下来的实例将用Java语言编写。

说到单例模式,第一个想到的是该类中有一个初始化为null的自身引用,且被private修饰符修饰,其它类不得直接访问。除此以外,单例模式的类还须要有private的构造方法,这一点不难理解,若是构造方法是public的,那么类外部能够直接调用该类的构造方法,如此一来便不具有单例的特性。那么怎么获取该类惟一的实例呢?这就须要一个公有的获取器,该方法返回值类型是单例模式类,返回的结果天然是该类中惟一的实例。思路有了,咱们即可以实现最简单的单例模式类:

不得不说,这样的作法确实达到了单例模式的要求,正常状况下系统中只有一个Singleton的对象。可是若是存在并发的状况呢?两个用户同时访问该类的获取器,此时假设Singleton对象还未被实例化,那么系统将会两次调用构造方法,这样一来系统中就会存在两个Singleton类的实例。说明这种方式的单例没有考虑到并发状况,说明面试者只是粗略的了解单例模式,并无加以深刻思考,想让面试官满意?

Java相较于C++而言我的认为编程的难易度上来讲要容易不少。在考虑线程同步时一个synchronized关键字便能解决普通加锁问题。synchronized关键字,当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多只有一个线程执行该段代码。也就是说当两个线程同时访问类中synchronized方法或代码块时,只能有一个线程执行其代码,另外一个只能等待当前线程调用结束后才能访问。这下子单例的实现就so easy了!只要对代码稍加改动便可:

这样的写法面试官会以为你这个面试者在思考问题的时候比较全面,考虑到并发的状况,相较以前的方式面试官会以为:少年,颇有前途哦!

然而光是让面试官看好是不够的,咱们要让他欣赏,经过单例这样的小问题便能拿到offer。也就是说第二种实现方式是能够进行优化的。如何优化呢?咱们看到,当前系统中每次调用获取方法时便会进行加锁,而加锁须要的时间即是咱们能够进行优化的地方。如今我所想的是咱们只须要在第一次调用时加一次锁日后便不再不须要加锁了,这样一来便省下了每次调用加锁的时间,虽然计算机执行加锁的时间很短但长此以往也是至关长的一段时间。

那么怎么实现呢?这须要引入另外一个关键字volatile。volatile修饰的话就能够确保instance = new Singleton();对应的指令不会重排序(JVM当发现代码执行顺序变化但结果不变时可能会改变执行顺序来提高自身性能。好坑。。。),也是线程安全的。

volatile关键字的含义

在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,觉得使用这个关键字,在进行多线程并发处理的时候就能够万事大吉。

Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。

synchronized 

同步块你们都比较熟悉,经过 synchronized 关键字来实现,全部加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程可以用

synchronized 修饰的方法 或者 代码块。

volatile

用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最新值。volatile很容易被误用,用来进行原子性操做。

下面看一个例子,咱们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一

public class Counter {

    public static int count = 0;

    public static void inc() {

        //这里延迟1毫秒,使得结果明显

        try {

            Thread.sleep(1);

        } catch (InterruptedException e) {

        }

        count++;

    }

    public static void main(String[] args) {

        //同时启动1000个线程,去进行i++计算,看看实际结果

        for (int i = 0; i < 1000; i++) {

            new Thread(new Runnable() {

                @Override

                public void run() {

                    Counter.inc();

                }

            }).start();

        }

        //这里每次运行的值都有可能不一样,可能为1000

        System.out.println("运行结果:Counter.count=" + Counter.count);

    }

}

 

 

1

运行结果:Counter.count=995

实际运算结果每次可能都不同,本机的结果为:运行结果:Counter.count=995,能够看出,在多线程的环境下,Counter.count并无指望结果是1000

不少人觉得,这个是多线程并发问题,只须要在变量count以前加上volatile就能够避免这个问题,那咱们在修改代码看看,看看结果是否是符合咱们的指望

public class Counter {

    public volatile static int count = 0;

    public static void inc() {

        //这里延迟1毫秒,使得结果明显

        try {

            Thread.sleep(1);

        } catch (InterruptedException e) {

        }

        count++;

    }

    public static void main(String[] args) {

        //同时启动1000个线程,去进行i++计算,看看实际结果

        for (int i = 0; i < 1000; i++) {

            new Thread(new Runnable() {

                @Override

                public void run() {

                    Counter.inc();

                }

            }).start();

        }

        //这里每次运行的值都有可能不一样,可能为1000

        System.out.println("运行结果:Counter.count=" + Counter.count);

    }

}

运行结果:Counter.count=992

运行结果仍是没有咱们指望的1000,下面咱们分析一下缘由

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先经过对象的引用找到对应在堆内存的变量的值,而后把堆内存

变量的具体值load到线程本地内存中,创建一个变量副本,以后线程就再也不和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完以后的某一个时刻(线程退出以前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图

描述这写交互

read and load 从主存复制变量到当前工做内存
use and assign  执行代码,改变共享变量值
store and write 用工做内存数据刷新主存相关内容

其中use and assign 能够屡次出现

可是这一些操做并非原子性,也就是 在read load以后,若是主内存count变量发生修改以后,线程工做内存中的值因为已经加载,不会产生对应的变化,因此计算出来的结果会和预期不同

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工做内存的值是最新的

例如假如线程1,线程2 在进行read,load 操做中,发现主内存中count的值都是5,那么都会加载这个最新的值

在线程1堆count进行修改以后,会write到主内存中,主内存中的count变量就会变为6

线程2因为已经进行read,load操做,在进行运算以后,也会更新主内存count的变量值为6

致使两个线程及时用volatile关键字修改以后,仍是会存在并发的状况。

对于值引用来讲,多线程操做的是变量的副本,操做完后刷新到主存中。而对于地址引用,多线程是经过地址操做的是同一个变量。volatitle关键字告诉编译器,直接去经过地址操做变量,而不是变量的副本

相关文章
相关标签/搜索