【Java并发】变量的内存存储、线程安全分析

news2024/11/15 9:28:23

要理解原因,首先要清楚局部变量是什么?局部变量的存储方式是什么?

局部变量,从名字上就可以知道,它是只在特定作用域内可见并且只能在该作用域内使用的变量。也就意味着不同作用域的局部变量是不共享的。在多线程环境下,局部变量是存储在线程栈上的,每个线程都有自己独立的线程栈。线程之间不会共享这些变量,因此局部变量是线程安全的。但是也可能存在例外,比如引用类型(如指针、数组、对象等)的局部变量可能存在线程安全问题,因为多个线程可以同时访问同一个地址,导致数据竞争问题。

更官方的解释:
局部变量是什么:局部变量是指在程序中定义的只在特定作用域内可见并且只能在该作用域内使用的变量。这些变量通常被定义在函数、代码块或循环体等限定作用域的区域内,其生命周期仅包括了该区域的执行时间段。当程序退出该作用域时,局部变量就会被销毁释放,不再占用内存空间。局部变量通常用于暂时保存和处理临时数据,有助于提高代码的可读性和安全性。

java文件经过编译器编译生成class文件 class文件进入jvm,由各种类加载器加载 加载完毕后交给jvm执行引擎执行
jvm内存模型就是运行时数据区,程序运行时用到的数据以及相关信息保存区

为什么需要考虑线程安全问题?在多线程环境下,多个线程可以同时访问共享的变量或资源。考虑线程安全问题可以避免数据竞争、死锁等问题的出现,保证程序的正确性和稳定性。

JAVA的内存模型:
在这里插入图片描述
局部变量的存储方式
线程栈是指每个线程在运行时所使用的一块内存区域,用于保存线程的局部变量、函数参数、返回值以及程序计数器等信息。每当一个线程被创建时,系统会为该线程分配一段内存作为线程栈,每个线程有自己独立的线程栈空间。线程栈具有“先进后出”的特点,也就是说,最后进入栈中的数据最先被取出来。
在这里插入图片描述

局部变量存储在线程栈上局部变量是在函数内定义的变量,其存储空间分配在函数调用时所创建的栈帧中,也就是存储在线程栈上。每个线程都有自己的线程栈,它用于存储函数的参数、局部变量和返回地址等信息。当函数被调用时,该函数的参数和局部变量将被压入栈中,在函数返回时再从栈中弹出,这样就可以保证多个函数之间的数据不会相互干扰。

线程栈的大小通常是固定的,因为它必须在编译时确定。如果在运行时需要更多的栈空间,则必须使用递归或动态分配内存来实现。由于局部变量存储在线程栈上,所以它们的生命周期与函数的执行时间相同。当函数返回时,它们的值将被销毁,并且该空间将被释放,以便其他函数可以使用该空间。

1.成员变量和静态变量是否线程安全?

  • 如果他们没有共享,则线程安全
  • 如果被共享:
    - 只有读操作,则线程安全
    - 有写操作,则这段代码是临界区,需要考虑线程安全

2.局部变量是否线程安全

  • 局部变量是线程安全的
  • 当局部变量引用的对象则未必
    - 如果给i对象没有逃离方法的作用访问,则是线程安全的
    - 如果该对象逃离方法的作用范围,需要考虑线程安全

3.局部变量的线程安全分析

public static void test1() {
 int i = 10;
 i++;
}

每个线程调用该方法时局部变量i,会在每个线程的栈帧内存中被创建多分,因此不存在共享
在这里插入图片描述
当局部变量的引用有所不同

先来看一个成员变量的里例子:

public class ThreadUnsafeDemo {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
}

class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            // 临界区,会产生竞态条件
            method2();
            method3();
        }
    }

    private void method2() {
        list.add("1");
    }

    private void method3() {
        list.remove(0);
    }

}

可能会发生一种情况:线程1和线程2都去执行method2,但是由于并发执行导致最后只有一个元素添加成功,当执行了两次移除操作,所以就会报错。

Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.remove(ArrayList.java:498)
	at org.example.juc.ThreadUnsafe.method3(ThreadUnsafeDemo.java:39)
	at org.example.juc.ThreadUnsafe.method1(ThreadUnsafeDemo.java:30)
	at org.example.juc.ThreadUnsafeDemo.lambda$main$0(ThreadUnsafeDemo.java:17)
	at java.lang.Thread.run(Thread.java:750)

