首页 > 编程语言 >Java 业务开发常见错误 100 例

Java 业务开发常见错误 100 例

时间:2023-10-30 11:00:10浏览次数:38  
标签:加锁 Java 错误 wrong 线程 static 100 Data public

第一讲:使用并发工具库类,建议

容易犯的四类错:

  1. 只知道使用并发工具,但并不清楚当前线程的来龙去脉,解决多线程问题却不了解线程;--错误

  2. 误以为使用了并发工具就可以解决一切线程安全问题,期望通过把线程不安全的类替换为线程安全的类来一键解决问题。--错误

  3. 没有充分了解并发工具的特性,还是按照老方式使用新工具导致无法发挥其性能。

  4. 没有了解清楚工具的适用场景,在不合适的场景下使用了错误的工具导致性能更差。

两点建议:

  1. 一定要认真阅读官方文档(比如 Oracle JDK 文档)。充分阅读官方文档,理解工具的适用场景及其 API 的用法,并做一些小实验。了解之后再去使用,就可以避免大部分坑。

  2. 如果你的代码运行在多线程环境下,那么就会有并发问题,并发问题不那么容易重现,可能需要使用压力测试模拟并发场景,来发现其中的 Bug 或性能问题。

 

第二讲:代码加锁

加锁范围

import lombok.Getter;

import java.util.stream.IntStream;

public class Test {
    public static void main(String[] args) throws Exception {
        IntStream.rangeClosed(1, 100000).parallel().forEach(i -> new Data().wrong());
        System.out.println(Data.getCounter());
    }

    static class Data {
        @Getter
        private static int counter = 0;

        public Data() {
        }

        public  synchronized void wrong() {
            counter++;
        }
    }
}

以上代码的输出结果为:36529;为什么不是100000。

问题分析:在非静态的 wrong 方法上加锁,只能确保多个线程无法执行同一个实例的 wrong 方法,却不能保证不会执行不同实例的 wrong 方法。而静态的 counter 在多个实例中共享,所以必然会出现线程安全问题。

修改后:

import lombok.Getter;

import java.util.stream.IntStream;

public class Test {
    public static void main(String[] args) throws Exception {
        IntStream.rangeClosed(1, 100000).parallel().forEach(i -> new Data().right1());
        System.out.println(Data.getCounter());
    }

    static class Data {
        @Getter
        private static int counter = 0;
        private static Object locker = new Object();  // 添加一个静态类锁

        public Data() {
        }

        public void right1() {
            synchronized (locker) {
                counter++;
            }
        }
    
     public static synchronized void right2(){
counter++;
}
    }
}

  以上有两种方法可以解决这个问题,一个是代码块级别的synchronized ,另一个是方法上标记 synchronized 关键字;

 这两种解决方式有什么差异:

1- 代码块级别:更灵活一些,锁的范围也更小一些; 最主要的差异,非静态的同步方法是实例级别的锁;

2- static方法级别:灵活性差些。是类级别的锁;

 作者给的答案是第一种,第二种属于滥用synchronized的做法,原因如下:

1- 对于无状态的方法,没有必要;

2- 可能会极大地降低性能。

即使我们确实有一些共享资源需要保护,也要尽可能降低锁的粒度,仅对必要的代码块甚至是需要保护的资源本身加锁。

如果精细化考虑了锁应用范围后,性能还无法满足需求的话,我们就要考虑另一个维度的粒度问题了,即:区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁。

 

多把锁要小心死锁问题

日志:Found one Java-level deadlock

 死锁:线程4等待线程3,线程3等待线程4;

错误代码:

    @GetMapping("wrong")
    public long wrong() {
        long begin = System.currentTimeMillis();
        //并发进行100次下单操作,统计成功次数
        long success = IntStream.rangeClosed(1, 100).parallel()
                .mapToObj(i -> {
                    List<Item> cart = createCart();
                    return createOrder(cart);
                })
                .filter(result -> result)
                .count();
        log.info("success:{} totalRemaining:{} took:{}ms items:{}",
                success,
                items.entrySet().stream().map(item -> item.getValue().remaining).re
                System.currentTimeMillis() - begin, items);
        return success;
    }

正确代码(排序):

    @GetMapping("right")
    public long right() {
        ...
        long success = IntStream.rangeClosed(1, 100).parallel()
                .mapToObj(i -> {
                    List<Item> cart = createCart().stream()
                            .sorted(Comparator.comparing(Item::getName))
                            .collect(Collectors.toList());
                    return createOrder(cart);
                })
                .filter(result -> result)
                .count();
        ...
        return success;
    }

  

