[Java]JDBC学习笔记(尚硅谷康师傅JDBC) (三)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: [Java]JDBC学习笔记(尚硅谷康师傅JDBC)(三)

🌊 使用 addBatch() / executeBatch() / clearBatch()

mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。?rewriteBatchedStatements=true 写在配置文件的url后面

user=root
password=123123
url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver
@Test
    public void testInsert1() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into goods(name) values(?);";
        PreparedStatement ps = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for (int i=0; i<20000; i++) {
            ps.setObject(1, "name_" + i);
            // ps.execute();
            // 攒sql
            ps.addBatch();
            if (i % 500 == 0) {
                // 执行Batch
                ps.executeBatch();
                // 清空Batch
                ps.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        JDBCUtils.closeResource(connection, ps);
    }

更换jar包(新版支持批量插入)

🌊 修改事务提交的时机

@Test
    public void testInsert1() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        // 获取连接时取消事务的自动提交
        connection.setAutoCommit(false);
        String sql = "insert into goods(name) values(?);";
        PreparedStatement ps = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for (int i=0; i<20000; i++) {
            ps.setObject(1, "name_" + i);
            // ps.execute();
            // 攒sql
            ps.addBatch();
            if (i % 500 == 0) {
                // 执行Batch
                ps.executeBatch();
                // 清空Batch
                ps.clearBatch();
            }
        }
        // 统一提交数据
        connection.commit();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        JDBCUtils.closeResource(connection, ps);
    }

🥽 数据库事务

🌊 数据库事务介绍

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
  • 一组逻辑操作单元:一行或多行DML操作。
  • 事务处理(事务操作)的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
  • 数据一旦提交,就不可回滚。
  • 哪些操作会导致数据的自动提交?
  • DDL操作一旦执行,都会自动提交。set autocommit = false的方式对DDL操作失效。
  • DML操作默认情况下,一旦执行,就会自动提交。可以通过set autocommit = false的方式取消DML操作自动提交
  • 默认在关闭连接时,会自动的提交数据。

🌊 考虑事务后的转账操作实现

public class TransactionTest {
    @Test
    public void test() {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            // 取消事务的自动提交
            connection.setAutoCommit(false);
            String sql1 = "update user_table set balance=balance-100 where user=?";
            update(connection, sql2, "AA");
            String sql2 = "update user_table set balance=balance+100 where user=?";
            update(connection, sql1, "BB");
            System.out.println("转账成功");
            // 提交事务
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            JDBCUtils.closeResource(connection, null);
        }
    }
    // 通用的增删改操作
    public int update(Connection connection, String sql, Object ...args) {
        PreparedStatement ps = null;
        try {
            ps = connection.prepareStatement(sql);
            // 填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            // 执行sql
            return ps.executeUpdate();
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
          // 修改为自动提交事务
          // 恢复自动提交状态
            try {
                connection.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            // 关闭资源
            JDBCUtils.closeResource(null, ps);
        }
        return 0;
    }
}

try {
            connection = JDBCUtils.getConnection();
            // 取消事务的自动提交
            connection.setAutoCommit(false);
            String sql1 = "update user_table set balance=balance-100 where user=?";
            update(connection, sql1, "AA");
            System.out.println(10 / 0);
            String sql2 = "update user_table set balance=balance+100 where user=?";
            update(connection, sql2, "BB");
            System.out.println("转账成功");
            // 提交事务
            connection.commit();
        }

若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

🌊 事务的ACID属性

  1. 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
  3. 隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

🌊 数据库的并发问题

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
  • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的 内容就是临时且无效的。
  • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段并提交了操作。之后, T1再次读取同一个字段, 值就不同了。
  • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

🌊 四种隔离级别

  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
  • 数据库提供的4种事务隔离级别:
  • Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED
  • Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ

🥽 DAO及相关实现类

🌊 BaseDAO

package DAO;
import utils.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
 * 封装了针对于数据表的通用的操作
 */
