• [技术干货] shiro的active Directory认证
    Apache Shiro 提供了对 Active Directory (AD) 的直接支持,通过 ActiveDirectoryRealm 可以轻松集成企业级 AD 环境,实现集中式身份认证。以下是 Shiro 中实现 AD 认证的完整指南,包括配置、代码示例和常见问题解决方案。1. Active Directory 认证基础作用:通过 AD 服务器验证用户凭据(用户名/密码),并获取用户角色和权限。优势:直接利用企业现有的 AD 基础设施,无需单独维护用户数据库。支持组权限管理(通过 AD 的组策略)。适用于 Windows 域环境下的单点登录(SSO)。2. Shiro AD 认证实现步骤(1) 添加依赖确保项目中包含 Shiro 和 AD 相关依赖(Maven 示例):<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.12.0</version> <!-- 使用最新版本 --> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-activedirectory</artifactId> <version>1.12.0</version> </dependency> (2) 配置 shiro.ini 文件通过 shiro.ini 配置 AD 认证的核心参数:[main] # 配置 ActiveDirectoryRealm activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldap://ad.example.com:389 # AD 服务器地址 activeDirectoryRealm.systemUsername = CN=admin,CN=Users,DC=example,DC=com # 系统管理员 DN activeDirectoryRealm.systemPassword = admin123 # 系统管理员密码 activeDirectoryRealm.searchBase = CN=Users,DC=example,DC=com # 用户搜索基路径 activeDirectoryRealm.groupRolesMap = "CN=Developers,CN=Users,DC=example,DC=com:developer,CN=Admins,CN=Users,DC=example,DC=com:admin" # AD 组到 Shiro 角色的映射 # 可选:启用调试日志 loggerFactory.logLevel = debug关键参数说明:url:AD 服务器地址(如 ldap://ad.example.com:389)。systemUsername 和 systemPassword:用于搜索用户的系统管理员凭据(需具有读取权限)。searchBase:用户搜索的基路径(通常是 CN=Users,DC=example,DC=com)。groupRolesMap:AD 组到 Shiro 角色的映射(格式:AD组DN:Shiro角色)。(3) 编写 Java 代码进行认证import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class AdAuthDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("user1", "password123"); try { // 执行认证 currentUser.login(token); System.out.println("AD 认证成功!"); System.out.println("用户角色: " + currentUser.getPrincipals().asList()); // 输出角色 } catch (Exception e) { System.out.println("AD 认证失败:" + e.getMessage()); } } } 3. 高级配置(1) 启用 SSL/TLS如果 AD 服务器使用安全连接:[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldaps://ad.example.com:636 # 使用 ldaps activeDirectoryRealm.systemUsername = CN=admin,CN=Users,DC=example,DC=com activeDirectoryRealm.systemPassword = admin123(2) 自定义搜索过滤器如果默认搜索不满足需求,可以自定义搜索过滤器:[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldap://ad.example.com:389 activeDirectoryRealm.searchBase = CN=Users,DC=example,DC=com activeDirectoryRealm.searchFilter = (sAMAccountName={0}) # 使用 AD 的 sAMAccountName 属性(3) 动态角色映射如果需要动态映射 AD 组到 Shiro 角色,可以继承 ActiveDirectoryRealm 并重写方法:import org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashMap; import java.util.Map; public class CustomAdRealm extends ActiveDirectoryRealm { @Override protected Map<String, String> getGroupRolesMap(PrincipalCollection principals) { Map<String, String> rolesMap = new HashMap<>(); // 动态映射逻辑(例如从数据库加载) rolesMap.put("CN=Developers,CN=Users,DC=example,DC=com", "developer"); rolesMap.put("CN=Admins,CN=Users,DC=example,DC=com", "admin"); return rolesMap; } } 然后在 shiro.ini 中配置:[main] customAdRealm = com.example.CustomAdRealm customAdRealm.url = ldap://ad.example.com:389 securityManager.realms = $customAdRealm4. 常见问题与解决方案(1) 认证失败:Invalid credentials原因:用户名或密码错误,或 AD 服务器配置不正确。解决:检查 systemUsername 和 systemPassword 是否正确。确认 AD 用户的 sAMAccountName 是否与输入的用户名匹配。(2) 认证失败:Connection refused原因:AD 服务器地址或端口错误。解决:确认 url 是否正确(如 ldap://ad.example.com:389)。检查防火墙是否放行 LDAP 端口(389 或 636)。(3) 角色映射不生效原因:groupRolesMap 配置错误或 AD 组路径不正确。解决:使用 AD 工具(如 Active Directory Users and Computers)确认组 DN。确保 groupRolesMap 的格式为 AD组DN:Shiro角色。(4) 性能问题原因:频繁的 AD 查询导致延迟。解决:使用缓存(如 Shiro 的 CachingRealm)。优化搜索过滤器,减少返回的数据量。5. 总结Shiro 的 Active Directory 认证通过 ActiveDirectoryRealm 实现,核心步骤包括:配置 shiro.ini 文件,设置 AD 服务器地址、系统管理员凭据、搜索基路径和角色映射。编写 Java 代码,通过 Subject.login() 执行认证。根据需求调整高级配置(如 SSL、自定义搜索、动态角色映射)。适用场景:企业级应用需要与现有 AD 环境集成。需要基于 AD 组管理用户角色和权限。适用于 Windows 域环境下的单点登录(SSO)。通过合理配置,Shiro 可以高效、安全地实现与 Active Directory 的认证集成。
  • [技术干货] Shiro 的 LDAP 认证
    Shiro 的 LDAP 认证方式详解Apache Shiro 提供了对 LDAP(轻量级目录访问协议)的集成支持,用于企业级系统的集中式身份认证。以下是 Shiro 中实现 LDAP 认证的完整指南,包括配置、代码示例和关键点说明。1. LDAP 认证基础作用:通过 LDAP 服务器(如 Active Directory、OpenLDAP)验证用户凭据(用户名/密码)。优势:集中式用户管理,无需在应用中维护密码。支持企业级目录服务,便于集成现有系统。2. Shiro LDAP 认证实现步骤(1) 添加依赖确保项目中包含 Shiro 和 LDAP 相关依赖(以 Maven 为例):<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.12.0</version> <!-- 使用最新版本 --> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ldap</artifactId> <version>1.12.0</version> </dependency> (2) 配置 shiro.ini 文件通过 shiro.ini 配置 LDAP 认证的核心参数:[main] # 配置 LDAP Realm ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.contextFactory.url = ldap://ldap.example.com:389 # LDAP 服务器地址 ldapRealm.contextFactory.authenticationMechanism = simple # 认证机制(simple/DIGEST-MD5) ldapRealm.userDnTemplate = uid={0},ou=users,dc=example,dc=com # 用户 DN 模板 ldapRealm.searchBase = dc=example,dc=com # 搜索基路径 ldapRealm.systemUsername = cn=admin,dc=example,dc=com # 系统管理员 DN(用于搜索) ldapRealm.systemPassword = admin123 # 系统管理员密码 # 可选:启用调试日志 loggerFactory.logLevel = debug关键参数说明:contextFactory.url:LDAP 服务器地址(如 ldap://ldap.example.com:389)。userDnTemplate:用户 DN 模板,{0} 会被替换为用户名。searchBase:搜索用户的基路径(如 dc=example,dc=com)。systemUsername 和 systemPassword:用于搜索用户的系统管理员凭据(可选,取决于 LDAP 配置)。(3) 编写 Java 代码进行认证import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class LdapAuthDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("user1", "password123"); try { // 执行认证 currentUser.login(token); System.out.println("LDAP 认证成功!"); } catch (Exception e) { System.out.println("LDAP 认证失败:" + e.getMessage()); } } } 3. 高级配置(1) 自定义 LDAP 搜索如果用户 DN 不能通过模板直接生成,可以自定义搜索逻辑:[main] ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.contextFactory.url = ldap://ldap.example.com:389 ldapRealm.searchBase = dc=example,dc=com ldapRealm.systemUsername = cn=admin,dc=example,dc=com ldapRealm.systemPassword = admin123 # 自定义搜索过滤器 ldapRealm.searchFilter = (uid={0}) # 搜索过滤器(默认是 (uid={0}))(2) 启用 SSL/TLS如果 LDAP 服务器使用安全连接:[main] ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.contextFactory.url = ldaps://ldap.example.com:636 # 使用 ldaps ldapRealm.contextFactory.environment[java.naming.security.protocol] = ssl # 启用 SSL (3) 结合 Active DirectoryActive Directory 的配置略有不同:[main] activeDirectoryRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm activeDirectoryRealm.url = ldap://ad.example.com:389 activeDirectoryRealm.systemUsername = CN=admin,CN=Users,DC=example,DC=com activeDirectoryRealm.systemPassword = admin123 activeDirectoryRealm.searchBase = CN=Users,DC=example,DC=com4. 常见问题与解决方案(1) 认证失败:Invalid credentials原因:用户名或密码错误,或 LDAP 服务器配置不正确。解决:检查 userDnTemplate 或 searchFilter 是否正确。使用工具(如 Apache Directory Studio)测试 LDAP 连接。(2) 认证失败:Connection refused原因:LDAP 服务器地址或端口错误。解决:确认 contextFactory.url 是否正确。检查防火墙是否放行 LDAP 端口(389 或 636)。(3) 性能问题原因:频繁的 LDAP 查询导致延迟。解决:使用缓存(如 Shiro 的 CachingRealm)。优化搜索过滤器,减少返回的数据量。5. 总结Shiro 的 LDAP 认证通过 JndiLdapRealm 或 ActiveDirectoryRealm 实现,核心步骤包括:配置 shiro.ini 文件,设置 LDAP 服务器地址、用户 DN 模板等。编写 Java 代码,通过 Subject.login() 执行认证。根据需求调整高级配置(如 SSL、自定义搜索)。适用场景:企业级应用需要与现有 LDAP/AD 集成。需要集中式用户管理和单点登录(SSO)。通过合理配置,Shiro 可以轻松实现安全、可靠的 LDAP 认证。
  • [技术干货] shiro细粒度资源访问控制
    细粒度资源访问控制是 Shiro 的核心功能之一,允许开发者基于资源实例(如数据库记录、文件路径等)和操作(如读取、编辑、删除)定义权限。以下是完整的实现步骤和示例代码:1. 核心概念权限字符串格式:资源:操作(如 user:edit:123 表示编辑 ID 为 123 的用户)授权流程:通过 Subject.isPermitted("权限字符串") 动态判断权限。2. 完整实现步骤(1) 自定义 Realm 实现权限校验import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.HashSet; import java.util.Set; public class PermissionRealm extends AuthorizingRealm { // 模拟数据库存储的用户角色和权限 private static final Map<String, Set<String>> USER_ROLES = new HashMap<>(); private static final Map<String, Set<String>> ROLE_PERMISSIONS = new HashMap<>(); static { // 初始化角色和权限数据 Set<String> adminPermissions = new HashSet<>(); adminPermissions.add("user:create"); adminPermissions.add("user:edit:123"); // 允许编辑ID为123的用户 adminPermissions.add("user:delete"); ROLE_PERMISSIONS.put("admin", adminPermissions); Set<String> userPermissions = new HashSet<>(); userPermissions.add("user:view"); ROLE_PERMISSIONS.put("user", userPermissions); USER_ROLES.put("admin", new HashSet<>(Collections.singletonList("admin"))); USER_ROLES.put("user1", new HashSet<>(Arrays.asList("user", "guest"))); } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 模拟从数据库获取用户信息 UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); if (!"admin".equals(username) && !"user1".equals(username)) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(username, "password", getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); Set<String> roles = USER_ROLES.get(username); if (roles == null) { return null; } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(roles); // 根据角色添加权限 for (String role : roles) { Set<String> permissions = ROLE_PERMISSIONS.get(role); if (permissions != null) { info.addStringPermissions(permissions); } } return info; } } (2) 配置 Shiro 使用自定义 Realmshiro.ini 配置文件:[main] permissionRealm = com.example.PermissionRealm securityManager.realms = $permissionRealm(3) 测试细粒度权限校验import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class PermissionDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 模拟用户登录 currentUser.login(new org.apache.shiro.authc.UsernamePasswordToken("admin", "password")); // 测试细粒度权限 System.out.println("是否有创建用户权限: " + currentUser.isPermitted("user:create")); // true System.out.println("是否有编辑ID为123的用户权限: " + currentUser.isPermitted("user:edit:123")); // true System.out.println("是否有删除用户权限: " + currentUser.isPermitted("user:delete")); // true System.out.println("是否有编辑ID为456的用户权限: " + currentUser.isPermitted("user:edit:456")); // false(未授权) // 模拟普通用户登录 currentUser.logout(); currentUser.login(new org.apache.shiro.authc.UsernamePasswordToken("user1", "password")); System.out.println("普通用户是否有编辑权限: " + currentUser.isPermitted("user:edit:123")); // false } } 3. 关键点说明权限字符串设计:user:create:全局用户创建权限。user:edit:123:仅允许编辑 ID 为 123 的用户,实现实例级控制。动态权限校验:通过 Subject.isPermitted("权限字符串") 动态判断,无需硬编码权限逻辑。扩展性:可从数据库加载权限数据,支持动态权限变更。4. 输出结果运行 PermissionDemo 的输出:是否有创建用户权限: true 是否有编辑ID为123的用户权限: true 是否有删除用户权限: true 是否有编辑ID为456的用户权限: false 普通用户是否有编辑权限: false 总结通过自定义 Realm 和权限字符串设计,Shiro 可以轻松实现细粒度资源访问控制。关键点包括:权限字符串格式化:用 资源:操作:实例 定义权限。动态校验:通过 Subject.isPermitted() 实时判断权限。数据驱动:权限数据可从数据库动态加载,适应复杂业务场景。这种设计既灵活又安全,适用于企业级应用中的权限管理需求。
  • [技术干货] shiro的OGNL 授权策略
    Shiro 的 OGNL (Object-Graph Navigation Language) 授权策略是一种高级的权限控制方式,允许开发者通过表达式语言定义复杂的权限规则。OGNL 表达式可以访问 Java 对象的方法和属性,从而动态判断权限。1. OGNL 授权策略概述作用:通过 OGNL 表达式定义权限条件,例如 user.age > 18 或 resource.owner == currentUser。使用场景:需要基于对象属性或业务逻辑动态判断权限时。2. 实现步骤(1) 自定义 Realm 支持 OGNL 表达式在 doGetAuthorizationInfo 方法中返回 OGNL 表达式对应的权限信息。import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import java.util.Collections; import java.util.HashSet; import java.util.Set; public class OgnlPermissionRealm extends AuthorizingRealm { @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 模拟从数据库获取用户信息 UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); if (!"admin".equals(username)) { throw new UnknownAccountException("用户不存在"); } return new SimpleAuthenticationInfo(username, "password", getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 添加一个 OGNL 表达式权限 info.addObjectPermission(new WildcardPermission("user:edit:[id]")); return info; } } (2) 配置 Shiro 使用自定义 Realmshiro.ini 配置文件:[main] ognlPermissionRealm = com.example.OgnlPermissionRealm securityManager.realms = $ognlPermissionRealm(3) 自定义 PermissionResolver 解析 OGNL 表达式实现 PermissionResolver 接口,解析 OGNL 表达式。import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.permission.PermissionResolver; import org.apache.shiro.authz.permission.WildcardPermission; public class OgnlPermissionResolver implements PermissionResolver { @Override public Permission resolvePermission(String permissionString) { // 解析 OGNL 表达式 if (permissionString.startsWith("user:edit:")) { return new OgnlPermission(permissionString); } return new WildcardPermission(permissionString); } } (4) 自定义 Permission 类import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.StringUtils; public class OgnlPermission implements Permission { private final String permissionString; public OgnlPermission(String permissionString) { this.permissionString = permissionString; } @Override public boolean implies(Subject subject, Permission permission) { // 实现 OGNL 表达式逻辑 if (permission instanceof OgnlPermission) { String otherPermission = ((OgnlPermission) permission).getPermissionString(); // 示例:解析 ID 并比较 if (otherPermission.startsWith("user:edit:")) { String id = StringUtils.substringAfter(otherPermission, "user:edit:"); // 假设从上下文中获取当前用户和资源 ID // 这里简化逻辑,直接返回 true(实际需根据业务实现) return true; } } return false; } public String getPermissionString() { return permissionString; } } (5) 测试 OGNL 权限import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class OgnlPermissionDemo { public static void main(String[] args) { // 初始化 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 模拟用户登录 currentUser.login(new UsernamePasswordToken("admin", "password")); // 测试 OGNL 权限 boolean hasPermission = currentUser.isPermitted("user:edit:123"); System.out.println("是否有编辑ID为123的用户权限: " + hasPermission); // true } } 3. 关键点说明OGNL 表达式解析:通过 PermissionResolver 解析自定义的 OGNL 表达式。在 implies 方法中实现具体的业务逻辑。动态权限判断:权限字符串可以包含变量(如 [id]),在运行时动态解析。扩展性:可以结合上下文信息(如当前用户、资源实例)实现复杂权限逻辑。4. 总结Shiro 的 OGNL 授权策略提供了一种灵活的方式来实现细粒度的权限控制,特别适合需要动态解析权限表达式的场景。通过自定义 PermissionResolver 和 Permission 类,可以轻松扩展 Shiro 的权限系统以满足业务需求。
  • [技术干货] shiro认证方式介绍
    Apache Shiro 支持多种认证方式,包括基于用户名/密码、自定义 Realm、第三方系统(如 LDAP、Active Directory)等。以下是几种常见的认证方式及其使用示例:1. 基于用户名/密码的认证这是最常见的认证方式,通常通过 UsernamePasswordToken 实现。示例代码:import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; public class UsernamePasswordAuthDemo { public static void main(String[] args) { // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } 2. 自定义 Realm 认证如果需要自定义认证逻辑(例如从数据库获取用户信息),可以实现 Realm 接口。自定义 Realm 示例:import org.apache.shiro.authc.*; import org.apache.shiro.realm.Realm; public class CustomRealm implements Realm { @Override public String getName() { return "CustomRealm"; } @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken = (UsernamePasswordToken) token; String username = upToken.getUsername(); String password = new String(upToken.getPassword()); // 模拟从数据库获取用户信息 if (!"admin".equals(username) || !"123456".equals(password)) { throw new UnknownAccountException("用户名或密码错误"); } // 返回认证信息(用户名、密码、盐、Realm 名称) return new SimpleAuthenticationInfo(username, password, getName()); } } 配置 Shiro 使用自定义 Realm:import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class CustomRealmDemo { public static void main(String[] args) { // 配置 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } shiro.ini 配置文件:[main] customRealm = com.example.CustomRealm securityManager.realms = $customRealm3. 集成 LDAP 认证Shiro 支持与 LDAP 集成,用于企业级系统的认证。示例代码:import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; public class LdapAuthDemo { public static void main(String[] args) { // 配置 SecurityManager Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-ldap.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } shiro-ldap.ini 配置文件:[main] ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm ldapRealm.url = ldap://localhost:389 ldapRealm.contextFactory.url = ldap://localhost:389 ldapRealm.userDnTemplate = uid={0},ou=users,dc=example,dc=com4. 记住我(RememberMe)功能Shiro 支持“记住我”功能,允许用户在关闭浏览器后重新登录时无需再次输入凭据。示例代码:import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; public class RememberMeDemo { public static void main(String[] args) { // 获取当前用户 Subject currentUser = SecurityUtils.getSubject(); // 创建用户名/密码 Token,并启用“记住我”功能 UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); token.setRememberMe(true); try { // 执行认证 currentUser.login(token); System.out.println("认证成功!"); } catch (Exception e) { System.out.println("认证失败:" + e.getMessage()); } } } 总结以上是 Shiro 中几种常见的认证方式及其使用示例。Shiro 的灵活性使其能够轻松集成到各种应用程序中,并根据需求选择合适的认证方式。
  • [技术干货] Apache Shiro 框架介绍
    Apache Shiro 简介Apache Shiro 是一个强大且易于使用的 Java 安全框架,旨在简化应用程序的身份验证、授权、加密和会话管理。它提供了全面的安全功能,同时保持了简洁性和灵活性,适用于各种规模的应用程序,从小型独立应用到大型企业级系统。Shiro 的核心功能Shiro 的核心功能可以归纳为四个主要方面:认证(Authentication)、授权(Authorization)、加密(Cryptography) 和 会话管理(Session Management)。以下是对这些功能的详细介绍:1. 认证(Authentication)认证是验证用户身份的过程,确保用户是他们声称的那个人。Shiro 提供了灵活的认证机制,支持多种认证方式,如用户名/密码、数字证书、OpenID 等。特点:支持多种认证数据源(如数据库、LDAP、Active Directory 等)。支持多因素认证。易于集成到现有应用程序中。示例:UsernamePasswordToken token = new UsernamePasswordToken("username", "password"); Subject currentUser = SecurityUtils.getSubject(); currentUser.login(token); // 执行认证 2. 授权(Authorization)授权是控制用户访问权限的过程,决定用户可以执行哪些操作或访问哪些资源。Shiro 提供了细粒度的授权控制,支持基于角色、权限和实例级别的授权。特点:支持基于角色的访问控制(RBAC)。支持基于权限的访问控制(如细粒度的资源访问控制)。支持表达式语言(如 Shiro 的 OGNL 表达式)进行灵活的授权策略定义。示例:// 检查用户是否具有特定角色 if (currentUser.hasRole("admin")) { // 用户具有管理员角色,执行相应操作 } // 检查用户是否具有特定权限 if (currentUser.isPermitted("user:create")) { // 用户具有创建用户的权限,执行相应操作 } 3. 加密(Cryptography)Shiro 提供了对加密操作的支持,包括哈希(Hashing)、加密(Encryption)和解密(Decryption)。这使得应用程序能够安全地存储密码、敏感数据等。特点:提供多种哈希算法(如 MD5、SHA-256 等)。支持对称加密和非对称加密。提供密码盐(Salt)支持,增强安全性。示例:// 使用 MD5 哈希算法对密码进行哈希处理 String hashedPassword = new Md5Hash("password", "salt").toString(); 4. 会话管理(Session Management)Shiro 提供了完整的会话管理功能,类似于 HTTP 会话,但不限于 Web 环境。它允许应用程序在非 Web 环境中(如桌面应用、移动应用等)也能管理用户会话。特点:支持会话的创建、销毁、持久化等操作。提供会话超时、会话监听等功能。支持分布式会话管理(通过 Redis 等缓存系统)。示例:// 获取当前用户的会话 Session session = currentUser.getSession(); // 设置会话属性 session.setAttribute("key", "value"); // 获取会话属性 Object value = session.getAttribute("key"); Shiro 的其他特性除了上述核心功能外,Shiro 还具有以下特性:Web 支持:提供对 Web 应用程序的支持,包括过滤器、Servlet 集成等。缓存支持:可以与多种缓存系统(如 Ehcache、Redis 等)集成,提高性能。“记住我”功能:支持用户登录后的“记住我”功能,提升用户体验。集成 Spring:提供与 Spring 框架的紧密集成,方便在 Spring 应用中使用 Shiro。总结Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,它提供了全面的身份验证、授权、加密和会话管理功能。通过使用 Shiro,开发者可以轻松地为应用程序添加安全功能,保护应用程序免受未经授权的访问和攻击。无论是小型独立应用还是大型企业级系统,Shiro 都是一个值得考虑的安全解决方案。
  • [问题求助] 【问题求助】 算法精英实战营
    提交作品后,经历2小时后报错:internal_error,中间没有出现任何超时现象,而在本地跑是没有问题的。想问下,这种情况,是华为云平台本身的问题,还是我提交的作品的问题呀?如果是我作品的问题,这种情况,是什么原因导致的呢?
  • [体验官] CodeArts的Java版本和C/C++和ShellRemote是一个安装包吗?
    CodeArts的Java版本和C/C++和ShellRemote是一个安装包吗, 为啥我下载这三个的安装包,安装下来都是一个软件呢?
  • [技术干货] Java内存分析全面指南
    Java内存分析是诊断性能问题和内存泄漏的关键技能。下面我将从内存结构、分析工具、常见问题和实战案例等方面进行全面介绍。一、Java内存结构详解1. 堆内存(Heap)新生代(Young Generation)Eden区:新对象分配区域Survivor区(From/To):存活对象过渡区默认比例:Eden:From:To = 8:1:1老年代(Old Generation)存放长期存活对象大对象直接进入老年代元空间(Metaspace)存储类元数据取代永久代(PermGen)默认不限制大小(受物理内存限制)2. 非堆内存JVM栈:线程私有,存储栈帧本地方法栈:Native方法调用程序计数器:线程执行位置二、内存分析工具1. JDK自带工具工具命令用途jpsjps -lvm查看Java进程jstatjstat -gcutil <pid> 1000实时GC统计jmapjmap -heap <pid>堆内存快照jhatjmap -dump:format=b,file=heap.hprof <pid> + jhat heap.hprof堆转储分析jstackjstack -l <pid>线程堆栈分析2. 图形化工具VisualVM:多功能监控/分析JConsole:基础监控Eclipse MAT:专业堆分析JProfiler:商业级分析工具Arthas:阿里开源的在线诊断工具3. 生产环境推荐Prometheus + Grafana:时序监控HeapHero:自动化堆分析GCeasy:GC日志分析三、常见内存问题分析1. 内存泄漏(Memory Leak)特征:堆内存持续增长Full GC后内存不释放OOM错误诊断步骤:获取堆转储:jmap -dump:live,format=b,file=leak.hprof <pid>使用MAT分析查找"Accumulation Point"(积累点)检查大对象和GC Roots引用链2. 内存溢出(OOM)常见类型:java.lang.OutOfMemoryError: Java heap space:堆内存不足java.lang.OutOfMemoryError: Metaspace:元空间不足java.lang.OutOfMemoryError: Unable to create new native thread:线程过多解决方案:增加对应区域内存(-Xmx, -XX:MaxMetaspaceSize)优化代码(减少对象创建)调整线程池配置3. GC相关问题症状:应用停顿(STW时间过长)CPU使用率高(GC线程占用)吞吐量下降分析方法:收集GC日志:-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps使用GCeasy或GCEViewer分析四、实战分析案例案例1:内存泄漏分析场景:Web应用运行一段时间后OOM步骤:获取堆转储使用MAT打开,选择"Leak Suspects"报告发现HashMap占用了80%内存检查引用链发现静态Map缓存未清理解决方案:改用WeakHashMap或添加清理逻辑案例2:元空间溢出场景:动态生成类导致Metaspace不足解决:增加Metaspace大小:-XX:MaxMetaspaceSize=256m使用jcmd <pid> VM.metaspace监控优化动态类生成逻辑案例3:GC频繁症状:Young GC每分钟超过20次调优:增加新生代大小:-Xmn512m调整Survivor比例:-XX:SurvivorRatio=6优化短命对象创建五、高级分析技巧1. 堆外内存分析使用NMT(Native Memory Tracking):-XX:NativeMemoryTracking=detailjcmd <pid> VM.native_memory detail检查DirectByteBuffer使用2. 线程堆栈分析jstack <pid> > thread.txt查找死锁(deadlock)和阻塞(blocked)线程结合top -Hp <pid>查找CPU高的线程3. 内存分配分析使用JFR(Java Flight Recorder):-XX:StartFlightRecording=duration=60s,filename=alloc.jfr分析对象分配热点
  • [问题求助] CodeArts IDE for java 什么时候支持 java21 版本呢
    CodeArts IDE最新只能选择 17 的特性,什么时候支持 21
  • [问题求助] 算法精英实战营-第二十四期-问题求助
    有2个问题需要求助:1.压缩包内需要存放整个项目还是单独的文件就可以呀?2.上传作品后得分为-1,是因为编译没通过吗?还是其他什么原因呀?
  • [技术干货] 解析Java根基:Object类核心方法
    Object类常见方法解析在Java编程中,Object类是所有类的根类,它包含了许多实用的方法,这些方法在不同的场景下发挥着重要作用。下面我们来详细了解一下Object类中的一些常见方法。1. toString方法toString方法是用于将对象转换为字符串表示形式的方法。在默认情况下,toString方法返回的结果是类名加上@符号,再跟上该对象对应哈希码的十六进制表示。例如,当我们打印一个对象时,如果没有重写toString方法,就会得到类似这样的结果:com.example.MyClass@12345678。然而,在实际开发中,我们通常需要根据对象的具体属性来定制它的字符串表示形式,以便更清晰地展示对象的信息。这时,我们就需要重写toString方法。比如,对于一个表示学生信息的类Student,我们可以这样重写toString方法:public class Student {    private String name;    private int age;     // 构造方法和其他方法省略     @Override    public String toString() {        return "Student{name='" + name + "', age=" + age + "}";    }}这样,当我们打印一个Student对象时,就会得到包含学生姓名和年龄信息的字符串。2. equals和hashcode方法equals方法和hashcode方法在对象比较和集合操作中起着关键作用。equals方法:在进行对象比较的时候,我们通常会使用equals方法。如果不重写equals方法,默认情况下它会根据对象的地址进行比较,即只有两个引用指向同一个对象时,它们才被认为是相等的。但在很多实际场景中,我们需要根据对象的属性和方法来判断它们是否相等。例如,对于两个表示学生信息的对象,如果它们的姓名和年龄都相同,我们就认为这两个学生是相等的。这时,我们就需要重写equals方法,如下所示:@Overridepublic boolean equals(Object obj) {    if (this == obj) return true;    if (obj == null || getClass()!= obj.getClass()) return false;    Student student = (Student) obj;    return age == student.age && Objects.equals(name, student.name);}hashcode方法:hashcode方法用于返回对象的哈希码值。哈希码是一个整数,它在许多数据结构中都有重要作用,比如HashMap和HashSet。当我们对对象进行存储和查找操作时,哈希码可以帮助我们快速定位对象的位置。需要注意的是,当我们重写equals方法时,通常也需要同时重写hashcode方法。这是因为如果两个对象根据equals方法判断是相等的,那么它们的哈希码也必须相等。如果不重写hashcode方法,可能会导致哈希冲突的概率增大。例如,在HashMap中,如果只重写了equals方法而没有重写hashcode方法,那么具有相同属性的两个对象可能会被存储在不同的哈希桶中,这会使得一些哈希桶中的对象个数过多,从而降低搜索效率。另外,如果只重写了hashcode方法而没有重写equals方法,当发生哈希冲突时,会导致对象的安全性受到威胁。因为在哈希冲突的情况下,不同的对象可能会被存储在同一个哈希桶中,如果不重写equals方法,就无法正确判断这些对象是否真的相等,从而可能导致一些错误的结果。3. wait、notify和notifyAll方法wait、notify和notifyAll方法是用于线程间通信的方法,它们只能在同步代码块或同步方法中使用。 wait方法:当一个线程调用某个对象的wait方法时,它会解除对该对象的锁,并进入等待状态,直到其他线程调用该对象的notify或notifyAll方法来唤醒它。例如,在生产者-消费者模型中,当消费者发现缓冲区为空时,它会调用缓冲区对象的wait方法进入等待状态,直到生产者生产了新的数据并调用notify或notifyAll方法来唤醒它。notify方法:notify方法用于唤醒陷入等待状态的某一进程。它会随机选择一个正在等待该对象锁的线程,并将其唤醒。需要注意的是,notify方法只会唤醒一个线程,如果有多个线程在等待,那么具体唤醒哪个线程是不确定的。notifyAll方法:notifyAll方法用于唤醒陷入等待状态的所有进程。它会唤醒所有正在等待该对象锁的线程,这些线程会竞争获取对象的锁,然后继续执行。在这个示例中,生产者线程先生产数据,然后通过notify方法唤醒消费者线程,消费者线程在收到通知后开始消费数据。Java为什么被称为平台无关性语言Java实现平台无关性的核心机制在于JVM(Java虚拟机)的中间层设计。当Java源代码通过javac编译器生成字节码文件(.class文件)后,这些包含平台中立中间代码的文件可以在任何安装有对应平台JVM的设备上运行。各个操作系统虽然使用不同的机器指令集,但通过针对特定平台实现的JVM,字节码会被实时转换为所在系统的本地机器指令执行。这种"一次编译,到处运行"的特性,使得开发者无需针对不同操作系统修改源代码,JVM作为抽象层有效隔离了底层硬件和操作系统的差异。= =和equals有什么区别?== 和 equals 的区别基本概念== 是一个操作符,用于比较两个变量的值或引用。equals 是 Object 类的一个方法,用于比较两个对象的内容。如果 equals 方法没有被重写,它的默认行为与 == 相同。比较规则对于基本数据类型(如 int、char 等):== 比较的是变量的值。例如:int a = 5;int b = 5;System.out.println(a == b); // 输出 true对于对象类型(如 String、Integer 等):== 比较的是对象的引用(即内存地址)。例如:String str1 = new String("hello");String str2 = new String("hello");System.out.println(str1 == str2); // 输出 falseequals 方法比较的是对象的内容。例如:System.out.println(str1.equals(str2)); // 输出 trueequals 方法的重写默认情况下,equals 方法比较的是对象的引用(与 == 相同)。如果类重写了 equals 方法,则可以根据自定义的逻辑比较对象的内容。例如,String 类重写了 equals 方法,用于比较字符串的内容。重写 equals 方法时,通常需要同时重写 hashCode 方法,以确保对象在哈希表等数据结构中的行为一致。equals()与hashcode()equals和hashCode属于Object的方法,equals默认情况下和==等效,hashCode()是根据一定的规则根据对象返回的值,比如对象的地址,经过处理,返回哈希值在进行对象的比较的时候,可以进行自定义比较方法,需要重写equals方法,为了使HashMap、HashSet等方法正常存储,还需要对HashCode()进行重写HashMap存储对象的时候,会先调用HashCode方法进行比较,当HashCode值相等的时候,再使用equals方法再进行比较Student对象,有name和height两个成员变量,使用HashSet存储两个name和height相等的对象,判断Student对象是否相等就看这两个变量,当没有重写HashCode的时候,存储到HashSet中也会当作两个不同的对象,调用HashCode方法,因为没有重写,默认哈希值不会相同,所以会被认为两个对象不相同解决方法就是重写HashCode(),最简单的就是返回两个变量哈希值的积equals() 与 hashCode() 的核心机制equals() 和 hashCode() 是定义在 java.lang.Object 类中的基础方法。equals() 方法:默认实现与 == 运算符等价,用于判断两个对象是否严格相等(即是否指向同一内存地址)。开发中需根据业务需求重写此方法,以实现自定义的相等性逻辑。hashCode() 方法:默认返回对象的内存地址经过哈希算法处理后的整数值。其作用是为对象生成一个紧凑的哈希标识,主要用于快速定位数据存储位置(如哈希表)。集合框架中的关键作用当使用 HashMap、HashSet 等基于哈希的集合存储对象时:哈希优先原则:集合会先调用对象的 hashCode() 方法计算哈希值,若哈希值冲突,则进一步通过 equals() 方法比较对象的实际内容。一致性要求:若重写了 equals(),必须同步重写 hashCode()。若二者逻辑不一致(例如两个对象通过 equals() 判断相等,但 hashCode() 返回不同值),会导致集合无法正确维护元素唯一性,引发潜在 bug。Student 对象的典型场景分析假设存在如下 Student 类:public class Student {        private String name;        private int height;}未重写 hashCode() 的问题向 HashSet<Student> 中添加两个属性完全相同的对象时:Student s1 = new Student("Alice", 160);Student s2 = new Student("Alice", 160);Set<Student> students = new HashSet<>();students.add(s1);students.add(s2); // 实际执行结果:s2 会被视为新元素加入集合原因:默认的 hashCode() 实现基于对象内存地址,两次 new 操作生成的 s1 和 s2 地址不同,导致哈希值冲突被判定为不同对象。解决方案:重写 hashCode()通过覆盖 hashCode() 方法,将关键字段纳入哈希值计算:@Overridepublic int hashCode() {return Objects.hash(name, height); // 或简化为 name.hashCode() * 31 + height}原理:将 name 和 height 的哈希值按规则组合,确保内容相同的对象生成相同的哈希码,从而解决集合存储异常问题。重载和重写重载(Overloading)重载指的是在同一个类中,允许存在多个方法名相同但参数列表不同的方法。这些方法的参数个数、顺序或类型必须有所区别。通过这种方式,可以为同一操作提供多种不同的实现方式,以适应不同的输入需求。特点:方法签名不同:方法名相同,但参数列表(参数的个数、顺序或类型)不同。编译时决定:重载的解析是在编译期间完成的,编译器根据调用方法时提供的参数类型来决定具体调用哪一个重载的方法。静态绑定:由于在编译时已经确定了调用的具体方法,因此重载属于静态绑定。重写(Overriding)重写是指子类对父类中已有的方法进行重新定义,方法名、参数列表以及返回类型都必须与父类中的方法完全一致。通过重写,子类可以改变父类方法的行为,以适应子类的特定需求。特点:方法签名相同:方法名、参数列表和返回类型必须与父类中的方法完全一致。运行时决定:重写的解析是在运行期间完成的,具体执行哪一个方法取决于对象的实际类型。动态绑定:由于在运行时才确定调用的具体方法,因此重写属于动态绑定。抽象和接口抽象类与接口的对比分析【核心概念】抽象类使用 abstract 关键字修饰,用于定义具有部分实现的基类;接口使用 interface 关键字声明,作为纯粹的行为契约。二者在代码组织层面存在本质区别:【抽象类的应用】当多个类存在代码复用需求时,通过抽象类进行逻辑抽取可提升代码简洁性与可维护性。其核心价值体现在:代码复用:将公共实现逻辑提取到抽象基类中,子类通过继承复用代码强制约束:通过抽象方法(public abstract 修饰)强制子类实现特定功能类型标识:表达严格的继承关系,体现"是"(is-a)的语义特征修改时只需调整抽象基类即可影响所有子类,但需注意普通类也能实现类似效果,抽象类的核心区别在于其无法实例化并具有强制约束力。【接口的演进】接口作为规范标准,体现"具有...能力"(like-a)的语义特征,其发展历经多个阶段:传统接口(Java 7-)仅包含隐式 public abstract 修饰的抽象方法只允许声明 public static final 常量增强接口(Java 8+)引入 default 方法(含具体实现)支持静态方法定义允许定义私有方法(Java 9+)【实现规范】类实现接口时需遵循:必须实现所有抽象方法(未实现则类需声明为 abstract)可选择性覆盖 default 方法常量字段自动继承且不可修改支持多接口实现,体现灵活的组合特性Finalfinal 是 Java 的关键字,用于限制类、方法或变量的可修改性,具体用法如下:修饰类当类被 final 修饰时,该类不能被继承。核心目的:防止通过子类继承修改原有类的行为,常用于保护核心 API 的稳定性。示例:public final class String { ... } // String 类不可被继承修饰方法当方法被 final 修饰时,该方法不能被子类重写。核心目的:确保关键方法的行为不被破坏,常用于模板方法模式中的固定步骤。示例:public class Parent {        public final void lock() { ... } // 子类无法重写 lock()}修饰变量基本类型变量:变量值初始化后不可修改,成为常量。示例:final int MAX_VALUE = 100;// MAX_VALUE = 200; // 编译报错引用类型变量:引用类型变量变量指向的内存地址不可变,但对象内部状态可以修改。示例:final List list = new ArrayList<>();list.add("Java"); // 允许修改内容 // list = new LinkedList<>();  编译报错(地址不可变) static final 联合使用 static final 修饰的变量为全局常量,在类加载时初始化。 优势: 编译时常量(如字符串字面量)会被 JVM 优化,直接存入常量池,提升访问效率。 示例: public static final String VERSION = "1.0";不可变设计的经典案例:String 类的不可变性: String 类被 final 修饰,禁止继承。 内部存储数据的 char[] value 数组被 private final 修饰,且不提供修改方法(如 setter)。 所有修改操作(如 substring、replace)均返回新对象,原对象不变。 优势: 线程安全(无需同步锁)。 哈希值可缓存,提升性能。多线程场景下的应用使用 final 修饰变量可确保该变量的值在线程间可见且不可变,避免数据竞争问题。示例:        public class SafePublication {                private final int safeValue; // 安全发布,防止指令重排序                public SafePublication(int value) {                this.safeValue = value;        }}异常的理解异常是指在程序运行过程中可能发生的非正常事件,这类事件会中断程序的正常执行流程。通过异常处理机制,开发者可以捕获并处理这些异常,在保障程序核心功能不受致命影响的前提下完成错误恢复操作。异常的种类Java 的异常类继承体系以 Throwable 为顶层父类,具体分为两类:Error(错误)由 JVM 或底层系统资源引发的严重问题(如 StackOverflowError 栈溢出、OutOfMemoryError 内存耗尽),属于不可恢复的致命错误。Exception(异常)可被捕获处理的非致命性问题,进一步细分为:编译时异常(Checked Exception)编译器强制要求处理的异常类型,如 FileNotFoundException(文件未找到)、IOException(输入输出异常)。运行时异常(Runtime Exception)由代码逻辑缺陷引发的异常,编译器不强制处理,如 NullPointerException(空指针异常)、IndexOutOfBoundsException(数组越界异常)。thow和throws的区别throw是在方法内部使用,进行抛出异常,是一个动作。throws是在方法声明的后面,表示可能会抛出的异常,是一种声明那么直接try-catch不就行了,为什么还要抛出异常呢?首先我们要分情况说明,如果该异常可以被try-catch处理的话,就可以直接try-catch,比如说虽然A方法抛出了一个异常,但是基于他异常处理的方法在其他地方,并不需要处理,不需要处理的情况下,就抛出异常给调用者。其次,当调用的几个方法中都存在相同的异常,如果在这几个方法内部进行try-catch的话,会使得整体变得冗余,可以让调用方统一进行捕获异常,并进行处理,简洁化。最后还有可能就是上层调用方处理String不可变类的核心优势线程安全:String的不可变性天然规避了多线程同步问题,无需额外锁机制即可保证线程安全。哈希缓存:首次计算哈希值后缓存,作为Map键时无需重复计算,显著提升查找效率(如HashMap的键比较场景)。字符串创建与常量池机制共享优化:常量池通过引用复用已存在的字符串,避免重复创建。例如:String s1 = "hello"; // 常量池新建对象(若不存在)String s2 = "hello"; // 直接引用常量池现有对象对象创建示例:String a = new String("aa") + "bb"; 的详细过程:常量池新建"aa"对象(若不存在)。new String("aa")在堆中创建新对象(非池引用)。常量池新建"bb"对象(若不存在)。拼接生成"aabb",堆中创建新对象并可能更新常量池(若未存在),共创建了四个对象。字符串存储与内存限制底层结构:使用char[]存储,理论最大长度为Integer.MAX_VALUE(2³¹-1)。实际限制:编译期:最大长度为2¹⁶-2(65534,受CONSTANT_Utf8_info限制)。运行时:受JVM可用内存限制(如堆大小)。字符串常量池的演进Java 7前:位于永久代(PermGen),易引发OOM。Java 8+:迁移至元空间(Metaspace),降低内存溢出风险。字符串拼接效率对比String的+操作:String result = "a" + "b"; // 编译后等效于:String result = new StringBuilder().append("a").append("b").toString();隐含创建StringBuilder和临时String对象,效率较低。StringBuffer:直接操作内部可变数组,避免频繁对象创建。线程安全(同步开销),适合多线程场景。StringBuilder(补充建议):单线程下更高效(非线程安全)。————————————————              原文链接:https://blog.csdn.net/tulingtuling/article/details/147128918
  • [技术干货] Java-抽象类与接口
    前言上期我们介绍了类与对象的知识点,那么这期我先为大家带来关于抽象类与接口的具体的讲解,希望大家能更进一步理解Java中的特点,下节再为大家介绍两大特性的详解。一、抽象类1.抽象类的概念在面向对象的概念中,所有的对象都是通过类来进行描绘的,但是并不是所有的类来进行描绘对象的。如果一个类中没有足够的信息内容来回一个具体的对象,那么这种类就是抽象类。2.抽象类的语法1.抽象类都是被abstract类修饰的叫做抽象类public abstract class A{}2.抽象类中的成员方法用abstract修饰的方法,叫做抽象方法public abstract class A{public abstract void test();//抽象方法}3.抽象类的特点通过上述方法我们可以得知:一个类中有抽象方法,那么这个类必是抽象类抽象类中也可以有普通成员变量和普通成员方法比如:abstract class Shape{    public int a = 10;//普通成员变量    public void draw(){//普通成员方法        System.out.println("画图形");    }    public abstract void draw2();//抽象方法}抽象类不能直接实例化public static void main(String[] args) {        Shape shape = new Shape();    }    抽象类不能实例化,那么是用来干什么的呢?当然是用来被继承的了,所以抽象类也可以实现了向上转型抽象类中的方法是不能被private修饰的    private abstract void draw2();final 和 abstract 两者不能同时存在,被abstract修饰的方法就是要被继承的与重写的,而final修饰的不能被重写和继承,属于是密封类和密封方法public abstract final void draw2();抽象方法不能被static修饰,因为抽象方法要被子类重写,而static是属于类本身的方法public static final void draw2();当一个普通类继承了这个抽象类之后,这个普通类一定要重写这个抽象类的当中的所有的抽象方法abstract class Shape{    public int a = 10;    public void draw(){        System.out.println("画图形");    }    public abstract void draw2();//抽象方法    public void test(){        }}class React extends Shape{    @Override    public void draw2() {        System.out.println("矩形");    }}class Flower extends Shape{    @Override    public void draw2() {        System.out.println("花" );    }}子类如果不想重写抽象方法,那么就把子类也设为抽象类比如这有一个子类Aabstract class A extends Shape{    public abstract void testDemo();}但是我们会想到,如果一直设计为抽象子类,那么它们的抽象方法怎么办?class  B extends A{    @Override    public void draw2() {    }    @Override    public void testDemo() {    }}所以B继承了A,A继承了Shape,两个类都是抽象类,所以这个普通类B类就要重写两个方法当一个抽象类A不想被一个普通类B继承,此时可以把这个B类变成抽象类,那么在当一个普通类C继承这个抽象类B之后,C要重写B和A的里面的所有抽象方法4.抽象类的操作给出完整的代码,让我们分析一下:abstract class Shape{    public int a = 10;    public void draw(){        System.out.println("画图形");    }    public abstract void draw2();    public void test(){    }    public Shape() {        //既然它不能实例化,但是可以让子类调用帮助这个抽象类初始化它自己的成员    }}class React extends Shape{    @Override    public void draw2() {        System.out.println("矩形");    }}class Flower extends Shape{    @Override    public void draw2() {        System.out.println("花" );    }}public class Test {    public static void drawMap(Shape shape) {        shape.draw2();    }    public static void main(String[] args) {        Shape shape = new React();        drawMap(new React());        drawMap(new Flower());           }Shape shape1 = new React();Shape shape2 = new Flower();这个操作是向上转型,在我们调用了一些抽象方法的时候,由于我们将其抽象方法进行了重写,进而发生了动态绑定,从而发生多态。  public static void drawMap(Shape shape) {      shape.draw2();   }这个方法我们来接收Shape类型,但是由于子类继承父类,构成了协变类型,也会发生向上转型,往里面传入了参数时,再调用抽象方法,这个时候发生了动态绑定,就会发生了多态new React() new Flower() 这种没有名字的对象 —> 匿名对象匿名对象的缺点:每次使用,都得去重新实例化所以我们可以使用向上转型的直接赋值,来去传进参数public static void main(String[] args) {        Shape shape1 = new React();        Shape shape2 = new Flower();        drawMap(shape1);        drawMap(shape2);           }5.抽象类的作用我们都知道普通的类也能被继承,那还需要抽象类来做什么?但是我们正常都需要跟业务和实际情况来进行选择,使用抽象类相当于多了一重编译器的校验:使用抽象类的场景就如上面的代码,实际工作不应该由父类完成,而应由子类完成.。那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的;但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。意义:很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们.充分利用编译器的校验, 在实际开发中是非常有意义的.二、接口1.接口的概念比如:我们都知道电脑上面有USB接口,可以插些:U盘,鼠标等符合USB协议的设备;接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。在Java中,接口可以看成是多个类的公共规范,是一种引用数据类型。2.接口语法public interface 接口名称{//抽象方法//成员变量}一般的接口名称前加一个I字母。interface IShape{//一般情况下我们以I开头来表示接口的    int a = 10;    void draw();    default void test(){        System.out.println("test()");    }    public static void test2(){        System.out.println(" static test2()");    }}在这个接口中我没有看到修饰符,难道是默认的吗?并不是默认的!原因这是在接口中,成员变量默认是被public static final修饰的,成员方法是抽象方法,但是默认被public abstract修饰的但是这种我们一般不写接口中的方法一般不能实现,都是抽象方法,但是从JDK8之后,可以支持default修饰的成员方法和static修饰的成员方法中可以有具体的实现(即主体)3.接口的使用与特性接口的成员方法必须是默认为public abstract来修饰的。其他修饰符不可以;private 和 protected还有default均不可以,都会出现报错接口类型也是一种引用类型接口不能有普通成员方法,也不能有成员变量interface IShape{//一般情况下我们以I开头来表示接口的    private int a = 10;    void draw();    default void test(){        System.out.println("test()");    }    public void test2(){        System.out.println(" static test2()");    }}就算你写成了public的成员变量,这时候也是默认成public static final修饰的,所以不会出现错误接口不能被new关键字来进行实例化,但是也可以用向上转型IShape iShape = new IShape();类实现了接口,那么该子类就要重写接口中的所有抽象的方法,用implements关键字来实现class Rect implements IShape{    @Override    public void draw() {        System.out.println("矩形");    }}class Flower implements IShape{    @Override    public void draw() {        System.out.println("花");    }}如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类abstract class A implements IShape{    }当然了,还是要还的,因为他只能被用来继承,所以这种类被继承后还是需要重写接口中的方法当一个类实现了接口当中的方法之后,当前类中的方法不能不加public,因为原来接口中的方法是public修饰的,所以重写的方法要大于等于public的范围,所以必须是public修饰class Rect implements IShape{    @Override    void draw() {        System.out.println("矩形");    }}接口当中,不能使用构造方法和静态代码块interface IShape{//一般情况下我们以I开头来表示接口的  public IShape(){            }    static{            }}4.实现多个接口在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。在父类中是放入一些共性的特点,而不是特有的,是共有的比如我们创建了一个动物类,再创建几个具体的动物的类,再分别创建几个接口://游泳接口public interface ISwimming {    void swim();}//跑步接口public interface IRunning {    void run();}//飞行接口public interface IFlying {    void fly();}//动物类:public abstract class Animals {    public String name;    public int age;    public Animals(String name, int age) {        this.name = name;        this.age = age;    }    public abstract void eat();}鸟可以进行飞行和跑步,所以实现这两接口,并且继承了动物类//鸟类public class Bird extends Animals implements IFlying, IRunning {    public Bird(String name, int age) {        super(name, age);//帮助父类进行构造,用super来进行访问父类    }    @Override    public void eat() {        System.out.println(this.name + "正在吃饭!");    }    @Override    public void fly() {        System.out.println(this.name + "正在用翅膀飞!");    }    @Override    public void run() {        System.out.println(this.name + "正在用鸟腿跑!");    }}狗类是可以进行跑步和游泳,所以实现这两个接口,并且继承了父类//狗类public class Dog extends Animals implements IRunning, ISwimming {    public Dog(String name, int age) {        super(name, age);//帮助父类进行构造    }    @Override    public void eat() {        System.out.println(this.name + "正在吃狗粮!");    }    @Override    public void run() {        System.out.println(this.name + "正在用狗腿跑!");    }    @Override    public void swim() {        System.out.println(this.name + "正在狗腿游泳!");    }}主类public class Test {    public static void test1(Animals animals){        animals.eat();    }    public static void test4(IFlying iFlying){        iFlying.fly();    }    public static void test2(IRunning iRunning){        iRunning.run();    }    public static void test3(ISwimming iSwimming){        iSwimming.swim();    }    public static void main(String[] args) {        Bird bird = new Bird("小鸟",1);        Dog dog = new Dog("小狗",10);        test1(dog);        test1(bird);                test2(bird);        test2(dog);        System.out.println("============");        test3(dog);        //test3(bird);bird没有实现swimming的接口        System.out.println("============");        test4(bird);        //test4(dog);dog没有实现了flying的接口           }}上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。语法上先继承后实现,否则会报错继承表示是其中的一种,接口表示的是它具有什么特性,能做什么。这时候我们在创建一个机器人类:public class Robot implements IRunning {    @Override    public void run() {        System.out.println("机器人在跑!");    }}比如我们在主类中测试test2(new Robot());1那么这种操作的好处是什么?时刻牢记多态的好处, 让我们忘记类型. 有了接口之后, 类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力我们创建了这个机器人类,实现了跑步的接口,所以直接去实现调用test2的方法,这个时候我们就可以发现了这个接口,回避了向上转型会使其更加灵活。5.接口之间的继承在Java中,类和类之间是单继承的,但是一个类可以实现多个接口,接口与接口之间可以多继承。用接口达到多继承的目的。接口的继承用extends关键字interface A{    void testA();}interface B{    void testB();}interface C{    void testC();}interface D extends B,C{    //D 这个接口具备了B和C接口的功能    //并且D还可以有自己的方法    void testD();}在接口中是进行抽象方法的声明,所以接口之间继承不用重写抽象方法当类来实现这个D接口的时候,我们要重写D接口中的方法和D所继承的接口中的方法public class Test implements D{    @Override    public void testB() {    }    @Override    public void testC() {    }    @Override    public void testD() {    }    //就如同子孙类一样总结:接口间的继承相当于把多个接口合并在一起。在接口中是进行抽象方法的声明,所以接口之间继承不用重写抽象方法。6.接口的实例(1).对象大小的比较当我们先给出了几个对象,从而想要按照一定顺序比较其中内容的大小我们先创建了一个Student类class Student {    public String name ;    public int age;    public Student(String name, int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}这个时候我们再创建一个主类public class Test {public static void main(String[] args) {        Student student1 = new Student("zhangsan", 10);        Student student2 = new Student("lisi", 15);        System.out.println(student1 > student2);    }}    这么运行会出现错误;(因为两者都是引用类型,无法直接进行比较)所以这个时候我们用接口来实现这个自定义类型的比较,所以分别是 Comparable和Comparator接口(1).Comparable接口这个是接口Comparable中的源码这个接口中涉及到后续的泛型,关于这个泛型到后期的数据结构我们在详细的讲解如果我们要实现比较的方法,就要用compareTo这个方法,当然如果直接使用:System.out.println(student1.compareTo(student2));因为compareTo是Comparable中的方法,所以要想使用这个方法,就要实现这个接口,并指定比较的类型用<>class Student implements Comparable<Student>{    public String name ;    public int age;    public Student(String name, int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }    @Override    public int compareTo(Student o) {//重写的CompareTo方法        return this.age - o.age;        //大于0,则前者大于后者        //小于0,前者小于后者        //this.age表示的是student1,o.age表示的是student2}}public class Test {public static void main(String[] args) {        Student student1 = new Student("zhangsan", 10);        Student student2 = new Student("lisi", 15);        System.out.println(student1.compareTo(student2));    }}如果我们给出的是student的对象数组,让其大小进行排序,不作对student类进行修改,只在主类中进行实例化import java.util.Arrays;public class Test {    public static void main(String[] args) {        //利用对象数组        Student[] students = new Student[3];        students[0] = new Student("zhangsan",10);        students[1] = new Student("lisi",2);        students[2] = new Student("liwu",18);        Arrays.sort(students);//这个时候要用comparable来进行一定的顺序排序.        System.out.println(Arrays.toString(students));    }}因为通过comparable这个接口来进行查找一定的内容,从而按一定的内容来进行比较大小当然,我们想要按其他内容的时候进行排序,需要改动某些代码,这时候就容易会导致某些错误。(2).Comparator接口Comparator接口中有compare的方法为默认权限,所以我们调用这个方法这种方式叫做比较器,在类的外部进行实现class Student2{    public String name;    public int age;    @Override    public int compareTo(Student2 o) {        return this.age - o.age;    }    public Student2(String name, int age) {        this.name = name;        this.age = age;    }    @Override    public String toString() {        return "Student{" +                "name='" + name + '\'' +                ", age=" + age +                '}';    }}比如我们要按对象的年龄大小进行比较,这时候再创建一个age类,实现了Comparator这个接口class AgeComparator implements Comparator<Student2>{    @Override    public int compare(Student2 o1, Student2 o2) {        return o1.age - o2.age;    }}这个时候我们先把在外部的类实例化,进而要比较需要两个对象的变量传递到compare方法中去,从去重写这个方法,并且发生向上转型(参数类型)import java.util.Comparator;public class Test2 {    public static void main(String[] args) {        Student2 student1 = new Student2("zhangsan", 10);        Student2 student2 = new Student2("lisi", 15);        AgeComparator ageComparator = new AgeComparator();        System.out.println(ageComparator.compare(student1, student2));}}当我们按照name进行比较的时候,这个时候在创建了Name类,再去实现了Comparator这个接口class NameComparator implements Comparator<Student2>{    @Override    public int compare(Student2 o1, Student2 o2) {        return o1.name.compareTo(o2.name);    }}name是引用类型,不能进行相减来进行比较,但是我们发现了String类中也有去实现Comparable的方法所以我们只需要重写compareTo方法即可当然我们还是需要进行将Name类进行类的实例化 NameComparator nameComparator = new NameComparator(); System.out.println(nameComparator.compare(student1, student2));关于对于自定义类型比较的总结:Comparator这种方式的优势就是对于类的侵入性不强,会比较灵活Comparable这种方式对于类的侵入性比较强,所以做好不要去改动原有的逻辑,比如我按名字排序这个时候就会容易发生变化并且两者可以共存,互不干扰;因为comparable是对原有的类进行实现,comparator是对要比较内容的类而实现,所以互不干扰(2).实现类的克隆(1).类的克隆Java中有许多的内置接口,而Cloneable就是一个非常有用的接口Object类中有一个clone方法,调用这个对象可实现创建一个对象的“拷贝”,但是想要成功调用这个方法,就要实现这个其中的接口class Person {    public int age;    public Person(int age) {        this.age = age;    }    @Override    public String toString() {        return "Person{" +                " age=" + age +                " }";    }}public class Test3 {    public static void main(String[] args){        Person person1 = new Person(10);        System.out.println(person1);        Person person2 = person1.clone();        System.out.println(person1);    }}例如:我们创建一个person类,存入了一个年龄的成员变量,并重写了toString方法;然后我们对Person类实例化,并存入了一个age为10的值让其构造方法为它自己进行初始化,这个时候我们直接打印了person1,这个时候我们再创建一个变量person2来接收person1的拷贝后的值。但是如果我们直接这么做,直接去调用:Person person1 = person.clone();这个时候就会出现这种错误;因为clone方法是在object类中的方法,所以我们点击查看clone的源码:protected native Object clone() throws CloneNotSupportedException;这个时候我们就会发现了这个clone方法是本地方法,是用C/C++来写的,所以我们也无法知道这个具体的过程实现,并且我们也看到了这个方法是被protected修饰的,它的访问权限是在不同包的子类可以访问不同包的父类被protected修饰的成员变量和方法, 但是也需要用super来访问,所以需要重写这个clone方法;class Person {    public int age;    public Person(int age) {        this.age = age;    }    @Override    public String toString() {        return "Person{" +                " age=" + age +                " }";    }@Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();//调用object类的方法来进行访问    }}这个时候我们会发现还是有错误啊,但是这时候提示我们类型不兼容,因为Object类是所有的类的父类,但是其中clone方法是比较特殊的,它返回的是object类型,但是它并没有与Person构成显示的父与子的协变类型,所以这也反映了不构成重写的条件之一,所以会出现这种错误,但是我们把这个类型强制转换为Person类,所以就可以了public class Test3 {    public static void main(String[] args){        Person person1 = new Person(10);        System.out.println(person1);        Person person2 = (Person) person1.clone();        System.out.println(person1);    }}这个时候在运行结果:这个时候还是报了错误,告诉我们还有异常这错误,但是异常处理我们后期会详细讲解,因为clone方法的异常是受查异常/编译时异常,所以必须在编译时处理:public class TestDemo {    public static void main(String[] args) throws CloneNotSupportedException{//必须在编译时处理        Person person1 = new Person(10);        System.out.println(person1);        Person person2 = (Person) person1.clone();        System.out.println(person2);}但是我们运行完,还是会出现错误,这可是真愁人CloneNotSupportedException,不支持克隆,告诉我们Person类不支持克隆所以我们这时候就要提到前面所说的Cloneable接口了,这个时候我们就用Person实现这个接口,那么运行结果:这时候我们就完成了person2对person1的所指的对象的克隆这个时候我们来看一下Cloneable这个接口:Cloneable是一个空接口,我们可以在IDEA中去查看源码:它是一个空接口,也作为一种标记接口,因为它用来证明当前类是可以被克隆的在堆上,创建了一个对象,假设地址是0X98,那么在虚拟机栈上得到是person1所指这个对象的地址,通过clone的方法来进行克隆出堆中一个相同的一份的对象,但是两者(person1和person2)在堆上不是一个位置,所以person2这个克隆后的获得了一个新的地址0X88(假设的),所以我们通过 return super.clone() 来克隆。那么就来总结一下Person person1 = person.clone();这个其中的所有的错误:修饰符的错误,protected,这一步用super.clone()访问object类,来重写这个clone方法异常,clone方法的异常是受查异常/编译时异常,所以必须在编译时处理,(在main后面加入throws)向下转型,进行强制转换,因为我们用的是object类中的clone方法,所以返回类型是固定的object类,而不是构成了协变类型。所以我们要进行向下转型,将其转换为子类;进行了上述的操作,还是会报错:CloneNotSupportedException,不支持克隆。所以我们要实现Cloneable这个接口,从而才能使用我的理解:clone方法实现的具体操作,用super.clone来访问,Cloneable判断这个类是否支持克隆操作(2).浅拷贝这时候我们在Person类的基础上再重新创建了一个Money类,让它与其Person类构成组合class Money{    public double money = 19.9;}class Person1 implements Cloneable{    public int age;    public Money m;    public Person1(int age) {        this.age = age;        this.m = new Money();//在构造方法中实例化一个money    }    @Override    public String toString() {        return "Person{" +                " age=" + age +                " }";    }    @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }}public class TestDemo {    public static void main(String[] args) throws CloneNotSupportedException {        Person1 person1 = new Person1(10);        Person1 person2 = (Person1) person1.clone();        System.out.println(person1);        System.out.println(person2);        System.out.println("===============");        System.out.println(person1.m.money);        System.out.println(person2.m.money);//先调用的是person1的成员变量m,        //由于在构造方法中进行了m的实例化,所以我还可以调用Money类中的成员变量        System.out.println("===============");        //将person2进行修改,正常我们所期望的是只有person2进行修改,person1没有变化        person2.m.money = 99.99;        System.out.println(person1.m.money);        System.out.println(person2.m.money);    }}this.m = new Money();也就是在堆上的空余的空间再创建了一个实例化Money的对象,并且这个m在堆上那个原本的空间占有的一定内存空间。person1.m.money为什么会这么调用money?这个m是Peron1类的成员变量,但是我们也在Person1类中的构造方法去实例化Money这个类,所以这个m又是Money实例化的变量名,再用这个m来调用了Money类中的成员变量money将person2进行修改,正常我们所期望的是只有person2进行修改,person1没有变化,但是可惜的是两者都发生了变化。所以我们可以通过图例来演示一下这个过程:在虚拟机栈上,会创建两个空间分别是person1和person2,person1所实例化的对象会在堆上创建一个对象的空间(地址为0x98),存入两个成员变量,但是其中m是Money类型即是引用类型,并且也是实例化了,所以那么也会是创建money类型的对象的空间(地址为0x65),这时候在对第一个对象中的成员变量m也会得到它实例化的地址person1.clone()是把person1指向的对象进行克隆,但是没把这个对象的空间中成员变量m所指的对象进行克隆所以那也意味着成员变量m还是指的是0x65的它自己的对象空间Money,这个对象的空间的成员变量是moneyperson2还是指的是第一次的对象中的对象的成员变量money;所以这个时候堆money的修改还是一次就改变person1和person2所指的money的值,两者还是一样的此时这个现象被称之为浅拷贝:并没有将对象中的对象去进行克隆(3).深拷贝class Money1 implements Cloneable{    public double money = 19.9;    @Override    protected Object clone() throws CloneNotSupportedException {        return super.clone();    }}class Person2 implements Cloneable{    public int age;    public Money1 m;    public Person2(int age) {        this.age = age;        this.m = new Money1();//在构造方法中实例化一个money    }    @Override    public String toString() {        return "Person{" +                " age=" + age +                " }";    }    @Override    protected Object clone() throws CloneNotSupportedException {        Person2 tmp = (Person2) super.clone();        //tmp.m类的调用成员变量,this.m.clone()中的this是指这个对象的成员变量m的引用        tmp.m = (Money1) this.m.clone();//谁调用某个方法谁就是this,现在是person1来调用,那么person1就是this        return tmp;    }}public class Test5 {    public static void main(String[] args) throws CloneNotSupportedException{        Person2 person1 = new Person2(10);        Person2 person2 = (Person2) person1.clone();        System.out.println(person1.m.money);        System.out.println(person2.m.money);        System.out.println("===============");        person2.m.money = 99.9;        System.out.println(person1.m.money);        System.out.println(person2.m.money);       }}这时候我们把Money1类也进行实现了Cloneable的接口,这时候我们再对clone方法的重写,但是我们会发现两者的重写的clone方法不同,在Person2类中的重写方法,创建一个临时变量tmp,通过将其向下转型变成了Person2类型,tmp.m是Person2类的调用成员变量m,this.m.clone()就是person1正在调用clone方法,这个时候先访问了Pesron2类中成员变量m,再去通过Money1类的实例化之后再调用clone方法,这个时候最终是Money1这个类型去调用这个方法;但是我们会发现tmp是局部变量,除了生命作用域就会销毁,但是最终返回的是给person2,又因为person2是引用类型,会得到了tmp的地址。所以我们可以通过图例来演示一下这个过程:在虚拟机栈上,会创建两个空间分别是person1和person2,还有一个在person类中创建一个临时变量tmp,因为是把对象的对象进行克隆,所以要再类中的另一个类(该类作为成员变量)中重写clone方法,但是在原有的类中的clone方法也需要进行修改person1所实例化的对象会在堆上创建一个对象的空间(地址为0x98),存入两个成员变量,但是其中m是Money类型即是引用类型,并且也是实例化了,所以那么也会是创建money类型的对象的空间(地址为0x65),这时候在对中的成员变量m也会得到它实例化的地址因为我们还在Person类中修改了重写方法;通过创建了一个临时变量tmp也是为Person类型,先把外面的对象先进行克隆完成,这时候再通过这个临时变量去调用Person类中的m(其实也就是类的引用),由于我将m这个变量也进行了Money类的实例化的操作,所以这时候再用m去调用正常的clone方法,从而通过去调用Money类中的clone方法这个就完成了克隆那么Money类的克隆的地址(0x888),但是tmp是临时的局部的变量,出了作用域就销毁,最终返回的是给的是person2[person2 = (Person2) person1.clone()]这个就是返回了person2么因为它是类类型(引用类型)所以这个时候的person2就会得到它的地址(0x91)最终person2和person1所指的两个值是不一样此时这个现象被称之为深拷贝:完全将对象中的对象去进行克隆。注意:深拷贝与浅拷贝就是看的是代码实现的过程,跟克隆方法没有关系三、抽象类和接口的区别(面试常考)(1).抽象类中可以有普通成员方法和成员变量,其中还可以有抽象方法,如果子类继承抽象类,必须要重写抽象类中的抽象方法,如果不想重写抽象方法,那么就要将子类设计为抽象类一个类可以实现多个接口(模拟多继承),但是类与类之间的继承必须是单继承的关系,继承用extends关键字(2).接口中不可以有普通的成员方法和成员变量,它的成员变量默认是public static final修饰,成员方法默认是public abstract修饰,如果类要实现接口,就要重写接口中的抽象方法实现用implements关键字,接口之间也可以继承多个接口(这种操作也是多继承),这种操作不用父接口的抽象方法;但是用类实现时,要注意重写接口与被继承接口的所有的抽象方法。这期内容就分享到这里了,希望大家可以获得新的知识,当然,如果有哪些细节和内容不足,欢迎大家在评论区中指出!————————————————                原文链接:https://blog.csdn.net/2401_87022967/article/details/147046361
  • [技术干货] JavaSE—多线程
    本文详细介绍了Java中多线程的创建方式,包括继承Thread类、实现Runnable接口和Callable接口,以及线程安全、线程同步的概念和实现方法,如同步代码块、同步方法和Lock锁。此外,还探讨了线程通信、线程池的使用,包括ExecutorService、ThreadPoolExecutor和Executors工具类,以及定时器ScheduledExecutorService的应用。最后,文章还涵盖了线程并发与并行以及线程的生命周期。摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >展开 文章目录多线程多线程的创建方式一:继承Thread类方式二:实现Runnable接口多线程的实现方案二:实现Runnable接口(匿名内部类形式)方式三:JDK5.0新增:实现Callable接口Thread的常用方法线程安全线程安全问题是什么、发生的原因线程安全问题案例模拟线程同步同步思想概述方式一:同步代码块方式二:同步方法Lock锁线程通信线程通信案例模拟线程池线程池概述线程池实现的API、参数说明**线程池常见面试题**线程池处理Runnable任务线程池处理Callable任务Executors工具类实现线程池定时器ScheduledExecutorService定时器线程并发与并行线程的生命周期多线程什么是线程?线程(Thread)是一个程序内部的一条执行路径。我们之前启动程序执行后,main方法执行其实就是一条单独的执行路径程序中如果只有一条执行路径,那么这个程序就是单线程的程序。多线程是什么?多线程是指从软硬件上实现多条执行流程的技术。多线程的创建Thread类Java是通过java.lang.Thread类来代表线程的。按照面向对象的思想,Thread类应该提供了实现多线程的方式。方式一:继承Thread类定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法创建MyThread类的对象调用线程对象的start()方法启动线程(启动后还是执行run方法的)public class ThreadDemo01 {    public static void main(String[] args) {        //3.new一个线程对象        Thread t = new MyThread();        t.start();        for (int i = 0; i < 5; i++) {            System.out.println("主线程启动了:"+i);        }    }}/**    1.定义一个线程类继承Thread类 */class MyThread extends Thread{    /**        2.重写run方法,里面定义线程以后要干啥     */    @Override    public void run() {        for (int i = 0; i < 5; i++) {            System.out.println("子线程启动了:"+i);        }    }}方式一优缺点:优点:编码简单缺点:线程类以及继承Thread,无法继承其他类,不利于拓展为什么不直接调用了run方法,而是调用start启动线程直接调用run方法会当成普通方法执行,此时相当于还是单线程执行只有调用start方法才是启动一个新的线程执行把主线程任务放在子线程之前了。这样主线程一直是先跑完的,相当于是一个单线程的效果了。方式二:实现Runnable接口定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法创建MyRunnable任务对象把MyRunnable任务对象交给Thread处理调用线程对象的start()方法启动线程public class ThreadDemo02 {    public static void main(String[] args) {        Runnable runnable = new MyRunable();        Thread t = new Thread(runnable);        t.start();        for (int i = 0; i < 10; i++) {        System.out.println("主线程开始执行:"+i);        }    }}class MyRunable implements Runnable{    @Override    public void run() {        for (int i = 0; i < 10; i++) {            System.out.println("子线程在执行:"+i);        }    }}方式二优缺点:优点:线程任务类只是实现接口,可以继续继承类和实现接口,拓展性强。缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。多线程的实现方案二:实现Runnable接口(匿名内部类形式)可以创建Runnable的匿名内部类对象交给Thread处理调用线程对象的start() 启动线程public class ThreadDemoOther02 {    public static void main(String[] args) {        //创建一个任务对象        Runnable a = new Runnable() {            @Override            public void run() {                for (int i = 0; i < 5; i++) {                    System.out.println("子线程1执行输出:" + i);                }            }        };        // 把任务对象交给Thread处理        Thread t = new Thread(a);        t.start();                    Thread t1 = new Thread(new Runnable() {                @Override                public void run() {                    for (int i = 0; i < 5; i++) {                        System.out.println("子线程2执行输出:" + i);                    }                }            });            t1.start();            new Thread(new Runnable() {                @Override                public void run() {                    for (int i = 0; i < 5; i++) {                        System.out.println("子线程3执行输出:" + i);                    }                }            }).start();            new Thread(() -> {                for (int i = 0; i < 5; i++) {                    System.out.println("子线程4执行输出:" + i);                }            }).start();        for (int i = 0; i < 5; i++) {            System.out.println("主线程执行输出:" + i);        }        }    }output:子线程2执行输出:0子线程2执行输出:1子线程2执行输出:2子线程2执行输出:3子线程2执行输出:4子线程1执行输出:0子线程1执行输出:1子线程1执行输出:2子线程1执行输出:3子线程1执行输出:4主线程执行输出:0主线程执行输出:1主线程执行输出:2主线程执行输出:3主线程执行输出:4子线程4执行输出:0子线程4执行输出:1子线程4执行输出:2子线程4执行输出:3子线程4执行输出:4子线程3执行输出:0子线程3执行输出:1子线程3执行输出:2子线程3执行输出:3子线程3执行输出:4方式三:JDK5.0新增:实现Callable接口多线程的实现方案三:利用Callable、FutureTask接口实现得到任务对象定义类实现Callable接口,重写call方法,封装要做的事情创建Callable任务对象交给FutureTask对象用FutureTask 把Callable对象封装成线程任务对象把线程任务对象交给Thread处理调用Thread的start方法启动线程,执行任务线程执行完毕后,通过FutureTask的get方法去获取任务执行的结果public class ThreadDemo03 {    public static void main(String[] args) {        Callable<String> call = new myCallable(100);        //FutureTask对象的作用:是Runnable的对象(实现了Runnable接口),可以交给Thread了        FutureTask<String> f = new FutureTask<>(call);        Thread t = new Thread(f);        t.start();        Callable<String> call2 = new myCallable(200);        FutureTask<String> f2 = new FutureTask<>(call2);        Thread t2 = new Thread(f2);        t2.start();        try {            String rs1 = f.get();            System.out.println(rs1);        } catch (Exception e) {            e.printStackTrace();        }        try {            String rs2 = f2.get();            System.out.println(rs2);        } catch (Exception e) {            e.printStackTrace();        }    }}class myCallable implements Callable<String> {    private int n;    public myCallable(int n) {        this.n = n;    }    @Override    public String call() throws Exception {        int sum = 0;        for (int i = 0; i <= n; i++) {            sum += i;        }        return "子线程结果是:" + sum;    }}output:子线程结果是:5050子线程结果是:20100FutureTask的API方法名称 说明public FutureTask<>(Callable call) 把Callable对象封装成FutureTask对象。public V get() throws Exception 获取线程执行call方法返回的结果Thread的常用方法Thread常用API说明Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。至于Thread类提供的诸如:yield、join、interrupt、不推荐的方法stop、守护线程、线程优先级等线程的控制方法,在开发中很少使用。Thread类获得当前线程的对象注意:此方法是Thread类的静态方法,可以直接使用Thread类调用。这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。public class ThreadDemo01 {    // main方法是由主线程负责调度的    public static void main(String[] args) {        Thread t1 = new MyThread();//        t1.setName("1号");        t1.start();        System.out.println(t1.getName());        Thread t2 = new MyThread();//        t2.setName("2号");        t2.start();        System.out.println(t2.getName());        // 哪个线程执行它,它就得到哪个线程对象(当前线程对象)        // 主线程的名称就叫main        Thread m = Thread.currentThread();        System.out.println(m.getName());        for (int i = 0; i < 5; i++) {            System.out.println(m.getName()+"输出:"+i);        }    }        public class MyThread extends Thread{    @Override    public void run() {        for (int i = 0; i < 5; i++) {            System.out.println(Thread.currentThread().getName()+"输出:"+i);        }    }}output:Thread-0Thread-1mainmain输出:0Thread-1输出:0Thread-1输出:1Thread-1输出:2Thread-1输出:3Thread-1输出:4main输出:1main输出:2main输出:3main输出:4Thread-0输出:0Thread-0输出:1Thread-0输出:2Thread-0输出:3Thread-0输出:4Thread的构造器方法名称 说明public Thread(String name) 可以为当前线程指定名称public Thread(Runnable target) 封装Runnable对象为线程对象public Thread(Runnable target, String name) 封装Runnable对象为线程对象,并指定线程名称Thread类的线程休眠方法方法名称 说明public static void sleep(long time) 让当前线程休眠指定的时间后再继续执行,单位为毫秒public class ThreadDemo02 {    public static void main(String[] args)throws Exception {        for (int i = 1; i <= 5; i++) {            System.out.println("主线程输出:"+i);            if (i==3){                //让线程进入3秒休眠状态                Thread.sleep(3000);            }        }    }}线程安全线程安全问题是什么、发生的原因线程安全问题多个线程同事操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。出现的原因:存在多线程并发同时访问共享资源存在修改共享资源线程安全问题案例模拟案例:取钱业务需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,模拟2人同时去取钱10万分析:需要提供一个账户类,创建一个账户代表2人的共享账户需要定义一个线程类,线程类可以处理账户对象创建两个线程对象,传入同一个账户对象启动两个线程,去同一个账户对象中取钱10万public class ThreadDemo {    public static void main(String[] args) {        //1.定义线程类,创建一个共享的账户对象        Account acc = new Account("ICBC-110",100000);        //2.创建2个线程对象,代表小红和小明同时进来了        new DrawThread(acc,"小明").start();        new DrawThread(acc,"小红").start();    }}/**共享账户类*/public class Account {    private String cardID;//卡号    private double money;//余额    public Account() {    }    /**     * 小明 小红取钱     */    public void drawMoney(double money) {        //1.先获取是谁来取钱,线程的名字就是人名        String name = Thread.currentThread().getName();        //1.判断账户是否有钱        if (this.money >= money) {            //取钱            System.out.println(name+"成功取出了"+money+"元");            //更新余额            this.money -= money;            System.out.println(name+"取款后剩余:"+this.money);        }else {            System.out.println(name+"来取钱,余额不足!");        }    }    public Account(String cardID, double money) {        this.cardID = cardID;        this.money = money;    }    public String getCardID() {        return cardID;    }    public void setCardID(String cardID) {        this.cardID = cardID;    }    public double getMoney() {        return money;    }    public void setMoney(double money) {        this.money = money;    }}/**    取钱的线程类 */public class DrawThread extends Thread{    //接收处理的账户对象    private Account acc;    public DrawThread(Account acc,String name) {        super(name);        this.acc = acc;    }    @Override    public void run() {        //小红 小红 取钱        acc.drawMoney(100000);    }}output:小红成功取出了100000.0元小明成功取出了100000.0元小明取款后剩余:-100000.0小红取款后剩余:0.0线程同步同步思想概述线程同步为了解决线程安全问题取钱案例出现问题的原因?多个线程同时执行,发现钱都是够的。如何才能保证线程安全呢?让多个线程实现先后依次访问共享资源,这样就解决了安全问题线程同步的核心思想加锁,把共享资源进行上锁,每次只能一个线程进入,访问完毕后解锁,然后其他线程才能进来。方式一:同步代码块作用:把出现线程安全问题的核心代码给上锁。原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。synchronized(同步锁对象){    操作共享资源的代码(核心代码)}锁对象要求理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可。/**    小红小明在此取钱 */public void drawMoney(double money) {    //看谁来这里取钱    String name = Thread.currentThread().getName();    //  同步代码块    // 小明 小红    // this == acc 共享账户    synchronized (this) {        //判断余额是否足够        if (this.money>=money){            //取钱            System.out.println(name+"取钱成功,本次取款:"+money);            //更新余额            this.money-=money;            System.out.println(name+"取款后,剩余:"+this.money);        }else {            System.out.println(name+"取款余额不足!");        }    }}锁对象用任意唯一的对象好不好呢?不好,会影响其他无关线程的执行。锁对象的规范要求规范上:建议使用共享资源作为锁对象对于实例方法建议使用this作为锁对象。对于静态方法,建议使用字节码(类名.class)对象作为锁对象/**    假设100个人来调用这个方法, */public static void run(){    synchronized (Account.class){        //这样每次就只能有一个人来调用    }}方式二:同步方法作用:把出现线程安全问题的核心方法给上锁原理:每次只能进入一个线程,执行完毕后自动解锁,其他线程才可以进来执行。格式:修饰符 synchronized 返回值类型 方法名称(形参列表){    操作共享资源}public synchronized void drawMoney(double money) {    String name = Thread.currentThread().getName();    if (this.money>=money){        System.out.println(name+"取钱成功!取了"+money+"元");        //更新余额        this.money-=money;        System.out.println(name+"取款后剩余"+this.money);    }else {        System.out.println(name+"余额不足!");    }}同步方法底层原理同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码如果方法是实例方法:同步方法默认用this作为锁对象,但是代码要高度面向对象如果方法是静态方法:同步方法默认用类名.class作为锁的对象。是同步代码块好还是同步方法好一点?同步代码块锁的范围更小,同步方法锁的范围更大Lock锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象方法名称 说明public ReentrantLock() 获得Lock锁的实现类对象Lock的API方法名称 说明void lock 获得锁void unlock 释放锁public class Account {    private String cardId;    private double money;//余额    // final修饰后:锁对象是唯一和不可替换的    private final Lock lock = new ReentrantLock();    public Account() {    }    public Account(String cardId, double money) {        this.cardId = cardId;        this.money = money;    }    public String getCardId() {        return cardId;    }    public void setCardId(String cardId) {        this.cardId = cardId;    }    public double getMoney() {        return money;    }    public void setMoney(double money) {        this.money = money;    }    public void drawMoney(double money) {        String name = Thread.currentThread().getName();        lock.lock();//上锁        try {            if (this.money >= money) {                System.out.println(name + "取钱成功!取了" + money + "元");                //更新余额                this.money -= money;                System.out.println(name + "取款后剩余" + this.money);            } else {                System.out.println(name + "余额不足!");            }        } finally {            lock.unlock();//释放锁        }    }}线程通信什么是线程通信?如何实现?所谓线程通信就是线程间相互发送数据。线程通信常见形式通过共享一个数据的方式实现。根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。线程通信实际应用模型生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据一般要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完数据后唤醒生产者,然后等待自己。线程通信案例模拟模拟手机接电话系统,有电话就接听,没有电话就等待、线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,切要保证线程安全。/**    模拟线程通信,来电提醒线程,  接电话线程 */public class Phone {    private boolean flag = false;    public void run(){    //a,负责来电提醒的线程        new Thread(new Runnable() {            @Override            public void run() {                try {                    while (true) {                        synchronized (Phone.this){                            if (!flag){                                //代表有来电提醒                                System.out.println("有电话正在呼入!!");                                flag = true;//代表继续等待呼入电话                                Phone.this.notify();                                Phone.this.wait();                            }                        }                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();        //b,负责接电话线程,正式接听了        new Thread(new Runnable() {            @Override            public void run() {                try {                    //不断的接听电话                    while (true) {                        synchronized (Phone.this){                            if (flag){                                //可以接听电话了                                System.out.println("电话接听中,通话了5分钟结束了!");                                flag = false;//代表继续等待呼入电话                                Thread.sleep(5000);                                //唤醒别人,等待自己                                Phone.this.notify();                                Phone.this.wait();                            }                        }                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();    }    public static void main(String[] args) {        //1,创建一部手机对象        Phone huawei = new Phone();        huawei.run();    }}output:有电话正在呼入!!电话接听中,通话了5分钟结束了!有电话正在呼入!!电话接听中,通话了5分钟结束了!Object类的等待和唤醒方法:方法名称 说明void wait() 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或notifyAll()方法void notify() 唤醒正在等待的单个线程void notifyAll() 唤醒正在等待的所有线程注意:上述方法应该使用当前同步锁对象进行调用线程池线程池概述什么是线程池?线程池就是一个可以复用线程的技术。不使用线程池的问题如果用户每发起一个请求,后台就创建一个新的线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。线程池实现的API、参数说明谁代表线程池?JDK5.0起提供了线程池的接口:ExecutorService如何得到线程池对象方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象ThreadPoolExecutor构造器的参数说明线程池常见面试题临时线程什么时候创建?新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。什么时候会开始拒绝任务?核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。小结谁代表线程池?ExecutorService接口ThreadPoolExecutor实现线程池对象的七个参数是什么意思?线程池处理Runnable任务ThreadPoolExecutor创建线程池对象示例ExecutorService1ExecutorService的常用方法方法名称 说明void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行 Runnable任务Future submit(Callable task) 执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务void shutdown() 等任务执行完毕后关闭线程池List shutdownNow() 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务新任务拒绝策略策略 详解ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常 这是不推荐的做法ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行public class MyRunnable implements Runnable {    @Override    public void run() {        for (int i = 1; i <= 5; i++) {            System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==>" + i);        }            try {                System.out.println(Thread.currentThread().getName()+"本任务与线程绑定了,线程进入休眠了");                Thread.sleep(5000000);            } catch (Exception e) {                e.printStackTrace();            }    }}/**    自定义一个线程池对象并测试其特性 */public class ThreadPoolDemo1 {    public static void main(String[] args) {        //1.创建线程池对象        /**         public ThreadPoolExecutor(  int corePoolSize,                                     int maximumPoolSize,                                     long keepAliveTime,                                     TimeUnit unit,                                     BlockingQueue<Runnable> workQueue,                                     ThreadFactory threadFactory,                                     RejectedExecutionHandler handler) {         */        ExecutorService pool = new ThreadPoolExecutor(3,5,                6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());        //2.给任务线程池处理        Runnable target = new MyRunnable();        pool.execute(target);        pool.execute(target);        pool.execute(target);        pool.execute(target);        pool.execute(target);        pool.execute(target);        pool.execute(target);        pool.execute(target);        //创建临时线程        pool.execute(target);        pool.execute(target);        //不创建,拒绝策略被触发        pool.execute(target);        //关闭线程池(开发中一般不会使用)        pool.shutdownNow();//立即关闭,即使任务没有完成,会丢失任务        pool.shutdown();//  会等待全部任务执行完毕之后再关闭    }}output:pool-1-thread-2输出了:HelloWorld ==>1pool-1-thread-2输出了:HelloWorld ==>2pool-1-thread-2输出了:HelloWorld ==>3pool-1-thread-2输出了:HelloWorld ==>4pool-1-thread-3输出了:HelloWorld ==>1pool-1-thread-2输出了:HelloWorld ==>5pool-1-thread-1输出了:HelloWorld ==>1pool-1-thread-1输出了:HelloWorld ==>2pool-1-thread-1输出了:HelloWorld ==>3pool-1-thread-3输出了:HelloWorld ==>2pool-1-thread-3输出了:HelloWorld ==>3pool-1-thread-3输出了:HelloWorld ==>4pool-1-thread-3输出了:HelloWorld ==>5pool-1-thread-1输出了:HelloWorld ==>4pool-1-thread-1输出了:HelloWorld ==>5pool-1-thread-2本任务与线程绑定了,线程进入休眠了pool-1-thread-3本任务与线程绑定了,线程进入休眠了pool-1-thread-1本任务与线程绑定了,线程进入休眠了pool-1-thread-4输出了:HelloWorld ==>1pool-1-thread-4输出了:HelloWorld ==>2pool-1-thread-4输出了:HelloWorld ==>3pool-1-thread-4输出了:HelloWorld ==>4pool-1-thread-4输出了:HelloWorld ==>5pool-1-thread-4本任务与线程绑定了,线程进入休眠了pool-1-thread-5输出了:HelloWorld ==>1pool-1-thread-5输出了:HelloWorld ==>2pool-1-thread-5输出了:HelloWorld ==>3pool-1-thread-5输出了:HelloWorld ==>4pool-1-thread-5输出了:HelloWorld ==>5pool-1-thread-5本任务与线程绑定了,线程进入休眠了Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[Thread-0,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@378bf509[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]线程池处理Callable任务public class MyCallable implements Callable<String> {    private int n;    public MyCallable(int n) {        this.n = n;    }    @Override    public String call() throws Exception {        int sum = 0;        for (int i = 0; i < n; i++) {            sum += i;        }        return Thread.currentThread().getName() + "执行1-" + n + "的结果是:" + sum;    }}public class ThreadPoolDemo2 {    public static void main(String[] args)throws Exception {        //1.创建线程池对象        /**         public ThreadPoolExecutor(  int corePoolSize,                                     int maximumPoolSize,                                     long keepAliveTime,                                     TimeUnit unit,                                     BlockingQueue<Runnable> workQueue,                                     ThreadFactory threadFactory,                                     RejectedExecutionHandler handler) {         */        ExecutorService pool = new ThreadPoolExecutor(3,5,                6, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());        //2.给任务线程池处理        Future<String> f1 = pool.submit(new MyCallable(100));        Future<String> f2 = pool.submit(new MyCallable(200));        Future<String> f3 = pool.submit(new MyCallable(300));        Future<String> f4 = pool.submit(new MyCallable(400));        Future<String> f5 = pool.submit(new MyCallable(500));//        String rs = f1.get();//        System.out.println(rs);        System.out.println(f1.get());        System.out.println(f2.get());        System.out.println(f3.get());        System.out.println(f4.get());        System.out.println(f5.get());    }}output:pool-1-thread-1执行1-100的结果是:4950pool-1-thread-2执行1-200的结果是:19900pool-1-thread-3执行1-300的结果是:44850pool-1-thread-2执行1-400的结果是:79800pool-1-thread-3执行1-500的结果是:124750线程池如何处理Callable任务,并得到任务执行完后返回的结果使用ExecutorService的方法:Future submit(Callable command)Executors工具类实现线程池Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。方法名称 说明public static ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间会被回收掉public static ExecutorService newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为它执行异常而结束,那么线程池会补充一个新线程替代它public static ExecutorService newSingleThreadExecutor ( ) 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池就会补充一个新线程public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的public class ThreadPoolDemo3 {    public static void main(String[] args) {        //1.创建固定线程数的线程池        ExecutorService pool = Executors.newFixedThreadPool(3);        pool.execute(new MyRunnable());        pool.execute(new MyRunnable());        pool.execute(new MyRunnable());        pool.execute(new MyRunnable());    }}output:pool-1-thread-1输出了:HelloWorld ==>1pool-1-thread-1输出了:HelloWorld ==>2pool-1-thread-3输出了:HelloWorld ==>1pool-1-thread-3输出了:HelloWorld ==>2pool-1-thread-3输出了:HelloWorld ==>3pool-1-thread-3输出了:HelloWorld ==>4pool-1-thread-3输出了:HelloWorld ==>5pool-1-thread-1输出了:HelloWorld ==>3pool-1-thread-1输出了:HelloWorld ==>4pool-1-thread-1输出了:HelloWorld ==>5pool-1-thread-2输出了:HelloWorld ==>1pool-1-thread-2输出了:HelloWorld ==>2pool-1-thread-2输出了:HelloWorld ==>3pool-1-thread-2输出了:HelloWorld ==>4pool-1-thread-2输出了:HelloWorld ==>5pool-1-thread-3本任务与线程绑定了,线程进入休眠了pool-1-thread-1本任务与线程绑定了,线程进入休眠了pool-1-thread-2本任务与线程绑定了,线程进入休眠了Executors使用可能存在的陷阱大型并发系统环境中使用Executors如果不注意可能会出现系统风险Executors工具类底层是基于什么方式实现的线程池对象?线程池ExecutorService的实现类:ThreadPoolExecutorExecutors是否适合做大型互联网场景的线程池方案?不合适建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险定时器定时器是一种控制任务延时调用,或者周期调用的技术作用:闹钟、定时邮件发送、定时弹送广告定时器的实现方法方式一:Timer方式二:ScheduledExecutorServiceTimer定时器构造器 说明public Timer() 创建Timer定时器对象方法 说明public void schedule(TimerTask task, long delay , long period) 开启一个定时器,按照计划处理TimerTask任务Timer定时器的特点和存在的问题Timer是单线程,处理多个任务按照顺序进行,存在延时与设置定时器的时间有出入。可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行public class TimerDemo01 {    public static void main(String[] args) {        //1.创建Timer定时器        Timer timer = new Timer();//定时器本身就是一个单线程        //2.调用方法,处理定时任务        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println(Thread.currentThread().getName() + "定时器AAA开始执行一次" + new Date());                try {                    Thread.sleep(5000);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }, 0, 2000);        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println(Thread.currentThread().getName() + "定时器BBB开始执行一次" + new Date());                System.out.println(10 / 0);            }        }, 0, 2000);        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println(Thread.currentThread().getName() + "定时器CCC开始执行一次" + new Date());            }        }, 0, 2000);    }}output:Timer-0定时器AAA开始执行一次Sat Jan 22 23:26:42 CST 2022Timer-0定时器BBB开始执行一次Sat Jan 22 23:26:47 CST 2022Exception in thread "Timer-0" java.lang.ArithmeticException: / by zeroat com.csl.d8_timer.TimerDemo01$2.run(TimerDemo01.java:28)at java.base/java.util.TimerThread.mainLoop(Timer.java:556)at java.base/java.util.TimerThread.run(Timer.java:506)ScheduledExecutorService定时器ScheduledExecutorService是jdk 1.5中引入了并发包,目的是为了弥补Timer的缺陷,ScheduledExecutorService内部为线程池Executors方法 说明public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 得到线程池对象ScheduledExecutorService的方法 说明public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period , TimeuUnit unit) 周期调度方法ScheduledExecutorService的优点基于线程池,某个任务的执行情况不会影响其他定时任务的执行。public class TimerDemo2 {    public static void main(String[] args) {        //1.创建ScheduledExecutorService线程池,做定时器        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);        //2.开启定时任务        pool.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                System.out.println(Thread.currentThread().getName() + "执行输出:AAA");                try {                    Thread.sleep(100000);                } catch (Exception e) {                    e.printStackTrace();                }            }        }, 0, 2, TimeUnit.SECONDS);        pool.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                System.out.println(Thread.currentThread().getName() + "执行输出:BBB");                System.out.println(10 / 0);            }        }, 0, 2, TimeUnit.SECONDS);        pool.scheduleAtFixedRate(new TimerTask() {            @Override            public void run() {                System.out.println(Thread.currentThread().getName() + "执行输出:CCC");            }        }, 0, 2, TimeUnit.SECONDS);    }}output:pool-1-thread-2执行输出:BBBpool-1-thread-3执行输出:CCCpool-1-thread-1执行输出:AAApool-1-thread-3执行输出:CCCpool-1-thread-3执行输出:CCCpool-1-thread-2执行输出:CCC线程并发与并行正在运行的程序(软件) 就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的并发的理解CPU同时处理线程的数量有限CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。并行的理解在同一个时刻上,同时有多个线程在被CPU处理并执行————————————————原文链接:https://blog.csdn.net/qq_46002941/article/details/122646052
  • [技术干货] javaSE—网络原理
    1,网络发展史1)独立模式我们刚开始使用计算机呢,客户端的数据不是共享的,如果有一个人想要办理业务,而这个业务所需的资源是在三台电脑上,那么这个人就需要在这三个电脑上不断的办理任务,而其他人想要办理业务,还需要等到前一个人办理完,效率非常低,那怎么办,我们就改进;2)网络互联接下来我们就使用网络进行计算机资源的共享,让多个计算机可以一起办理业务,达成数据共享,即网络通信,我们可以根据网络互联的规模分为局域网和广域网;3)局域网LAN局域网是本地,局部构建的一种私有网络,又被称为内网,局域网内的主机能够实现网络通信,局域网和局域网在没有连接的情况是不能进行通信的;组件局域网等待方式也有很多种,可以通过网线直连,也可以通过交换机相连,还可以通过集线器相连,还可以通过路由器连接交换机在与多个主机相连;4)广域网WAN广域网就是多个局域网完成了连接,很多很多的局域网都能进行网络通信,我们其实可以把咱们中国的网络看成一个巨大的广域网,我们管内部叫做内网,外面的就是我们常说的外网,有很多人可能对此不满,但这也是保护我们的一种方式,起码我们活的挺快乐的;不说了,再说被封了,哈哈哈哈哈哈;2,网络通信基础1)IP地址那么,广域网这么大,我们怎么能准确找到每个主机的所在呢,我们就使用IP地址来标识每个网络主机和网络设备的网络地址,我们可以通过CMD看自己主机的地址,输入这个命令ipconfig,就能看到了,那个IPv4地址就是我们的地址啦;2)端口号端口是啥玩意,我们有了地址,那么电脑发送或者我们接收了一个数据,难道我们只是通过地址就能知道吗,我们知道了地址,但不知道是哪个软件发送或者接收这个数据,比如发来一个QQ的数据报,那我们去给CSDN吗,不,我们应该是找到QQ的端口号,之后把这个数据给到QQ,让QQ来做相应的操作;我们可以把网络通信可以看成送快递,我们把IP地址看作收货地址,把端口号看作收件人;3)认识协议我们现在能找到地址和端口号了,我们网络传输的是二进制的数据,那么我们传入一段二级制指令,对方是怎么知道我们传的是什么东西呢,之前说过,图片,音频和视频都是二进制的指令,我们到一个数据报,我们怎么知道这是啥文件呢,去使用什么编码方式呢,所以就需要大家都统一一下,我们就约定网络传输的格式,我们就管它叫协议;协议的最终体现呢,就是网络传输数据报的格式;4)五元组在TCP/IP协议中,我们使用五元组来标识网络通信:1,源IP:标识源主机2,源端口号:标识源主机中该次通信发送的进程3,目的IP:标识目的主机4,目的端口号:标识源主机中该次通信接收的进程5,协议号:标识发送进程和接收进程双方约定的格式5)协议分层啥事协议分层呢,我们的协议很多,很复杂,我们把它分为不同层次的协议,让每个协议尽可能有自己的功能,OSI七层模型和TCP/IP五层模型,都把每层划分了很多不同的功能;OSI七层调用模型:层数    名称    功能    功能概览7    应用层    针对特定应用的协议    比如我们发送邮件,就用电子邮件协议,实现登录,就要使用登录协议6    表示层    数据固有格式和网络标准格式的转换    我们将接收的信息会根据网络标准格式转换为标准的信息5    会话层    通讯管理,负责建立和断开通讯    何时建立连接,何时断开连接和建立多久的连接;4    传输层    管理两个节点之间的数据传输,负责可靠传输    检查是否有数据丢失3    网络层    地址管理与路由选择    会考虑经过哪些路由到达地址2    数据链路层    互联数据的传送和识别数据帧    .....数据帧和比特流之间的转换1    物理层    以‘0’,‘1’代表电压高低,灯光闪灭,界定连接器和网线的规格    比特流与电子信号的转换这个我们大概了解即可,我们也是从网上扒下来的,我们会重点去学习应用层; TCP/IP通讯协议:TCP/IP模型其实就是OSI七层协议模型,只不过把OSI重新划分了一下,TCP/IP通讯协议采用五层的层级结构,每一层都可以呼叫下一层来给自己提供网络需求;5层,应用层:负责应用程序间沟通,如简单电⼦邮件传输(SMTP)、文件传输协议(FTP)、网络远 程访问协议(Telnet)等。我们的⽹络编程主要就是针对应⽤层。4层,传输层:负责两台主机之间的数据传输。如传输控制协议(TCP),能够确保数据可靠的从源主机发 送到⽬标主机,还有UDP。3层,网络层:负责地址管理和路由选择。例如在IP协议中,通过IP地址来标识⼀台主机,并通过路由表 的⽅式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)⼯作在⽹路层。2层,数据链路层:负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步(就是说从⽹线上 检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就⾃动重发)、数据差错校验等⼯作。 有以太⽹、令牌环网,⽆线LAN等标准。交换机(Switch)工作在数据链路层。1层,物理层:负责光/电信号的传递方式。比如现在以太网通用的网线(双绞线)、早期以太网采用的的同 轴电缆(现在主要⽤于有线电视)、光纤,现在的wifi无线网使用电磁波等都属于物理层的概念。物理 层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)工作在物理层。这也是扒来的,下面我用自己的理解讲讲:——————5,应用层:就是我们拿到了包裹(数据包)后怎么样~4,传输层:任意两个设备的通信,不考虑传输过程,只考虑起点和终点;3,网络层:任意两个设备的通信,考虑中间的过程,传输过程可能有很多的交换机啥的;2,数据链路层:完成相邻两个设备之间的通信;1,物理层:规定网络通信中一些硬件设施的符合要求:我们拿送快递来举一个例子,比如我们网购一个手机,我们拿到手机之后怎么使用,就是应用层;商家发货的寄件方后收件方的地址,就是传输层;物流公司关心包裹是咋传输的,就是网络层;大车司机关心今天送到哪个地方,一个一个节点之间,就是数据链路层;TCP/IP协议栈其实里面包含了很多协议,但是最重要的就是TCP/IP协议了,我们再来谈谈主机,交换机和路由器都涉及到哪些层次:1,主机    工作涉及到    物理层到应用层(通过应用层满足数网络通信的要求);2,路由器    工作涉及    物理层到网路层(组件局域网,进行网络数据报的转发);3,交换机    工作涉及到    物理层到数据链路层(对路由器接口的扩展,不需要考虑组网问题);3,网络通信基本流程不同的协议层对数据包有不同的叫法,在传输层叫段,在网络层叫数据报,在数据链路层叫数据帧;应用层数据包,往往是结构化数据:我们发送数据的时候,会把结构化数据变成二进制比特流或者字符串,我们叫做序列化;我们接收数据的时候,会把二进制比特流或者字符串变成结构化数据,我们叫做反序列化;流程:我们使用QQ,发送Hello给对方;1,应用程序获取用户输入,构造一个应用层数据包,会遵守应用层协议(往往是程序员自己定制的)我们假设约定的格式为(发送者QQ,接收着QQ,消息时间,消息正文);2,应用层调用传输层API(socket api)把数据交给传输层,把数据拿到后,构造出传输层数据包,传输层的协议主要就是TCP和UDP;我们拿TCP数据包举例,TCP数据包 = TCP报头(TCP功能的相关属性) + TCP载荷(就是应用层的数据包);数据包就变成这样的了; 3,传输层数据包构造好之后,就会调用网络层的API,把传输层的数据包交给网络层,网络层来处理数据包,网络最重要的协议,IP协议我们又会加一个IP报头,IP数据包 = IP报头(包含很多信息,包括源IP和目的IP)  +  IP载荷(整个传输层的数据包);在这些报头中还包含了上一层所用协议的内容,4,IP协议继续调用数据链路层的API,把IP协议交给数据链路层,数据链路层的核心协议,以太网,根据以太网这个协议会在网络层的基础上进一步加工以太网数据帧 = 帧头 + 载荷 + 帧尾5,以太网继续把数据帧给硬件设备(网卡)网卡会把二进制的比特流发送出去,这才成功的发送出去 。发送数据我们我们从上到下的过程我们称为封住,反过来接收数据的时候我们从下到下的过程我们称为复用;————————————————           原文链接:https://blog.csdn.net/2301_79083481/article/details/146917352
总条数:737 到第
上滑加载中