如果业务逻辑中锁的实现比较复杂的话,要仔细看看加锁和释放是否配对,是否有遗漏释放或重复释放的可能性;并且要考虑锁自动超时释放了,而业务逻辑却还在进行的情况下,如果别的线线程或进程拿到了相同的锁,可能会导致重复执行。

我理解的是: 1- 锁机制排序加锁;  2- 超时释放锁;  这两种方式是目前解决死锁的两个基本方法。

 

  

标签:加锁,Java,错误,wrong,线程,static,100,Data,public
From: https://www.cnblogs.com/parent-absent-son/p/17797288.html

相关文章

  • CPU 100%问题排查
    引用:https://blog.csdn.net/qq_37515544/article/details/123921604https://blog.csdn.net/yujing1314/article/details/114524668 一、定位哪个程序占用的CPU较高linux命令:top    二、jstack使用2.1栈信息输出命令格式:jstackpid>文件信息eg:jstack5115>a.tx......
  • SpringBoot3特性——错误信息Problemdetails
    SpringFramework6实现了HTTPAPI规范RFC7807的问题详细信息。在本文中,我们将学习如何在SpringBoot3RESTAPI(使用SpringFramework6)中处理异常,并使用ProblemDetailsAPI提供错误响应。详见https://www.sivalabs.in/spring-boot-3-error-reporting-using-proble......
  • Java基本语法
    一、基本框架1)概念一个Java程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作对象(object):代表现实世界中可以明确标识的一个实体,存在独特的标识、状态和行为。例如,一条鱼是一个对象,它的状态有:颜色、品种;行为有:漫游、鱼跃等。类(class):是创建对......
  • 黑马程序员2023新版JavaWeb开发教程学习笔记
    前言该笔记灵感来源于B站《黑马程序员2023新版JavaWeb开发教程,实现javaweb企业开发全流程(涵盖Spring+MyBatis+Springboot》源视频地址:黑马程序员2023新版JavaWeb开发教程个人声明:本文记录个人在进行该视频学习中的知识总结,帮助大家能更快地进行对该视频内容的学习;由于该视频对......
  • java中native源码查找方法
    以Object的hashCode()方法为例:1.下载openjdk源码或从github中查找,这里以github中查找为例;2.GitHub中查找https://github.com/bpupadhyaya/openjdk-8/tree/master/hotspot源码;3.搜索到Object.c源码文件,并查找hashCode字眼,如下所示: 4.由上可知,hashCode方法实际是调用的jvm.c......
  • Java基础 反射获取构造方法
    在Java中,万物皆对象。比如,字节码文件可以看作是Class这个类的对象;Constructor这个类是用来描述构造方法的,所以这个类的对象就表示构造方法的对象;Field这个类是用来描述成员变量的,所以这个类的对象就表示成员变量的对象;Method 这个类是用来描述成员方法的,所以这个类的对......
  • Java基础 获取 class 对象的三种方式
    ①Class.forName("全类名")  →  最为常用全类名:包名+类名Class的首字母是大写的,所以是一个类名,是用来描述字节码文件的。这个类里面有一个静态方法叫 forName,参数可以传递一个类的全类名,可以获取到参数的字节码文件对象 ②类名.class  →  这种方式更......
  • Java实现分页的方式有哪些?
    1、手动分页不使用任何框架,用limt分页selectxxfromtab_alimt#{pageNo},#{pageSize}2、RowBounds分页(不推荐)这个是内存分页,它的原理是一次性查出所有数据,然后在内存里进行分页,占内存。3、PageHelper分页(推荐)Mybatis分页插件pom依赖:<dependency><groupId>com.g......
  • Java基础 反射
     反射允许对封装类的字段(成员变量)、方法(成员方法)和构造函数(构造方法)的信息进行编程访问  获取字段(成员变量)、成员方法和构造方法的时候,我们不是从Java文件中获取的,而是从class字节码文件当中获取的所以我们首先要先学习如何获取class字节码文件的对象 ......
  • Java项目报错java.lang.UnsupportedOperationException: null 处理
    编写好业务代码后运行项目时报错: 人都麻了!查看控制台信息sql语句,发现原本输出6条结果的,却只查出一条就报错了。查了一下关于:2023-10-29T23:08:23.431+08:00ERROR29156---[nio-8099-exec-1]o.a.c.c.C.[.[.[/].[dispatcherServlet]  :Servlet.service()forservlet......