public class BaseDAO {
    /**
     * 通用的增删改操作
     * @param connection 数据库连接对象
     * @param sql sql语句
     * @param args sql语句填充参数
     * @return 返回本次增删改操作对数据库表影响的行数
     */
    public int update(Connection connection, String sql, Object ...args) {
        PreparedStatement ps = null;
        try {
            ps = connection.prepareStatement(sql);
            // 填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1, args[i]);
            }
            // 执行sql
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            JDBCUtils.closeResource(null, ps);
        }
        return 0;
    }
    /**
     * 通用的查询操作
     * @param conn 数据库连接对象
     * @param clazz 查询数据对应的类
     * @param sql sql语句
     * @param args sql语句填充参数
     * @return 返回查询的结果
     * @param <T> 泛型
     */
    public <T> List<T> getForList(Connection conn, Class<T> clazz, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            // 获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            // 通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();
            // 创建集合对象
            ArrayList<T> list = new ArrayList<T>();
            while (rs.next()) {
                T t = clazz.newInstance();
                // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
                for (int i = 0; i < columnCount; i++) {
                    // 获取列值
                    Object columValue = rs.getObject(i + 1);
                    // 获取每个列的列名
                    // String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    // 给t对象指定的columnName属性,赋值为columValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, ps, rs);
        }
        return null;
    }
    /**
     * 用于查询特殊值的通用的方法
     * @param conn 数据库连接对象
     * @param sql sql语句
     * @param args sql语句填充参数
     * @return 返回查询的结果
     * @param <E> 泛型
     */
    public <E> E getValue(Connection conn,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for(int i = 0;i < args.length;i++){
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            if(rs.next()){
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(null, ps, rs);
        }
        return null;
    }
}

🌊 BaseDAO(泛型+反射)

public abstract class BaseDAO<T> {
  private Class<T> clazz = null;
//  public BaseDAO(){
//    
//  }
  { 
    //获取当前BaseDAO的子类继承的父类中的泛型
    Type genericSuperclass = this.getClass().getGenericSuperclass();
    ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数
    clazz = (Class<T>) typeArguments[0];//泛型的第一个参数
  }
  // 通用的增删改操作---version 2.0 (考虑上事务)
  public int update(Connection conn, String sql, Object... args) {// sql中占位符的个数与可变形参的长度相同!
    PreparedStatement ps = null;
    try {
      // 1.预编译sql语句,返回PreparedStatement的实例
      ps = conn.prepareStatement(sql);
      // 2.填充占位符
      for (int i = 0; i < args.length; i++) {
        ps.setObject(i + 1, args[i]);// 小心参数声明错误!!
      }
      // 3.执行
      return ps.executeUpdate();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 4.资源的关闭
      JDBCUtils.closeResource(null, ps);
    }
    return 0;
  }
  // 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
  public T getInstance(Connection conn, String sql, Object... args) {
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      ps = conn.prepareStatement(sql);
      for (int i = 0; i < args.length; i++) {
        ps.setObject(i + 1, args[i]);
      }
      rs = ps.executeQuery();
      // 获取结果集的元数据 :ResultSetMetaData
      ResultSetMetaData rsmd = rs.getMetaData();
      // 通过ResultSetMetaData获取结果集中的列数
      int columnCount = rsmd.getColumnCount();
      if (rs.next()) {
        T t = clazz.newInstance();
        // 处理结果集一行数据中的每一个列
        for (int i = 0; i < columnCount; i++) {
          // 获取列值
          Object columValue = rs.getObject(i + 1);
          // 获取每个列的列名
          // String columnName = rsmd.getColumnName(i + 1);
          String columnLabel = rsmd.getColumnLabel(i + 1);
          // 给t对象指定的columnName属性,赋值为columValue:通过反射
          Field field = clazz.getDeclaredField(columnLabel);
          field.setAccessible(true);
          field.set(t, columValue);
        }
        return t;
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      JDBCUtils.closeResource(null, ps, rs);
    }
    return null;
  }
  // 通用的查询操作,用于返回数据表中的多条记录构成的集合(version 2.0:考虑上事务)
  public List<T> getForList(Connection conn, String sql, Object... args) {
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      ps = conn.prepareStatement(sql);
      for (int i = 0; i < args.length; i++) {
        ps.setObject(i + 1, args[i]);
      }
      rs = ps.executeQuery();
      // 获取结果集的元数据 :ResultSetMetaData
      ResultSetMetaData rsmd = rs.getMetaData();
      // 通过ResultSetMetaData获取结果集中的列数
      int columnCount = rsmd.getColumnCount();
      // 创建集合对象
      ArrayList<T> list = new ArrayList<T>();
      while (rs.next()) {
        T t = clazz.newInstance();
        // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
        for (int i = 0; i < columnCount; i++) {
          // 获取列值
          Object columValue = rs.getObject(i + 1);
          // 获取每个列的列名
          // String columnName = rsmd.getColumnName(i + 1);
          String columnLabel = rsmd.getColumnLabel(i + 1);
          // 给t对象指定的columnName属性,赋值为columValue:通过反射
          Field field = clazz.getDeclaredField(columnLabel);
          field.setAccessible(true);
          field.set(t, columValue);
        }
        list.add(t);
      }
      return list;
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      JDBCUtils.closeResource(null, ps, rs);
    }
    return null;
  }
  //用于查询特殊值的通用的方法
  public <E> E getValue(Connection conn,String sql,Object...args){
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      ps = conn.prepareStatement(sql);
      for(int i = 0;i < args.length;i++){
        ps.setObject(i + 1, args[i]);
      }
      rs = ps.executeQuery();
      if(rs.next()){
        return (E) rs.getObject(1);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }finally{
      JDBCUtils.closeResource(null, ps, rs);
    }
    return null;
  } 
}

