美文网首页
JAVA题库(一)

JAVA题库(一)

作者: Bonjour_蒙 | 来源:发表于2019-03-27 13:26 被阅读0次
    1.多个线程同时读写,读线程的数量远远大于写线程,你认为应该如何解决并发的问题?你会选择什么样的锁?

    答:解决高并发问题:选择ReadWriteLock读写锁。

    public class ReadWriteLockTest {
        public static void main(String[] args) {
            final Queue queue = new Queue();
            for(int i =1;i<=3;i++){
                new Thread(new Runnable() {
                    public void run() {
                        while(true){
                            try {
                                Thread.sleep((long)Math.random()*100000);
                                queue.put(new Random().nextInt(100000));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
                new Thread(new Runnable() {
                    public void run() {
                        while(true){
                            try {
                                Thread.sleep((long)Math.random()*100000);
                                queue.get();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
        }
    }
    class Queue{
        //共享数据,只能有一个线程对其能更改
        private Object data = 85;
        ReadWriteLock rwl = new ReentrantReadWriteLock();
        public void get(){
            rwl.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + " be ready to read data !");
                System.out.println(Thread.currentThread().getName() + " have read data :" + data);
            }finally{
                rwl.readLock().unlock();
            }
        }
        public void put(Object data){
            rwl.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + " be ready to write data !");
                this.data = data ;
                System.out.println(Thread.currentThread().getName() + " have write data :" + data);
            }finally{
                rwl.writeLock().unlock();
            }
        }
    }
    
    2.JAVA的AQS是否了解,它是干嘛的?

    答:AbstractQueuedSynchronizer,抽象的队列式的服务器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。

    3.除了synchronized关键字之外,你是怎么来保障线程安全的?

    答: 每次查询少查点,用rowid记录标记位,下次查询从标记位开始,就是个变相的分页。

    4.Tomcat本身的参数你一般会怎么调整?

    答:tomcat一些默认参数不适合生产环境使用,因此需要修改一些参数。
    ①.修改启动时内存参数,并指定JVM时区:

    在Tomcat上运行j2ee项目代码时,经常会出现内存溢出的情况,解决办法是在系统参数中增加系统参数:
    
    window下,在catalina.bat最前面:
    set JAVA_OPTS=-XX:PermSize=64M -XX:MaxPermSize=128m -Xms512m -Xmx1024m;-Duser.timezone=GMT+08;一定加在catalina.bat最前面。
    
    linux下,在catalina.sh最前面添加:
    JAVA_OPTS=“-XX:PermSize=64M -XX:MaxPermSize=128m -Xms512m -Xmx1024m; -Duser.timezone=Asia/Shanghai”;一定要加在catalina.bat最前面;
    
    注意:前后二者区别,有无set,有无双引号。
    

    ②.线程池配置:

    使用线程池,用较少的线程处理较多的访问,可以提高Tomcat处理请求的能力,使用方式:
    首先,打开/conf/server.xml,增加<Exector name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="20" maxldleTime="60000"/>
    最大线程500,最小空闲线程数20,线程最大空闲时间60秒。
    然后,修改<Connector ...>节点,增加executor属性,如:
    <Connector    
    exector="tomcatThreadPool" 
    port="80" 
    protocol="HTTP/1.1" 
    maxThreads="600" 
    minSpareThreads="100"  
    maxSpareThreads="300"
    connectionTimeout="60000"
    keepAliveRequests="1"
    redirectPort="443"/>
    Tomcat可创建的最大线程数,每一个线程处理一个请求;
    Tomcat启动时的初始化的线程数;
    Tomcat就会关闭不再需要的socket线程;
    connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常设置为30000毫秒。
    enableLookups:是否允许DNS查询
    注意:可以多个connector公用一个线程池。
    

    ③.调整连接相关Connector的参数:

    <Connector 
    executor="tomcatThreadPool" 
    port="80" 
    protocol="HTTP/1.1"
    connectionTimeout="60000"
    keepAliveTimeout="15000"
    maxKeepAliveRequests="1"
    redirectPort="443"
    maxHttpHeaderSize="8129" 
    URIEncoding="UTF-8"
    enableLookups="false" acceptCount="100"
    disableUploadTimeout="true"/>
    

    ④.负载均衡,集群的配置
    Tomcat6支持分布式部署,可以实现集群功能,提高相应能力
    ⑤.利用JMX监控Tomcat运行情况,需要手工调整启动参数

    打开catalina.bat,增加一行
    set JAVA_OPTS=%JAVA_OPTS%
    -Dcom.sun.management.jmxremote.port=10090
    -Dcom.sun.management.jmxremote.ssl=false 
    -Dcom.sun.management.jmxremote.authenticate=false
    -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 
    Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties"
    

    linux下修改cataline.sh:

    JAVA_OPTS="-Dcom.sun.management.jmxremote.port=10090 
    -Dcom.sun.management.jmxremote.ssl=false
    -Dcom.sun.management.jmxremote.authenticate=false 
    -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 
    -Djava.util.logging.config.file=%CATALINA_BASE\conf\logging.properties"
    
    注意JDK\jre\lib\management\management.properties文件必须存在。重新启动Tomcat节点,然后用jconsole连接

    ⑥.Tomcat增加一个应用

    在server.xml的Host标签中增加行
    <Context displayName="OA" docBase="/app/web-apps/GACWP" path=""/>
    path表示上下文名称,空表示根路径
    
    5.你有没有用过Spring的AOP? 是用来干嘛的? 大概会怎么使用?

    答:常用的AOP及时安全检验,日志操作,事务操作等。

    假如没有aop,在做日志处理的时候,我们会在每个方法中添加日志处理;
    但大多数的日志处理代码是相同的,为了实现代码复用,我们可能把日志处理抽离成一个新的方法。
    但是这样我们仍然必须手动插入这些方法。
    但这样两个方法就是强耦合的,假如此时我们不需要这个功能了,或者想换成其他功能,那么就必须一个个修改。
    通过动态代理,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。
    这样的思想,被称为面向切面编程,亦即AOP。
    
    为了在指定位置执行这些横向的功能,需要知道指定的是什么地方,把切点和通知合在一起就是切面了,
    一个切面指定了在何时何地执行何种方法。在spring aop中如此定义这个切面:
    @Aspect
    @Component
    public class UserAspect {
        @Before("execution(* com.aop.service.impl.UserServiceImpl.login(..))")
        public void loginLog(){
            System.out.println("user login");
        }
    }
    使用注解@Aspect将某个特定的类声明为切面,这样,该类下的方法就可以声明为横向的功能点后插入到指定位置。
    使用execution表达式声明在这个切点,第一个位置指定了方法的返回值,*号代表任意类型的返回值,
    然后是所在的类和方法名,*号同样代表任意,就是该类中任意的方法,在上一个例子中方法名是login,
    则是指定了该类中的login方法。然后最后一个参数是方法入参,因为java中支持重载,
    所以这个参数可以帮助你更精确的进行定位。两点表示任意参数类型。
    这样,execution表达式告诉了程序该在何地执行通知。
    而被诸如@Before注解修饰的方法就是通知的内容,也就是做什么。
    
    至此,我们就可以使用spring aop,但是还有两点需要得到注意
    1.  将切面类声明为一个bean
    2.  切点指定的方法所在的类也同样需由spring注入才能生效
    
    
    6.如果一个接口有2个不同的实现, 那么怎么来Autowire一个指定的实现?
    //使用@Qualifier("aaaService")注解
    @Service
    public class AaaService implements IChangePassword {
        @Override
        public void changePassword(String username, String password) {}
    }
    
    @Service
    public class BbbService implements IChangePassword {
        @Override
        public void changePassword(String username, String password) {}
    }
     
    public class AccountController extends BaseController {
        @Autowired
        @Qualifier("aaaService")
        private IChangePassword aaaService;
     
        @Autowired
        @Qualifier("bbbService")
        private IChangePassword bbbService;
    }
    
    7.如果想在某个Bean生成并装配完毕后执行自己的逻辑,可以什么方式实现?

    答:有时,我们需要在启动bean时初始化bean属性,例如读取perporties文件,对属性进行赋值;启动容器时让某个method方法执行等等。这时需要在进行配置,让bean在注入时启动指定方法。

    共有以下几种方法:
    ①、如果是通过XML配置文件进行Bean的生成,我们可以在配置Bean的时候,使用init-method=“executionMethod”属性,这样在当前Bean实例化完成后,就会自动执行指定的executionMethod。executionMethod为定义在Bean中的一个方法。

    <bean id="initializingBean" class="全类名" init-method="executionMethod"></bean>

    ②、可以让Bean实现InitializationBean接口,并重写其afterPropertiesSet()方法。

    ③、给需要调用的方法加上@PostConstruct注解,即在构造方法后调用。比如
    @PostConstruct
    private void initMethod1(){ .....}

    7.SpringBoot没有放到web容器里为什么能跑HTTP服务?

    答:因为springboot中内嵌了tomcat,jetty,undertow。

    8.SpringBoot中如果你想使用自定义的配置文件而不仅仅是application.properties,应该怎么弄?

    答:①.在类中使用注解 @PropertySource,例如:@PropertySource("classpath:define.properties")
    ②.在配置文件中引入配置文件<context:property-placeholder location="classpath:jdbc.properties,classpath:rabbitmq.properties"/>

    9.SpringMVC如果希望把输出的Object(例如XXResult或者XXResponse)这种包装为JSON输出, 应该怎么处理?

    答:①.加入jackson依赖的jar包:

            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-core</artifactId>
                <version>2.9.8</version>
            </dependency>
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.8</version>
            </dependency>
    

    ②.配置文件中进行配置:

               <bean id="fastJsonHttpMessageConverter" class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                    <property name="supportedMediaTypes">
                            <value>application/json;charset=UTF-8</value>
                    </property
                </bean>
    

    ③.直接使用注解的方式“@ResponseBody”自动将返回值转化为json格式.

    10.如果有很多数据插入MYSQL 你会选择什么方式?

    答:在MySQL的命令行界面执行以下命令:LOAD DATA INFILE 'd:/t.sql' INTO TABLE e_tuike_goods FIELDS TERMINATED BY ',';

    11.如果查询很慢,你会想到的第一个方式是什么?索引是干嘛的?

    答:sql语句优化或者该数据表添加索引,

    就比如一本书,你想看第六章第六节讲的是什么,你会怎么做,一般人肯定去看目录,
    找到这一节对应的页数,然后翻到这一页。这就是目录索引,帮助读者快速找到想要的章节。
    在数据库中,我们也有索引,其目的当然和我们翻书一样,能帮助我们提高查询的效率。
    索引就像目录一样,减少了计算机工作量,对于表记录较多的数据库来说是非常实用的,
    可以大大的提高查询的速度。否则的话,如果没有索引,计算机会一条一条的扫描,
    每一次都要扫描所有的记录,浪费大量的cpu时间。
    
    我们都知道对于一个无序的表,和一个有序的表,有序表的查询方法会有更多地选择,
    每种查询方法的效率也不同,其实为表建立索引,也就是对表中的记录按照索引字段排序。
    
    12.查询死掉了,想要找出执行的查询进程用什么命令?

    答: ps S 列出程序时,包括已中断的子程序资料。

    13.读写分离是怎么做的?你认为中间件会怎么来操作?这样操作跟事务有什么关系?

    答:①.读写分离的实现原理就是在执行SQL语句的时候,判断到底是读操作还是写操作,把读的操作转向到读的服务器上(从服务器,一般是多台),写的操作转到写的服务器上(主服务器,一般是一台),当然为了保证多台数据库数据的一致性,需要主从复制。
    主从复制的实现原理是:mysql中有一种日志,叫做bin日志(二进制日志),会记录下所有修改过数据库的sql语句。
    主从复制的原理实际是多台服务器都开启bin日志,然后主服务器会把执行过的sql语句记录到bin日志中,之后从服务器读取这个bin日志,把该日志的内容保存到自己中继日志里面,从服务器再把中继日志中记录的sql语句同样的执行一遍,这样从服务器上的数据就和主服务器相同了。
    ②.中间件有淘宝开源的cobar,以及后来开源社区根据cobar进行二次开发的mycat

    14.你知道哪些或者你们线上使用什么GC策略? 它有什么优势,适用于什么场景?
    使用SerialGC的场景: 
    1、如果应用的堆大小在100MB以内。 
    2、如果应用在一个单核单线程的服务器上面,并且对应用暂停的时间无需求。 
    使用ParallelGC的场景: 
    如果需要应用在高峰期有较好的性能,但是对应用停顿时间无高要求(比如:停顿1s甚至更长)。 
    使用G1、CMS场景: 
    1、对应用的延迟有很高的要求。 
    2、如果内存大于6G请使用G1。
    
    15.JAVA类加载器包括几种?它们之间的父子关系是怎么样的?双亲委派机制是什么意思?有什么好处?

    答:java类加载器包括:
    1.启动类加载器(Bootstrap ClassLoader),也叫跟类加载器,负责加载java的核心类库,例如(%JAVA_HOME%/lib)目录下的rt.jar(包含System,String这样的核心类),跟类加载器非常特殊,它不是java.lang.ClassLoader的子类,它是JVM自身内部由C/C++实现的,并不是java实现的。

    2.扩展类加载器(Extension ClassLoader),负责加载扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包,用户可以把自己开发的类打包成jar包放在这个目录下即可扩展核心类以外的功能

    3.系统类加载器(System ClassLoader\APP ClassLoader),又称为应用程序类加载器,是加载CLASSPATH环境变量下所指定的jar包与类路径,一般来说,用户自定义的就是由APP ClassLoader加载的

    各类加载器之间的关系:

    以结合关系复用父类加载器的父子关系,注意,这里的父子关系并不是继承关系实现的

    类加载器的双亲委派加载机制:

    当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载都是如此,因此所有的加载请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候(在他的加载路径里找不到这个所需要加载的类),子类加载器才会尝试自己去加载。

    双亲委派模型的源码实现:

    主要体现在ClassLoader的loadClass()方法,思路很简单:先检查是否已经被加载,若没有被加载则调用父类的LoadClass()方法,若父类加载器为空,则默认使用启动类加载器作为父类加载器,如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。

    16.如何自定义一个类加载器?你使用过哪些或者你在什么场景下需要一个自定义的类加载器吗?堆内存设置的参数是什么?

    答:我们需要的类不一定存放在已经设置好的ClassPath下(有系统类加载器APPClassLoader加载的路径),对于自定义路径下的class类文件的加载,我们需要自己的ClassLoader。
    有时我们不一定是从类文件中读取类,可能是从网络的输入流中读取类,这就需要做一些加密和解密操作,这就需要自己实现加载类的逻辑,当然其他的特殊处理也同样适用。
    可以定义类的实现机制,实现类的热部署,如OSGi中的bundle模块就是通过实现自己的ClassLoader实现的。

    public class MyClassLoader extends ClassLoader {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            if (args.length == 0) {
                System.out.println("没有类啊");
            }
            // 取出第一个参数,就是需要运行的类
            String procressClass = args[0];
            // 剩余参数为运行目标类的参数,将这些参数复制到一个新数组中
            String[] procress = new String[args.length - 1];
            System.arraycopy(args, 1, procress, 0, procress.length);
            MyClassLoader myClassLoader = new MyClassLoader();
            Class<?> class1 = myClassLoader.loadClass(procressClass);
            Method main = class1.getMethod("main", (new 
            String[0]).getClass());
            Object argsArray[] = { procress };
            main.invoke(null, argsArray);
        }
    
        /**
         * @TODO 读取文件内容
         */
        public byte[] getBytes(String fileName) {
            File file = new File(fileName);
            long len = file.length();
            byte[] raw = new byte[(int) len];
            try {
                FileInputStream fileInputStream =
                 new FileInputStream(file);
                try {
                    int r = fileInputStream.read(raw);
                    fileInputStream.close();
                    if (r != len)
                        throw new IOException("fail to read
                         the file...");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return raw;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * @TODO 编译java文件
         */
        public boolean complie(String javaFile) {
            System.out.println("正在编译...");
            Process process = null;
            try {
                process = Runtime.getRuntime().exec("javac " + javaFile);
                try {
                    process.waitFor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            int result = process.exitValue();
            return result == 0;
        }
    
        /**
         * @TODO 关键,重写findClass方法
         */
        @Override
        protected Class<?> findClass(String arg0) throws ClassNotFoundException {
            Class<?> class1 = null;
            String filePath = arg0.replaceAll(".", "/");
            String className = filePath + ".class";
            String javaName = filePath + ".java";
            File javaFile = new File(javaName);
            File classFile = new File(className);
            if (javaFile.exists()
                    && (!classFile.exists() || javaFile.lastModified() > classFile .lastModified())) {
                if (!complie(javaName) || !classFile.exists()) {
                    throw new ClassNotFoundException(javaName + " Class找不到");
                }
            }
            if (classFile.exists()) {
                byte[] raw = getBytes(className);
                class1 = defineClass(arg0, raw, 0, raw.length);
            }
    
            if (class1 == null) {
                throw new ClassNotFoundException(javaName + " 加载失败");
            }
            return class1;
        }
     }
    
    17.HashMap和Hashtable的区别。

    答:HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的区别,主要的区别有:线程安全性,同步(synchronization)以及速度。

    1.HashMap是非synchronized的,并可以接收null,HashMap可以接受为null的键(key)和值(value),而Hashtable则不行。
    
    2.Hashtable是线程安全的,多个线程是可以共享一个Hashtable,而如果没有正确的同步的话,多个线程是不能共享HashMap的,java5提供了ConcurrentHashMap,它是HashTable的替代,扩展性更好。
    
    3.HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的,所以当有其他线程改变了HashMap的结构,将会抛出ConcurrentModificationException。
    
    4.由于Hashtable是线程安全的,所以在单线程环境下他比HashMap要慢,如果不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
    
    注意:
    1.synchronized意味着在一次仅有一个线程能够更改Hashtable,就是说任何线程要更新Hashtable时要首先获得同步锁,其他线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。
    
    2.使HashMap同步:
    Map m = Collections.synchronizeMap(hashMap);
    
    3.仅在需要线程安全的时候使用HashTable,使用java5或以上的话,使用ConcurrentHashMap。
    

    相关文章

      网友评论

          本文标题:JAVA题库(一)

          本文链接:https://www.haomeiwen.com/subject/nrvbvqtx.html