Java知识点学习-1

本文介绍了Redis的五大数据类型及其应用场景,解释了Redis单线程但高效的原因,对比了Redis和Memcached的区别,详细说明了Redis的内存淘汰机制。同时,讨论了Java中的排序算法(如快速排序),并发编程的概念(如线程调度、原子性、可见性和有序性),以及类加载器、线程池的工作原理和死锁问题。此外,还涉及了网络协议、双亲委派机制等Java相关知识。
摘要由CSDN通过智能技术生成

1.redis的常见数据类型及应用场景

String类型:存储字符串类型的数据,如用户信息、商品信息等。
Hash类型:存储键值对类型的数据,如用户信息、商品信息等。
List类型:存储列表类型的数据,如消息队列、任务队列等。
Set类型:存储集合类型的数据,如点赞用户、浏览用户等。
Sorted Set类型:存储有序集合类型的数据,如排行榜、热门商品等。

2.redis是单线程为什么还这么快?

Redis是单线程的,但它采用了多路复用的机制,可以同时处理多个客户端请求,避免了线程上下文切换的开销。此外,Redis的数据都存储在内存中,读写速度非常快,也是Redis快速的原因之一。

3.redis和memcached的区别?

1. Redis支持更丰富的数据类型,如Hash、List、Set、Sorted Set等,而Memcached只支持简单的key-value类型。
2. Redis支持持久化,可以将数据存储到磁盘中,而Memcached不支持持久化。
3. Redis支持主从复制和哨兵机制,可以实现高可用性和数据备份,而Memcached没有这些功能。
4. Redis支持Lua脚本,可以实现复杂的业务逻辑,而Memcached不支持脚本。
5. Redis的性能比Memcached更好,尤其是在多核CPU和大数据量的情况下。

4.redis内存淘汰机制?

Redis内存淘汰机制是指当Redis的内存使用达到一定阈值时,需要淘汰一些数据来释放内存空间。Redis提供了6种内存淘汰策略,分别是noeviction、allkeys-lru、volatile-lru、allkeys-random、volatile-random和volatile-ttl。其中noeviction表示不淘汰数据,而其他5种策略则是根据不同的规则来淘汰数据,如最近最少使用(LRU)和随机淘汰等。可以通过配置文件或命令来设置内存淘汰策略。

5. Java中常见的排序算法有哪些?请简述快排的原理和实现。

常见的排序算法有冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序等。
快排的原理是:选择一个基准元素,将小于基准元素的放在左边,大于基准元素的放在右边,然后对左右两个子序列递归地进行快排,直到序列有序。
快排的实现:

/**
 * 快速排序
 * @param arr 待排序数组
 * @param left 左边界
 * @param right 右边界
 */
public static void quickSort(int[] arr, int left, int right) {
    if (left < right) {
        // 选取基准元素
        int i = left, j = right, pivot = arr[left];
        // 将小于基准元素的放在左边,大于基准元素的放在右边
        while (i < j) {
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            if (i < j) {
                arr[i++] = arr[j];
            }
            while (i < j && arr[i] < pivot) {
                i++;
            }
            if (i < j) {
                arr[j--] = arr[i];
            }
        }
        // 将基准元素放在正确的位置
        arr[i] = pivot;
        // 对左右两个子序列递归地进行快排,直到序列有序
        quickSort(arr, left, i - 1);
        quickSort(arr, i + 1, right);
    }
}

快速排序的原理是选择一个基准元素,将小于基准元素的放在左边,大于基准元素的放在右边,然后对左右两个子序列递归地进行快排,直到序列有序。这个实现中,基准元素是数组的第一个元素,通过双指针的方式将小于基准元素的放在左边,大于基准元素的放在右边,最后将基准元素放在正确的位置。

6.什么是Class文件?

Class文件是Java编译器编译Java源代码后生成的二进制文件,其中包含了Java程序的字节码和其他相关信息,可以被Java虚拟机(JVM)解释执行。

7.Class文件主要的信息结构是什么?