进程已结束,退出代码0

分析:

  • 无论哪个线程中的 method2 引用的都是同一个对象中的list成员变量
  • method2 和 method3 分析相同

在这里插入图片描述
但如果将list修改为局部变量,就不会有上述的问题了。

class Threadsafe {

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            ArrayList<String> list = new ArrayList<>();
            // 临界区,会产生竞态条件
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
        list.remove(0);
    }

}

分析:

  • list 是局部变量,每个线程调用时会创建其不同实例,没有共享
  • 而 method2 的参数是从 method1 中传递过来的,与 method1 中引用通过一个对象
  • menthod3 的参数分析与 method2 相同

在这里插入图片描述
方法访问修饰符带来的思考,如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题?

  • 情况1:有其他线程调用 mthod2 和 method3
  • 情况2:在情况1的基础上,为 ThreadSafe 类添加子类,子类覆盖为 method2 或 method3 方法

我们先来看情况1,这两个方法的访问修饰符修改为public,其他线程就可以调用了,但是它们不能调用 method1,所以 method1里的局部变量list是安全的,其他线程要调用 method2 的话只能使用自己创建新的list变量。

我们再来看情况2,访问修饰符修改为 public ,也就意味着子类可以去覆盖重写 method2 和 method3 方法,即

class ThreadUnsafe {

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            ArrayList<String> list = new ArrayList<>();
            // 临界区,会产生竞态条件
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
        list.add("1");
    }

    public void method3(ArrayList<String> list) {
        list.remove(0);
    }

}

class ThreadSafeSubClass extends ThreadUnsafe {
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

我们重写方法中,开启了一个新的线程,这个线程就能够去操作method1方法中的局部变量 list,此时 list就变成共享变量了,会有多个线程去修改它,也就产生了线程不安全的问题。也就是我们前面提到的局部变量的引用逃离了方法的作用范围(有其他线程去使用)就可能会产生安全问题。

4.常见线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

这里说的线程安全是指,多个线程调用他们同一个实例的方法时,时线程安全的,也可以理解为:

Hashtable table = new Hashtable();
new Thread(()->{
 table.put("key", "value1");
}).start();
new Thread(()->{
 table.put("key", "value2");
}).start();

他们的每个方法是原子的,但它们多个方法的组合不是原子的,比如:

Hashtable table = new Hashtable();
// 线程1
if( table.get("key") == null) {
 table.put("key", "t1");
}
// 线程2
if( table.get("key") == null) {
 table.put("key", "t2");
}

这里也就是检查和上锁不同步导致的线程不安全。

不可变线程安全性

String、Integer 等都是不可变类,因为其内部的状态不可以改变,因为他们的方法都是线程安全的。

有同学或许有疑问,String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安全的呢?

5.深入刨析String类为什么不可变?
什么是不可变?

String s = "aaa";
s = "bbb";

我们现在有一个字符串 s = “aaa”,如果我把它第二次赋值 s = “bbb”,这个操作并不会在原内存地址上修改数据,也就是不会吧 “aaa” 的那块地址里的数据修改为"bbb",而是重新指向了一个新的 内存地址,即”bbb"的内存地址,所以说 String 类是不可变的,一旦创建不可被修改的。

String 类里的replace方法

我们可以看到就是创建了一个新的String对象。

 public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

不可变的本质

我们看String类的源码就可以发现
在这里插入图片描述

1.String 类是一个 final 类
String类由final修饰,我们都知道当final修饰一个类时,该类不可以被其他类继承,自然String类就没有子类,也更没有方法被子类重写的说法了,所以这就保证了外界无法通过继承String类,来实现对String不可变性的破坏。

2.String底层是通过一个char[]来存储数据的,且该char[]由private final修饰。

该value数组被final修饰,我们知道被final修饰的引用类型的变量就不能再指向其他对象了,也就是说value数组只能指向堆中属于自己的那一个数组,不可以再指向其他数组了。但是我们可以改变它指向的这个数组里面的内容啊,比如咱们随便举个例子:

public class StringDemo {
    public static void main(String[] args) {
        final char[] c = {'a', 'b', 'c'};
        c[0] = 'd';
        System.out.println(Arrays.toString(c));
    }
}

其实不然,我们虽然可以修改一个对象的内容,但是我们根本无法修改String类里的数据,因为 String 类里的 value 数组是私有的,也没有对外修改的public方法,所以根本就没有可以修改的机会。

保证String类不可变靠的就是以下三点:

