出场率比较高的一道多线程安全面试题

这个问题是 Java 程序员面试经常会遇到的吧。

工作一两年的应该都知道 ArrayList 是线程不安全的,要使用线程安全的就使用 Vector,这也是各种 Java 面试宝典里面所提及的,可能很多工作好几年的程序员都停留在这个知识面上。

先说说为什么 ArrayList 是线程不安全的吧,来看以下的代码。

/**
 * 微信公众号:Java技术栈
 */
public class TestArrayList {

    private static List<Integer> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            testList();
            list.clear();
        }
    }

    private static void testList() throws InterruptedException {
        Runnable runnable = () -> {
            for (int i = 0; i < 10000; i++) {
                list.add(i);
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        System.out.println(list.size());
    }

}

这是它的输出结果,我们期望的结果应该都是:30000,然后并不是,这就是传说中的多线程并发问题了。

Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 15786
    at java.base/java.util.ArrayList.add(ArrayList.java:468)
    at java.base/java.util.ArrayList.add(ArrayList.java:480)
    at com.test.thread.TestArrayList.lambda$testList$0(TestArrayList.java:23)
    at java.base/java.lang.Thread.run(Thread.java:844)
20332
16100
14941
23749
15631
22118
27417
30000
28691
27843
现象分析

从以上结果可以总结出 ArrayList 在并发情况下会出现的几种现象。

1、发生 ArrayIndexOutOfBoundsException 异常;

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

定位到异常所在源代码,毫无疑问,问题是出现在多线程并发访问下,由于没有同步锁的保护,造成了 ArrayList 扩容不一致的问题。

2、程序正常运行,输出了少于实际容量的大小;

这个也是多线程并发赋值时,对同一个数组索引位置进行了赋值,所以出现少于预期大小的情况。

3、程序正常运行,输出了预期容量的大小;

这是正常运行结果,未发生多线程安全问题,但这是不确定性的,不是每次都会达到正常预期的。

解决方案

既然这样,那么在高并发情况下,使用什么样的列表集合保护线程安全呢?回到文章最开始的地方,使用 Vector,还有别的吗?当然有,篇幅有限,请各位看官期待后续文章。

另外,像 HashMap, HashSet 等都有类似多线程安全问题,在多线程并发环境下避免使用这种集合。

转载请注明原文实际来源地址:原文地址


资料:成为架构师的十阶段学习资料!

教程:史上最强 Spring Boot & Cloud 教程汇总

工具:推荐一款在线创作流程图、思维导图软件

转载于:https://www.cnblogs.com/javastack/p/9303176.html

### Java 面试问题与答案 #### 封装、继承和多态的概念 封装是面向对象编程的重要特性之一,在Java中,通过将类的数据成员设置为私有来实现。这使得外部无法直接访问这些数据成员,从而保护了数据的安全性和完整性[^3]。 继承是指子类可以获取父类的方法和属性的能力。这种机制有助于代码重用并建立良好的层次结构。当一个类从另一个类派生时,它可以继承后者的所有公共方法和字段,并可以选择性地覆盖某些行为以适应特定需求。 多态意味着同一个实体能够表现出不同的形态或形式。具体来说,在Java里主要体现在两个方面:编译期(静态)多态——即方法重载;运行期(动态)多态——即方法覆写。前者是在调用同名但参数列表各异的方法时发生的,而后者则是指子类提供了某个已存在的超类方法的新版本,实际执行哪个取决于实例所属的具体类型[^4]。 ```java // 示例展示简单继承与多态现象 class Animal { public void sound() { System.out.println("Animal makes a sound"); } } class Dog extends Animal { @Override public void sound() { System.out.println("Dog barks"); } // 覆盖sound方法体现多态性 } ``` #### JDBC简介 JDBC (Java Database Connectivity) 是一组由Sun Microsystems定义的标准API接口集,旨在使Java应用程序能方便快捷地操作各种关系型数据库管理系统(RDBMS),完成诸如创建表单、插入记录以及查询等功能。借助DriverManager类加载合适的驱动程序后,开发者便可以通过Connection对象获得对目标数据库的连接权限,进而利用PreparedStatement或者Statement发送SQL语句给服务器端解释执行。 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; public class JdbcExample { private static final String URL = "jdbc:mysql://localhost:3306/test"; private static final String USER = "root"; private static final String PASSWORD = ""; public static void main(String[] args){ try{ Connection conn = DriverManager.getConnection(URL,USER,PASSWORD); PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users(name,email,password) VALUES(?,?,?)"); // 设置参数值 pstmt.setString(1,"John Doe"); pstmt.setString(2,"john@example.com"); pstmt.setString(3,"secret"); int affectedRows = pstmt.executeUpdate(); System.out.printf("%d row(s) inserted%n",affectedRows); // 关闭资源... }catch(Exception e){e.printStackTrace();} } } ``` #### 类加载过程概述 在Java虚拟机启动过程中,会先初始化引导类加载器,负责加载核心库文件如rt.jar内的基础组件。接着是由扩展类加载器接管工作,用来引入位于$JAVA_HOME/lib/ext目录下的额外包集合。最后便是应用级的系统类加载器出场,专门针对用户自定义的应用模块实施解析加载任务。每当遇到新的未见过的全限定名(class name with package prefix), JVM就会按照上述顺序依次尝试定位对应字节码文件(.class file).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值