Class文件主要的信息结构包括:
1. 魔数和版本号
2. 常量池(字面量和符号引用)
3. 访问标志
4. 类索引、父类索引和接口索引集合
5. 字段表集合
6. 方法表集合
7. 属性表集合

8.并发编程三个必要因素是什么?

1. 多线程/多进程
2. 共享内存/消息传递
3. 同步机制

9.原子性、可见性和有序性是什么?

原子性、可见性和有序性是Java内存模型中的三个重要概念。
原子性指的是一个操作是不可中断的,要么全部执行成功,要么全部执行失败;
可见性指的是一个线程对共享变量的修改对其他线程是可见的;
有序性指的是程序执行的顺序按照代码的先后顺序执行,但是在不影响程序结果的前提下,JVM可能会对指令进行重排序。

10.有哪些类加载器?

Java中有以下几种类加载器:
1. Bootstrap ClassLoader:负责加载JVM自身需要的类,如java.lang包中的类。
2. Extension ClassLoader:负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的jar包。
3. AppClassLoader:负责加载应用程序classpath目录下的类,是最常用的类加载器。
4. User-defined ClassLoader:用户自定义的类加载器,可以根据需要实现自己的类加载器。

11.线程的调度策略?

Java中的线程调度策略有两种:时间片轮转和优先级调度。
时间片轮转是指每个线程被分配一个时间片,当时间片用完后,系统会重新分配时间片给其他线程。
优先级调度是指系统根据线程的优先级来决定哪个线程先执行,优先级高的线程先执行,优先级低的线程后执行。但是,优先级调度可能会导致低优先级的线程饥饿现象,即一直得不到执行机会。因此,Java中的线程调度策略一般采用时间片轮转和优先级调度相结合的方式,即每个线程被分配一个时间片,同时根据线程的优先级来决定哪个线程先执行。

12.Java写个冒泡排序

/**
 * 冒泡排序
 * @param arr 待排序数组
 */
public static void bubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

13.Java对象的布局?

Java对象的布局包括对象头和实例数据两部分。对象头包括Mark Word和Class Pointer,用于存储对象的元数据信息,如对象的锁状态、GC标记等;
实例数据包括对象的成员变量,用于存储对象的状态信息。对象头和实例数据的大小取决于对象的类型和JVM的实现。

14.什么是tomcat valve?

Tomcat Valve是Tomcat服务器中的一种过滤器,用于在请求到达Tomcat服务器之前或响应离开Tomcat服务器之后对请求和响应进行处理。Valve可以用于实现各种功能,如访问日志记录、安全认证、请求重定向等。

15.同步方法和同步块,哪个是更好的选择?

同步方法和同步块都可以用于实现线程安全,但是它们的使用场景不同。
同步方法适用于对整个方法进行加锁的情况,而同步块适用于对部分代码块进行加锁的情况。因此,选择哪种方式取决于具体的业务需求和代码实现。一般来说,如果需要对整个方法进行加锁,可以使用同步方法;如果只需要对部分代码块进行加锁,可以使用同步块。另外,同步方法的锁是对象锁,而同步块的锁可以是任意对象,因此同步块具有更大的灵活性。

16.运行时常量池的作用是什么?

运行时常量池是Java虚拟机在运行时动态生成的一块内存区域,用于存储类、接口、方法等的常量池信息。它的作用是为Java程序提供一种动态性的机制,可以在运行时动态地加载类、接口、方法等信息,从而实现Java程序的动态性和灵活性。同时,运行时常量池还可以用于存储字符串常量、数字常量等,以便在程序运行时快速访问和使用。

17.如果提交任务时,线程池队列已满,这时会发生什么?