  • String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变性。

  • 保存字符串的value数组被 final 修饰且为私有的。

  • String 类里没有提供或暴露修改这个value数组的方法。

6.实例分析
我们来看几个例子,检验一下我们学的怎么样吧

线程安不安全,看这几个方便:

  • 是否是共享变量
  • 是否存在多个线程并发
  • 是否有写操作

例1

public class MyServlet extends HttpServlet {
 // 是否安全?
 Map<String,Object> map = new HashMap<>();
 // 是否安全?
 String S1 = "...";
 // 是否安全?
 final String S2 = "...";
 // 是否安全?
 Date D1 = new Date();
 // 是否安全?
 final Date D2 = new Date();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
     // 使用上述变量
 }
}

他们都是成员变量

  • map:HashMap是线程不安全的类,所以不安全
  • S1 :可以修改其对象的引用地址,线程不安全
  • S2 :被final修饰,所以不能修改它的引用地址,也不可能修改它的值
  • D1 :Date()是线程不安全的类
  • D2:虽然被final修饰,但可以修改它里面的值

例2

public class MyServlet extends HttpServlet {
 // 是否安全?
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 记录调用次数
 private int count = 0;
 
 public void update() {
 // ...
 count++;
 }
}

userService:成员变量,不安全,有多个线程会修改它的count变量

例三

@Aspect
@Component
public class MyAspect {
 // 是否安全?
 private long start = 0L;
 
 @Before("execution(* *(..))")
 public void before() {
 start = System.nanoTime();
 }
 
 @After("execution(* *(..))")
 public void after() {
 long end = System.nanoTime();
 System.out.println("cost time:" + (end-start));
 }
}

MyAspect没有指定是单例对象还是多例对象,Spring默认是单例。所以多个线程都共享一个MyAspect

  • start:成员变量,线程不安全

例四