🥽 数据库连接池

🌊 数据库连接池简介

  • 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
  • 在主程序(如servlet、beans)中建立数据库连接
  • 进行sql操作
  • 断开数据库连接
  • 这种模式开发,存在的问题:
  • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
  • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(Java的内存泄漏,存在对象不能被回收)
  • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

  • 数据库连接池技术的优点
  1. 资源重用
    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
  2. 更快的系统反应速度
    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
  3. 新的资源分配手段
    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
  4. 统一的连接管理,避免数据库连接泄漏
    在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
  • 多种开源的数据库连接池
  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
  • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  • BoneCP 是一个开源组织提供的数据库连接池,速度快
  • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
  • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
从Java环境的搭建到实际代码的编写,从基本用法的讲解到底层原理的剖析,深度解析Java基础知识。本文是《Java学习路线》专栏的起始文章,旨在提供一套完整的Java学习路线,覆盖Java基础知识、数据库、SSM/SpringBoot等框架、Redis/MQ等中间件、设计模式、架构设计、性能调优、源码解读、核心面试题等全面的知识点,并在未来不断更新和完善,帮助Java从业者在更短的时间内成长为高级开发。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
|
2月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
本文是Java基础的进阶篇,对异常、集合、泛型、Java8新特性、I/O流等知识进行深入浅出的介绍,并附有对应的代码示例,重要的地方带有对性能、底层原理、源码的剖析。适合Java初学者。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
|
1月前
|
Java 数据安全/隐私保护
java学习笔记(基础习题)
java学习笔记(基础习题)
32 0
|
1月前
|
Java 程序员 开发工具
java学习笔记
java学习笔记
34 0
|
1月前
|
SQL Java 数据库连接
如何在 Java 脚本中有效地使用 JDBC
如何在 Java 脚本中有效地使用 JDBC
16 0
|
2月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)
本文是“Java学习路线”中Java基础知识的高级篇,主要对多线程和反射进行了深入浅出的介绍,在多线程部分,详细介绍了线程的概念、生命周期、多线程的线程安全、线程通信、线程同步,并对synchronized和Lock锁;反射部分对反射的特性、功能、优缺点、适用场景等进行了介绍。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(高级篇)
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
15天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
2天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
16 9
|
6天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####