1.如果使用的是无界队列LinkedBlockingQueue,当线程池队列已满时,新的任务会被加入到队列中,直到队列满为止。由于LinkedBlockingQueue是无界队列,因此队列永远不会满,新的任务会一直被加入到队列中,直到内存耗尽为止。因此,使用无界队列可以避免任务被拒绝执行的情况,但是需要注意内存的使用情况,避免内存泄漏等问题。
2.如果使用的是有界队列如ArrayBlockingQueue,当线程池队列已满时,新的任务会被加入到队列中,直到队列满为止,那么就会使用拒绝策略。拒绝策略是指当线程池无法处理新的任务时,采取的一种策略。常见的拒绝策略有四种:AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。其中,AbortPolicy是默认的拒绝策略,它会直接抛出RejectedExecutionException异常,表示拒绝执行新的任务。CallerRunsPolicy会将任务交给调用线程来执行,即由提交任务的线程来执行该任务。DiscardPolicy会直接丢弃新的任务,不做任何处理。DiscardOldestPolicy会丢弃队列中最旧的任务,然后将新的任务加入队列。

18.写一段简单的死锁代码

/**
 * 死锁代码示例
 * t1获取lock1的锁,t2获取lock2的锁,然后t1尝试获取lock2的锁,t2尝试获取lock1的锁,导致两个线程相互等待,形成死锁。
 */
public class DeadLockDemo {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) { // 获取lock1的锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) { // 获取lock2的锁
                    System.out.println("Thread 1");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock2) { // 获取lock2的锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) { // 获取lock1的锁
                    System.out.println("Thread 2");
                }
            }
        });

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

这段代码是一个简单的死锁示例,其中定义了两个对象lock1和lock2,并创建了两个线程t1和t2。在t1中,先获取lock1的锁,然后休眠1秒钟,再尝试获取lock2的锁;在t2中,先获取lock2的锁,然后休眠1秒钟,再尝试获取lock1的锁。由于t1和t2的获取锁顺序不同,当它们同时运行时,就可能会出现死锁的情况,即t1持有lock1的锁,等待获取lock2的锁,而t2持有lock2的锁,等待获取lock1的锁,两个线程相互等待,形成死锁。

为了避免死锁的发生,可以采取以下措施: 1.避免多个线程同时竞争多个锁; 2.尽量减少同步代码块的嵌套层数; 3.尽量避免在同步代码块中调用外部方法,以免产生不可预知的结果; 4.使用Lock对象代替synchronized关键字,可以更加灵活地控制锁的获取和释放。

19.栈帧有哪些数据?

栈帧包含以下数据:
1.局部变量表:用于存储方法执行过程中的局部变量;
2.操作数栈:用于存储方法执行过程中的操作数;
3.动态链接:用于指向运行时常量池中该方法的引用;
4.方法返回地址:用于存储方法返回时的返回地址;
5.附加信息:用于存储其他与Java虚拟机实现相关的信息。

20.BIO、NIO、AIO有什么区别?

BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
NIO (Non-Blocking I/O):同步非阻塞I/O模式,数据的读取写入可以在等待数据的过程中同时干其他的事情。
AIO (Asynchronous I/O):异步非阻塞I/O模式,数据的读取写入不需要等待其完成,可以通过回调函数的方式来获取I/O操作的结果。

21.什么是自旋?

自旋是一种线程等待的方式,它不会让线程进入阻塞状态,而是让线程执行一个忙循环,不断地检查某个条件是否满足,直到条件满足为止。在Java中,可以使用synchronized关键字或Lock对象来实现自旋锁。在获取锁的过程中,如果锁已经被其他线程占用,当前线程就会进入自旋状态,不断地尝试获取锁,直到获取成功为止。自旋锁的优点是可以减少线程上下文切换的开销,缺点是会占用CPU资源,如果自旋时间过长,会导致CPU利用率过高。因此,在使用自旋锁时,需要根据具体情况来选择自旋的次数和时间。 通常情况下,自旋等待的时间应该控制在几个微秒以内,避免对系统的性能造成影响。此外,自旋锁只适用于单核CPU或者多核CPU中访问同一共享内存的情况,对于分布式系统中的锁,自旋锁无法实现。