public class MyServlet extends HttpServlet {
 // 是否安全
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 是否安全
 private UserDao userDao = new UserDaoImpl();
 
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao { 
 public void update() {
 String sql = "update user set password = ? where username = ?";
 // 是否安全
 try (Connection conn = DriverManager.getConnection("","","")){
 // ...
 } catch (Exception e) {
 // ...
 }
 }
}

UserDaoImpl中的update方法中的 conn 是局部变量,并且没有逃离方法的作用范围,所以 conn是线程安全的,UserServiceImpl 中的 UserDao是成员变量,但是userDao它调用的方法是线程安全的,所以userDao也是线程安全的,同理,userService也是线程安全的。

例5

public class MyServlet extends HttpServlet {
 // 是否安全
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 是否安全
 private UserDao userDao = new UserDaoImpl();
 
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao { 
 public void update() {
 // 是否安全
 private Connection conn = null;
 public void update() throws SQLException {
 String sql = "update user set password = ? where username = ?";
 conn = DriverManager.getConnection("","","");
 // ...
 conn.close();
 }
}

conn是成员变量,多个线程用的是同一个conn,所以是线程不安全的,同时 userDao 也是线程不安全的,userService也是线程不安全的。

例6

public class MyServlet extends HttpServlet {
 // 是否安全
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService { 
 public void update() {
 UserDao userDao = new UserDaoImpl();
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao {
 // 是否安全
 private Connection = null;
 public void update() throws SQLException {
 String sql = "update user set password = ? where username = ?";
 conn = DriverManager.getConnection("","","");
 // ...
 conn.close();
 }
}

UserServiceImpl中不在用的是成员变量而是局部变量,所以 conn 虽然是局部变量但是不被多个线程之间共享,所以conn是线程安全的,所以userDao也是线程安全的,userService也是线程安全的。

例7

public abstract class Test {

    public void bar() {
        // 是否安全
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        foo(sdf);
    }

    public abstract foo(SimpleDateFormat sdf);


    public static void main(String[] args) {
        new Test().bar();
    }
}

foo 方法是抽象方法,所以它的行为是不确定的,可能导致不安全的方法,被称之为外星方法

public void foo(SimpleDateFormat sdf) {
    String dateStr = "1999-10-11 00:00:00";
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            try {
                sdf.parse(dateStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

例8

private static Integer i = 0;
public static void main(String[] args) throws InterruptedException {
    List<Thread> list = new ArrayList<>();
    for (int j = 0; j < 2; j++) {
        Thread thread = new Thread(() -> {
            for (int k = 0; k < 5000; k++) {
                synchronized (i) {
                    i++;
                }
            }
        }, "" + j);
        list.add(thread);
    }
    list.stream().forEach(t -> t.start());
    list.stream().forEach(t -> {
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    log.debug("{}", i);
}

这里虽然i是静态变量,但是又synchronized给修改i的代码块上了锁,所以是线程安全的。

参考链接:
https://www.zhihu.com/question/601406551/answer/3033562577 、https://blog.csdn.net/weixin_53029342/article/details/128903824
https://blog.csdn.net/weixin_29091105/article/details/114717003

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2076808.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apache Tomcat与反向代理

Apache Tomcat 是一个开源的 Java Servlet 容器&#xff0c;主要用于部署和运行基于 Java 的 Web 应用程序。Tomcat 提供了一个环境&#xff0c;让开发者能够使用 Java 编写的 Web 应用程序在 Web 服务器上运行。下面是对 Tomcat 的详细介绍&#xff1a; Tomcat 的历史 Tomca…

C++学习笔记——三角形面积

一、题目描述 二、代码 #include <iostream> #include <bits/stdc.h> using namespace std; int main() {double a0,b0,c0;cin >> a >> b >> c;double p(abc)/2;double dp * (p-a)* (p-b)* (p-c);cout<< fixed << setprecision(3)&l…

QT 简易网页信息抓取程序模板基础代码

有些网页爬不了&#xff0c;只是一个简单的代码。 项目结构 NetBugBaseCode.pro #------------------------------------------------- # # Project created by QtCreator 2024-08-26T15:13:10 # #-------------------------------------------------QT core gui netw…

抖音小红书爆款预定,Tiktok爆火的短视频玩法,Ai生成宝宝走秀视频,萌翻全场

大家好&#xff0c;我是方知有&#xff0c;每天分享一个互联网副业&#xff0c;喜欢的朋友可以关注~ 今天给大家分享在Tiktok爆火的短视频玩法&#xff0c;现在抖音小红书制作这类型视频的人数还不多&#xff0c;大家可以赶快操作起来&#xff0c;这个玩法就是用Ai生成宝宝走秀…

论坛测试报告1.0

版本号&#xff1a; 作者&#xff1a; 日期&#xff1a; 目录 1 引言1.1 项目概述1.2 文档概述1.2.1 编写目的1.2.2 读者对象 1.3 产品需求和设计文档 2 测试执行2.1测试工具2.2制定测试计划2.3设计测试用例2.4执行测试用例 3.测试结果4.遗留风险5.测试结果评估 1 引言 1.1 项…

【kubernetes】Pod生命周期-启动钩子、停止钩子

一&#xff0c;Pod的生命周期 pod从开始创建到终止退出的时间范围称为Pod生命周期。pod生命周期包含以下几个重要流程&#xff1a; 初始化容器&#xff08;initContainers&#xff09;。 一个pod可以拥有任意数量的init容器。init容器是按照顺序以此执行的&#xff0c;并且仅…

HAL库:中断 方式按键检测:抬起执行、按下执行、长按短按检测、延时执行

目录 HAL库&#xff1a;中断 方式按键检测&#xff1a;抬起执行、按下执行、长按短按检测、延时执行 注意事项&#xff1a; 初始化部分&#xff1a; 回调函数部分 HAL库&#xff1a;中断 方式按键检测&#xff1a;抬起执行、按下执行、长按短按检测、延时执行 注意事项&am…

如何在CMD/PowerShell中使用命令行管理IIS

使用通用IIS命令行管理工具&#xff1a; C:\Windows\System32\inetsrv\appcmd.exe 查看现有站点 1 appcmd list site 添加一个新站点 1 appcmd add site /name:"My New Site" /id:2 /bindings:http/*:81: /physicalPath:"c:\inetpub\mynewsite" /name …

559. N 叉树的最大深度(迭代法)

目录 一&#xff1a;题目&#xff1a; 二&#xff1a;代码&#xff1a; 三&#xff1a;结果&#xff1a; 一&#xff1a;题目&#xff1a; 给定一个 N 叉树&#xff0c;找到其最大深度。 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 N 叉树输入按层序遍历…

学习系列三:V8目标检测与分割自动化标注

学习系列三&#xff1a;YOLOv8目标检测与分割自动化标注 提示&#xff1a;本次文章主要介绍yolov8目标检测与自动化标注(较简单&#xff0c;通用性比较强&#xff0c;标签格式txt)&#xff0c;yolov8实例分割与自动化标注(程序较复杂&#xff0c;自动化标注效果有待提升,标签格…

SE11 没有激活的名称表存 No active nametab exists for

背景&#xff1a; SE11中减少某个非空表的字段的长度后&#xff0c;在SE14中的操作不当&#xff0c;并且对该表进行了删除重建的操作后&#xff0c;发生SE11激活该表报错。 原因&#xff1a; 出现了一些未知原因&#xff0c;导致该表在底层数据库存在&#xff0c;但是运行时对…

柔版印刷版市场前景:预计2030年全球市场规模将达到20.9亿美元

一、当前市场状况 目前&#xff0c;柔版印刷版市场呈现出较为稳定的发展态势。随着全球经济的逐步复苏&#xff0c;包装印刷等领域对柔版印刷版的需求持续增长。柔版印刷版具有环保、高效、印刷质量高等特点&#xff0c;在食品包装、标签印刷等行业中得到广泛应用。 全球前四…

微信聊天记录删除怎么恢复?两个抱藏方法快速恢复数据

微信作为我们日常沟通的重要工具&#xff0c;存储了大量的聊天记录&#xff0c;这些记录往往承载着珍贵的记忆或重要的工作信息。我们在日常使用微信时不小心左滑删除了和领导的聊天记录&#xff01;里面还有很重要的工作内容&#xff0c;删除的微信聊天记录怎么恢复啊&#xf…

环境问题处理:Python写工具,转换excel内容合并到xml中(openpyxllxml)

问题描述 提示报错&#xff0c;但是没有像java代码的解决方案推荐。 Note&#xff1a;PycharmProjects\项目名\venv\Scripts 创建项目时自带的脚本&#xff0c;也包含python.exe 查看python文件有输出路径&#xff0c;使用python也能打开python3.8&#xff0c;但是无法查找pyt…

Java学习_22_网络编程

文章目录 前言网络编程网络编程三要素IPInetAddress 端口号协议OSI七层模型TCP/IP模型UDP协议TCP协议 综合练习多发多收接收和反馈上传文件上传文件重名问题上传文件多线程问题上传文件线程池问题BS架构 总结 前言 博客仅记录个人学习进度和一些查缺补漏。 学习内容&#xff1…

软考:一个月拿下软件设计师

前言 软考我满打满算也就准备了一个月不到&#xff0c;期间也走了不少弯路&#xff1b;特地做个博客记录一下&#xff0c;也给其它备考的人一些建议。 我是24年上半年参加的考试&#xff0c;说实在的这年下午题有几道不好写。 只要上午成绩和下午成绩都过45就算及格了。 正文…

【Git保姆级使用教程】Git从入门到精通超级详细的使用教程,一套教程带你搞定Git(高见龙版本)。

目录 Git下载与安装设置GitGit的用户设置 使用Git新增初始Repository将文件交给Git管控(将文件加入暂存区)查看Git文件记录查找commit记录更改提交commit的记录撤销提交commit 将某些文件取消Git版控Git中删除或更改文件名Git查看某个文件的某一行代码是谁写的.git目录中有什么…

Tower for Mac Git客户端管理软件

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试1、打开软件&#xff0c;测试2、克隆项目&#xff0c;测试 安装完成&#xf…

港股震荡中保持乐观,市场信心回来了!

港股上午盘三大指数集体上涨&#xff0c;恒生科技指数一度冲高至1.54%&#xff0c;最终收涨0.98%&#xff0c;恒生指数上涨1.06%。盘面上&#xff0c;大型科技股多数维持上涨行情&#xff0c;百度、腾讯涨超1.5%&#xff0c;快手、美团小幅上涨&#xff0c;阿里巴巴、京东飘绿&…

Java:Date类

文章目录 Date类常用方法代码演示 SimpleDateFormat常用方法代码演示时间格式常见符号SimpleDateFormat解析字符串为Date对象代码总结 案例&#xff1a;秒杀活动&#xff1a; 黑马学习笔记 这些方法在API文档都能查到 Date类 常用方法 代码演示 package Time;import java.uti…