private static Object lock = new Object(); // 创建一个锁对象
private static boolean condition = false; // 创建一个条件变量

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        synchronized (lock) { // 获取锁对象
            System.out.println("Thread1 start");
            while (!condition) { // 自旋等待条件变量为true
                try {
                    lock.wait(); // 释放锁对象并等待
                    System.out.println("释放锁对象并等待");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    Thread t2 = new Thread(() -> {
        synchronized (lock) { // 获取锁对象
            System.out.println("Thread2 start");
            condition = true; // 设置条件变量为true
            lock.notify(); // 唤醒等待的线程
            System.out.println("唤醒等待的线程");
        }
    });

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

22.可以在hashcode()中使用随机数字吗?

在Java中,hashcode()方法用于获取对象的哈希码,它是一个int类型的整数。虽然在hashcode()方法中可以使用随机数字,但是建议不要这样做。
使用随机数字会导致相同的对象可能会生成不同的哈希码,这会破坏哈希表的性质,导致哈希表无法正确地工作。因此,建议在hashcode()方法中使用对象的状态来生成哈希码,这样可以保证相同的对象始终生成相同的哈希码。

23.网络协议有哪些?

网络协议有很多种,常见的有TCP/IP协议、HTTP协议、FTP协议、SMTP协议、POP3协议、IMAP协议、DNS协议、SSL/TLS协议等。其中,TCP/IP协议是互联网的基础协议,HTTP协议是Web应用程序的基础协议,FTP协议是文件传输协议,SMTP协议是邮件发送协议,POP3协议和IMAP协议是邮件接收协议,DNS协议是域名解析协议,SSL/TLS协议是安全传输协议。

24.什么是双亲委派机制?

双亲委派机制是指在类加载器加载类时,先让父类加载器尝试加载该类,只有在父类加载器无法加载该类的情况下,才由子类加载器来加载该类。这样可以保证类的唯一性,避免重复加载,同时也可以保证类的安全性,防止核心API被篡改。在Java中,系统默认提供了三个类加载器,分别是启动类加载器、扩展类加载器和应用程序类加载器,它们之间形成了一种层次关系,父类加载器优先于子类加载器加载类,这就是双亲委派机制。

25.什么情况下会出现内存溢出、内存泄漏?

内存溢出(Out Of Memory)是指程序在申请内存时,没有足够的内存空间供其使用,出现内存溢出的情况通常是由于程序中存在内存泄漏或者内存消耗过大的情况导致的。
内存泄漏(Memory Leak)是指程序在申请内存后,无法释放已申请的内存空间,导致系统的可用内存越来越少,最终会导致系统的崩溃或者死机。常见的内存泄漏情况包括未关闭的数据库连接、未关闭的IO流、静态集合类持有大量对象等。

26.内存溢出、内存泄漏的解决方案?

内存溢出和内存泄漏是常见的内存问题,对程序的稳定性和性能有着严重的影响。下面介绍一些常见的解决方案:
内存溢出
内存溢出是指在程序运行时,申请的内存超过了JVM可以分配的最大内存限制,导致程序崩溃。解决内存溢出的方法主要有以下几种:
1.调整JVM的内存限制:可以通过修改JVM启动参数来调整最大内存限制,如 -Xmx 参数用于设置最大堆内存大小。
2.优化代码:可以通过优化代码逻辑,减少内存的使用,如及时释放不需要的对象,避免创建过多的对象等。
3.使用内存分析工具:可以使用内存分析工具(如 Eclipse Memory Analyzer)来分析程序的内存使用情况,找出内存泄漏和内存占用过大的原因。
内存泄漏
内存泄漏是指程序中已经不再需要的对象仍然占用着内存,导致可用内存逐渐减少,最终导致程序崩溃。解决内存泄漏的方法主要有以下几种:
1.及时释放对象:在使用完对象后,要及时将其释放,以释放占用的内存。尤其是在使用大量对象的情况下,更要注意及时释放对象,避免内存占用过大。
2.避免使用静态变量:静态变量会一直存在于内存中,容易导致内存泄漏。因此,尽量避免使用静态变量,特别是在容易造成内存泄漏的地方。
3.使用弱引用:弱引用是指被引用的对象可以被垃圾回收器回收,避免出现内存泄漏。因此,在需要保存对象引用的地方,可以使用弱引用代替强引用。
4.使用内存分析工具:可以使用内存分析工具来分析程序的内存使用情况,找出内存泄漏和内存占用过大的原因,从而进行优化。

27.乐观锁和悲观锁的理解及实现?

乐观锁和悲观锁是并发编程中常用的两种锁机制。
悲观锁认为在并发情况下,数据很可能会被其他线程修改,因此在对数据进行操作时,会先加锁,保证同一时刻只有一个线程能够对数据进行操作。
乐观锁则认为在并发情况下,数据很可能不会被其他线程修改,因此在对数据进行操作时,不会加锁,而是在更新数据时判断数据是否被其他线程修改过,如果没有修改过,则更新成功,否则需要重新获取数据并再次尝试更新。

悲观锁的实现方式有很多种,常见的有 synchronized 关键字、ReentrantLock、ReadWriteLock 等。
以下是使用 synchronized 关键字实现悲观锁的示例代码:
public synchronized void updateData() {
    // 对数据进行操作
}

乐观锁的实现方式也有很多种,常见的有版本号机制、CAS(Compare And Swap)机制等。
以下是使用版本号机制实现乐观锁的示例代码:
public void updateData() {
    int version = getDataVersion();
    // 获取数据版本号
    // 对数据进行操作
    if (compareAndSetDataVersion(version, version + 1)) {
        // 更新数据版本号
    } else {
        // 版本号不匹配,需要重新获取数据并再次尝试更新
    }
}

28.什么是CAS机制?

CAS (Compare And Swap) 机制是一种乐观锁的实现方式,用于解决并发情况下的数据同步问题。
它通过比较内存中某个位置的值和预期值是否相等来确定是否进行更新操作,从而避免了加锁操作带来的性能开销和死锁等问题。
在 Java 中,CAS 机制主要通过 Atomic 相关类来实现,如 AtomicInteger、AtomicLong 等。

29.线程与进程的区别?

进程是操作系统资源分配的基本单位,线程是进程的执行单位。
一个进程可以包含多个线程,它们共享进程的内存空间和系统资源。
线程之间的切换比进程之间的切换更快,因为线程之间共享内存,上下文切换的开销较小。
进程之间的通信需要通过 IPC(Inter-Process Communication)机制,而线程之间可以通过共享内存等方式直接进行通信。
举个例子来说,我们可以将一个打印机服务作为一个进程,而每一个正在打印的任务可以作为一个线程。这样,不同的任务可以在同一个进程中运行,共享打印机资源和打印设置等信息,从而提高打印效率。同时,每个线程可以独立地处理自己的打印任务,不会干扰其他任务的执行,保证了打印服务的稳定性和安全性。

30.用代码演示三种代理

package com.tool.cn.controller;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 代理示例
 */
public class ProxyDemo {

    public static void main(String[] args) {
        //静态代理
        RealSubject realSubject = new RealSubject();
        RealProxy realProxy = new RealProxy(realSubject);
        realProxy.request();

        //动态代理
        RealSubject realSubject1 = new RealSubject();
        InvocationHandler handler = new DynamicProxy(realSubject1);
        Subject dynamicProxy = (Subject) Proxy.newProxyInstance(
                handler.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                handler);
        dynamicProxy.request();

        //CGLIB代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new CglibProxy(new RealSubject()));
        RealSubject proxy = (RealSubject) enhancer.create();
        proxy.request();
    }

}


interface Subject {
    void request();
}

class RealSubject implements Subject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 静态代理示例
class RealProxy implements Subject {
    private RealSubject realSubject;

    public RealProxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    public void request() {
        System.out.println("RealProxy: Logging before request.");
        realSubject.request();
        System.out.println("RealProxy: Logging after request.");
    }
}

// 动态代理示例
class DynamicProxy implements InvocationHandler {
    private Object subject;

    public DynamicProxy(Object subject) {
        this.subject = subject;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("DynamicProxy: Logging before request.");
        Object result = method.invoke(subject, args);
        System.out.println("DynamicProxy: Logging after request.");
        return result;
    }
}

//CGLIB代理示例
class CglibProxy implements MethodInterceptor {
    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CglibProxy: Logging before request.");
        Object result = method.invoke(target, args);
        System.out.println("CglibProxy: Logging after request.");
        return result;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值