线程安全(上)

2023-11-13

前言:

在多线程中,并不是说知道怎么使用就完事了,学习完如何使用多线程之后,我们了解到多线程的并发性和随机调度的特性是我们程序员不容易控制的,所以一旦操作不当就会带来许多安全问题,那我们就开始学习线程安全吧!

目录

1.使用多线程带来的风险(硬件角度分析)

2.产生线程安全的原因(5点)

3.线程安全的解决方法 

3.1synchronized 监视器锁(monitor lock)

3.3 synchronized的4种修饰写法(实际上2种)

4.volatile关键字(强制读内存)  

5.synchronized与volatile区别


1.使用多线程带来的风险(硬件角度分析)

为了让我们好的了解学习什么是线程安全,我以一个经典的触发线程安全的实例来让大家感受一下多线程带来的安全问题。

实例

有这样一个Java程序,要使用多线程的方式对同一个变量进行修改操作,那此时就会触发线程安全问题,我们两个线程分别对同一变量自增到5w,那我们的期望结果就是两个线程对同一变量共同修改的结果就改为10w,但实际结果真的如想想如此吗?。

package Boke;
class Counter1{
    public int cout;
    public void increase(){
        cout++;
    }

    public int getCout(){
        return cout;
    }
}

public class SameUpdate {
    public static void main(String[] args) throws InterruptedException {
 System.out.println("目标结果是:100000");
        Counter1 counter1=new Counter1();
        //线程t1让cout成员自增到5w
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5_0000; i++) {
                counter1.increase();
            }

        });
        t1.start();
        //线程t2让cout成员自增到5w
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5_0000; i++) {
                counter1.increase();
            }
        });

        t2.start();

        //等待两个线程都结束
        t1.join();
        t2.join();
        System.out.println("多线程处理下:"+counter1.getCout());
    }
}

 由运行结果可知实际结果并不是我们想象的一个线程对变量自增5w,两个线程就是10w,那想要搞清楚发生这种情况的原因,那就要先从硬件的角度搞明白计算机是怎么将一条自增语句执行成功的,当执行一段代码,要先后经历第1步从内存读取命令(一条Java代码对应一条或者多条指令)到CPU中,例如上述的自增操作就对应了3条指令,第2步在CPU寄存器中完成相应的运算,第3步返回运算好的结果到内存中。

对于上述业务(单线程情况下)

 可见在单线程的情况下,不管调度器怎么随机执行指令顺序,其结果都是一样的,即都要依次经过读取内存,cpu运算,返回运算结果。

对于上述业务(多线程程情况下)

 可见多线程的情况下,就会发生这种自增数遗失的尴尬情况,所以通过这个经典简单的例子我们就可以看出,使用多线程看似人畜无害,其实可能出现大问题。

2.产生线程安全的原因(5点)

什么是线程安全呢?简单来说就是在操作系统随机调度的过程中,代码指令的先后执行顺序有无数种可能,如果在这样的情况下,代码逻辑都不会出现问题,那此时我们认为线程是安全的。

标准定义

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线 程安全的。

那产生线程安全都有哪些原因呢?

原因1

第一种产生线程安全的原因呢,也是最熟悉也是最无奈的操作系统以随机调度,抢占式执行的方式来调度我们的多线程,这也是产生线程安全的"罪恶之源",我们对于这种随机调度的方式是无能为力的。

原因2(原因3的一种)

这第二种,也就是我们上边的哪个用多线程修改同一的一个变量,其实啊,这种原因的本质也是操作系统在执行指令的过程中,采用随机执行的结果,对于这种情况我们可以通过调整程序设计尽量避免上述的情况发生。

注意

 注意分析这就话,多线程修改同一个变量,也就是说只有同时满足多线程和修改操作以及同一变量的前提下才有可能会触发线程安全。

1.单线程修改同一变量,没事!

2.多线程读取同一变量,没事!

3.多线程修改不同变量,没事!

 原因3

第3种情况是由于操作是否为原子性的操作,当有的线程操作不为原子性时,极大可能发生线程安全问题,什么意思呢?如果一个线程正在对一个变量修改操作,中途其他线程插入进来了也对这个变量进行修改,此时这个操作被打断了,结果就可能是错误的。原子性的操作也就是不可分割的最小操作,例如"="赋值操作,非原子性操作例如上方的自增操作(一共有加载,运算,返回3步操作),为了解决这种情况,我们往往会将非原子性打包成为一个整体(类似于MySQL数据库的事物),此时这个操作就变成了原子性的操作了,我们在多线程这里可以通过加锁( synchronized)的方式完成这种功能(后面详讲)。

原因4

内存可见性,一种由于编译器,JVM或者操作系统"优化"而产生的线程安全问题,经常产生此类问题的场景为一个线程不断读取和判断,另一个线程进行修改 ,所谓优化就是开发者在编写编译器或则操作系统时,考虑到我们写的代码可能不够好,而编写的一系列二进制指令,例如不断重复地读取和判断,读取是读内存操作,判断是读cpu操作,而读内存要比读cpu多耗时3~4个数量级,所以我们的编译器就会将重复的读内存操作优化为只读第一次,直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存),因为反复读取重复的内存数据相比读cpu十分的耗时,所以如果在多线程的情况下,如果另一个线程对数据进行了修改,那读取并判断数据的那个线程就不能及时读取到最新数据,这就是内存可见性,从而引发线程安全问题。

当然我们为了避免操作系统或者编译器这种在多线程情况下的"图快""不图对"优化,我们Java也给我们提供了相应的解决方法,那就是对要操作的变量前加上volatile关键字, 能保证内存可见性 (后面详讲)

原因5

指令重排序,产生原因也是和内存可见性问题一样由于编译器的"优化"所致,指令重排序是啥呢,指令重排序就是调整了代码的执行顺序,也是一种优化行为,可以达到加快执行速度的好处,如果还不懂,请看简图。

 在单线程的情境下,指令重排序的确可以达到加快执行速度的优化效果,但是在多线程里可能就会出现问题,例如我们要实例化一个对象,Student t=new Student(),那这句代码会先后依次执行,1创建内存空间,2在内存空间上构造对象,3将该内存

的引用赋值给t,在单线程下,交换2和3的执行继续,其实并无大碍,但是在多线程的场景下,如果还有一个线程在读取t的引用就会出现问题,我们同样可以使用volatile关键字防止出现指令重排序。

小结

3.线程安全的解决方法 

要知道,要想解决所有的线程安全,不是一件容易的事,例如上边我们介绍到的,产生线程安全的根源方法系统随机调度,我们就解决不了,所以我们这里主要介绍如何解决修改操作不是原子性的,加锁和系统优化产生的线程安全问题。

3.1synchronized 监视器锁(monitor lock)

synchronized是Java中的关键字,是一种同步锁,会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待,注意是同一个对象才会产生阻塞。

进入 synchronized 修饰的代码块 , 相当于 加锁
退出 synchronized 修饰的代码块 , 相当于 解锁
注意
1.synchronized永远都是只对于"对象"加锁的,synchronized 用的锁是存在 Java 对象头里的。 ,例如"类对象"和"普通对象",被加锁的对象实际上就是对象头里与加锁相关的标记信息被修改了,当其他线程都想对同一个对象进行加锁时(也就是都想获取同一把锁时),那这些线程就会进入了阻塞等待,当给对象加锁的线程释放锁之后,其他线程才有机会获取到锁,为什么说是有机会获取到锁呢?因为如果有多个线程都想要获取到同一把锁的话,那她们之间就会发生锁竞争,需注意的是,只有两个线程针对同一个对象加锁时,才会发生锁竞争,如果对于两个不同的对象加锁,则不会有锁竞争。
阻塞等待
针对每一把锁 , 操作系统内部都维护了一个等待队列 . 当这个锁被某个线程占有的时候 , 其他线程尝试进行加锁, 就加不上了 , 就会阻塞等待 , 一直等到之前的线程解锁之后 , 由操作系统唤醒一个新的线程, 再来获取到这个锁。
注意
  • 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 也就是操作系统线程调度的一部分工作.
  • 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B C 先来的, 但是 B 不一定就能 获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则,这就是所谓的锁竞争.

3.2 synchronized的使用及修饰的对象(3个案例)

它修饰的对象有以下几种

 1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象,类对象; 
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

实例1 

例如我们还是用两个线程对同一个变量进行自增操作(上边多个线程修改同一变量的线程安全问题的实例)作为例子,现在我们通过使用synchronized来解决这个由于操作是非原子操作而引发的线程问题

package Boke;
class Counter1{
    public int cout;
    //将自增操作所属的对象加锁
    //将自增操作打包为原子操作
    public synchronized void increase(){
        cout++;
    }

    public int getCout(){
        return cout;
    }
}

public class SameUpdate {
    public static void main(String[] args) throws InterruptedException {
        Counter1 counter1=new Counter1();
        System.out.println("目标结果是:100000");
        //线程t1让cout成员自增到5w
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5_0000; i++) {
                counter1.increase();
            }

        });
        t1.start();
        //线程t2让cout成员自增到5w
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5_0000; i++) {
                counter1.increase();
            }
        });

        t2.start();

        //等待两个线程都结束
        t1.join();
        t2.join();
        System.out.println("多线程处理下实际结果为:"+counter1.getCout());
    }
}

 

由运行结果可知,当我们给increace()方法前添加关键字 synchronized也就是对该this对象进行加锁,将自增操作打包为原子操作时,此时多线程处理结果达到我们的预期值。
实例2(类对象)
再来看一个多线程代码,我们通过 synchronized分别对类中的方法进行修饰达到对 类对象 进行加锁操作,观察多线程之间因对同一个对象加锁而产生阻塞等待,引发的代码执行过程,如下列例子我们预期的结果是线程t1比线程2开始执行,且比t2先执行完。
package Boke;
class Sock{
    //该方法实际上是对类对象(仅1个)加锁
    public synchronized static void fun1(){
        System.out.println("t1获得锁竞争(类对象加锁):fun1()开始");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1释放锁,fun1()结束");
    }
    //与fun1()方法一样,也是对类对象加锁
    //只不过是代码书写不同
    public static void fun2(){
        synchronized (Sock.class){
            System.out.println("t2获得锁竞争(类对象加锁):fun2()开始");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2释放锁,fun2()结束");
        }
    }
    
}
public class suo {
    public static void main(String[] args) throws InterruptedException {
        Sock sock=new Sock();
        Thread t1=new Thread(()->{
            Sock.fun1();
            System.out.println("t1 开始");
           
            System.out.println("t1 结束");
        });


        Thread t2=new Thread(()->{
            Sock.fun2();
            System.out.println("t2 开始");
           
            System.out.println("t2 结束");
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }
}

 有运行结果和代码可知,因为fun1()和fun2()都是对类对象加锁,线程t1和线程t2第一行代码都想对类对象进行加锁,那此时就会产生锁竞争,所以后拿到锁的线程就会进入线程等待,线程t1先拿到锁,且fun1()和fun2()内部执行时间都较长,所以先拿到锁的线程先执行结束,这是线程对同一个类对象加锁的情况。

实例3(实例化对象)

我们通过synchronized分别对类中的方法进行修饰达到对实例化对象进行加锁操作,观察多线程之间因对同一个对象加锁而产生阻塞等待,引发的代码执行过程,例如下列例子我们预期结果是,线程t1和线程t2都先打印开始,但是t1比t2先结束。

package Boke;
class Sock{
  
    //实际上是对实例化对象加锁(可有多个)
    public synchronized void fun3(){
        System.out.println("t1获得锁(实例对象加锁),fun3()");
        for (int i = 0; i < 50000; i++) {
            i++;
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1释放锁,fun3()结束");
    }
    //与fun4()方法一样,只不过代码书写不同罢了
    public void fun4() {
        synchronized (this){
            System.out.println("t2获得锁(实例对象加锁),fun4()");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2释放锁,fun4()结束");
        }
    }
}
public class suo {
    public static void main(String[] args) throws InterruptedException {
        Sock sock=new Sock();
        Thread t1=new Thread(()->{
         
            System.out.println("t1 开始");
            sock.fun3();
            System.out.println("t1 结束");
        });


        Thread t2=new Thread(()->{
           
            System.out.println("t2 开始");
            sock.fun4();
            System.out.println("t2 结束");
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }
}

 结果可知,的确达到了期望结果,通过synchronized对类的普通方法修饰,都达到了对this对象进行加锁,且调用fun3()和fun4()的实例化对象都是sock,那当两个线程并发执行打印完第一句话之后(大概率情况下,具体情况还得看调度情况),t1线程和t2线程开始进行this对象的锁竞争,由于fun3()和fun4()的内部时间都较长,所以先拿到锁的线程先结束。

3.3 synchronized的4种修饰写法(实际上2种)

使用并书写synchronized的方法已经在上方演示过了,其实写法也就两种,只是针对的加锁对象不同罢了,这里总结一下

第1种synchronized修饰方法

//synchronized修饰静态方法
//该方法实际上是对类对象(仅1个)加锁
    public synchronized static void fun1(){
        System.out.println("t1获得锁竞争(类对象加锁):fun1()开始");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1释放锁,fun1()结束");
    }

//synchronized修饰普通成员方法
//实际上是对实例化对象加锁(可有多个)
    public synchronized void fun3(){
        System.out.println("t1获得锁(实例对象加锁),fun3()");
        for (int i = 0; i < 50000; i++) {
            i++;
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1释放锁,fun3()结束");
    }

 这两种都是在方法名前加上synchronized,前者是对类对象加锁(仅有一个),后者是对当前this对象加锁(不止一个),一个实例化对象对应一个this对象。 

 第2种synchronized修饰代码块

 //synchronized修饰静态代码块
    public static void fun2(){
        synchronized (Sock.class){
            System.out.println("t2获得锁竞争(类对象加锁):fun2()开始");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2释放锁,fun2()结束");
        }
    }

//synchronized修饰普通代码块
 public void fun4() {
        synchronized (this){
            System.out.println("t2获得锁(实例对象加锁),fun4()");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2释放锁,fun4()结束");
        }
    }

 这两种都是在方法的代码块中使用synchronized,前者是对类对象加锁(仅有一个),后者是对当前this对象加锁(不止一个),一个实例化对象对应一个this对象。 

小结 

不管是第一种写法还是第二种写法,其实他们在本质上都是没有差别的,只不过是形式上的差异罢了,他们都是对对象进行加锁(对对象头里的锁标记进行修改),记住这个概念就行了。

4.volatile关键字(强制读内存)  

volatile是一个特征修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值,这个关键字主要可以解决上边提到的内存可见性问题,使用方法就是在要进行修改的变量前加上volatile,注意该关键字只能使用在类的数据成员变量前。 

实例

这个代码演示了内存可见性的问题,我们期望的结果是通过t1线程和t2线程并发执行,当线程t2对变量进行修改后,线程t1就立即执行完毕。

package Boke;

import java.util.Scanner;

public class See {
    //用两个线程实现出内存可见性线程安全问题
    static int cout=0;
    public static void main(String[] args) throws InterruptedException {
        
        //线程t1负责对变量的读和判断
        Thread t1=new Thread(()->{
            System.out.println("cout==0,t1开始");
            //此处编译器已对重复读取的指令进行优化(只读第一次)
            while(cout==0){
                //让编译器产生优化
            }
            System.out.println("cout!=0,t1结束");
        });
        
        //线程t2负责对t1读取判断的变量进行修改,控制线程t1退出
        Thread t2=new Thread(()->{
            System.out.println("t2开始");
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入一个非零值结束线程t1");
            cout=scanner.nextInt();
            System.out.println("t2结束");
        });

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

        t1.join();
        t2.join();
    }

}

 可得到的实际情况是当修改了变量值为非0时,线程t1并没有结束,此时因为编译器优化而产生了线程安全问题(详情请看文章开头介绍),此时我们就可以通过volatile关键字对阻止编译器的这种优化,也就是给这个变量加上了"内存屏障"(二进制指令),JVM在读取这个这个变量时,由于内存屏障,那这个变量就会每次都从内存中读取。

添加volatile后

package Boke;

import java.util.Scanner;

public class See {
    //用两个线程实现出内存可见性线程安全问题

    //此处使用volatile对成员变量进行修饰,防止编译器优化
    volatile static int cout=0;
    public static void main(String[] args) throws InterruptedException {
        // int a=0;

        //线程t1负责对变量的读和判断
        Thread t1=new Thread(()->{
            System.out.println("cout==0,t1开始");
            //此处编译器已对重复读取的指令进行优化(只读第一次)
            while(cout==0){
                //让编译器产生优化
            }
            System.out.println("cout!=0,t1结束");
        });

        //线程t2负责对t1读取判断的变量进行修改,控制线程t1退出
        Thread t2=new Thread(()->{
            System.out.println("t2开始");
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入一个非零值结束线程t1");
            cout=scanner.nextInt();
            System.out.println("t2结束");
        });

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

        t1.join();
        t2.join();
    }

}

 如图加上volatile关键字之后,内存可见性问题得到解决,但其实解决上述代码的内存可见性问题并不是一定要使用volatile关键字,因为这里编译器优化的原因是t1中循环的速度过快,如果适当在循环中加上一些代码减慢循环的速度,那其实也是能避免这种优化行为的,但是实际开中我们并不能确定编译器是否会对我们的代码进行优化,所以一般还是加上volatile最为保险。

5.synchronized与volatile区别

1.synchronized功能:可以保证操作的是原子的,也可以保证内存可见性,也可避免指令重排序。

2.volatile功能:能保证内存可见性和避免指令重排序,但不能保证操作是原子的,。

3.synchronized既能保证操作原子性又能保证内存可见性也可避免指令重排序。,而volatile不能保证操作原子

注意

即使看上去synchronized好像是个"宝贝",它能解决volatile能解决的和不能解决的问题,但实际上,我们在写代码的过程中,因避免一股脑的使用synchronized,因为synchronized是会导致线程阻塞等待的,而正是在这个阻塞过程中,你并不能预测会发生些什么,且当线程再次唤醒时,也不能具体把握时间,造成不必要的时间和cpu资源浪费,可以这样说,一旦盲目的使用synchronized那可以说是与"高性能"没干系了,而我们volatile的优点正是不会使线程进入阻塞等待。

希望以上知识能对你有帮助

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程安全(上) 的相关文章

  • 在Java中将*s打印为三角形?

    我在 Java 课程中的作业是制作 3 个三角形 一份左对齐 一份右对齐 一份居中 我必须为什么类型的三角形制作一个菜单 然后输入需要多少行 三角形必须看起来像这样 到目前为止 我能够完成左对齐的三角形 但我似乎无法获得其他两个 我尝试用谷
  • ScheduledThreadPoolExecutor如何在特定时间运行任务?

    特别是 它是否像这样在内部实现了 while true 循环 while System currentTimeMillis lt timeToRunTask Thread sleep 1000 doTask From http grepco
  • 如何配置 Spring-WS 以使用 JAXB Marshaller?

    感谢您到目前为止对此的帮助 我正在更新问题 因为我没有显示我需要的所有内容 并显示了建议的更改 肥皂输出仍然不是我想要的 servlet xml
  • 如何在Java中优雅地处理SIGKILL信号

    当程序收到终止信号时如何处理清理 例如 我连接到一个应用程序 希望任何第三方应用程序 我的应用程序 发送finish注销时的命令 发送该信息最好说什么finish当我的应用程序被破坏时的命令kill 9 编辑1 kill 9无法被捕获 谢谢
  • JavaFX 2.0 FXML 子窗口

    经过多次搜索我发现了这个问题如何创建 javafx 2 0 应用程序 MDI https stackoverflow com questions 10915388 how to create a javafx 2 0 application
  • 在 Java 中从 SOAPMessage 获取原始 XML

    我已经在 J AX WS 中设置了 SOAP WebServiceProvider 但我无法弄清楚如何从 SOAPMessage 或任何 Node 对象获取原始 XML 下面是我现在获得的代码示例 以及我试图获取 XML 的位置 WebSe
  • 尝试获取屏幕上绘制的每个随机圆圈的 x、y 坐标

    您好 我正在制作一款游戏 该游戏将在屏幕上创建随机圆圈 随机创建的圆圈的值为红色或绿色 我的问题是 我希望不仅能够确定用户何时单击其中一个圆圈 而且还能够确定他们最终单击的圆圈 红色或绿色 下面是我的代码 我的主要问题是试图找到将要绘制的圆
  • 如何为小程序提供对文件系统写入的访问权限

    我在设置小程序的策略文件时遇到问题 我是第一次这样做 不知道如何在java中设置小程序的策略文件 实际上我想授予小程序在文件系统上写入的权限 为此我必须向小程序授予文件权限 所以我创建了一个名为 java policy 的文件 并将以下代码
  • 当 JMS Prod 位于辅助 POJO 类中时,如何在事务中包含 JMS Producer

    简短的问题 有没有办法强制无状态 EJB 调用的 POJO 存在于 EJB 的上下文中 以便事务和资源注入可以在 POJO 中工作 具体来说 在我想要做的事情的上下文中 如何在 EJB 的事务中包含 POJO JMS 生产者 该生产者在调用
  • Android volley使用RequestFuture.get()时出现超时异常

    在我的片段中 我尝试使用 TMDB 的开放电影数据库来获取有关 正在播放 电影的详细信息 如果我使用 RequestFuture get time TimeUnit 方法来执行此齐射请求 我总是会收到超时错误 如果我在 Safari 中手动
  • 从 HttpClient 3 转换为 4

    我已经成功地对所有内容进行了更改 但以下内容除外 HttpClient client HttpPost method client new DefaultHttpClient method new HttpPost url InputStr
  • 合并两个地图的最佳实践是什么

    如何将新地图添加到现有地图 地图具有相同的类型Map
  • 如何在 Bean Validation 1.0 中构造 ConstraintViolationException?

    我对 javax validation API 感到困惑 我正在编写一个简单的测试来理解它 Sample sample new Sample Set
  • JAXB 编组器无参数默认构造函数

    我想从 java 库中编组一个 java 对象 当使用 JAXB marschaller 编组 java 对象时 我遇到了一个问题 A 类没有无参数默认构造函数 我使用Java Decompiler来检查类的实现 它是这样的 public
  • 在循环中按名称访问变量

    我正在开发一个 Android 项目 并且有很多可绘制对象 这些绘图的名称都类似于icon 0 png icon 1 png icon 100 png 我想将这些可绘制对象的所有资源 ID 添加到整数 ArrayList 中 对于那些不了解
  • Java 中处理异步响应的设计模式

    我读过类似问答的答案 如何在 JAVA 中创建异步 HTTP 请求 https stackoverflow com questions 3142915 how do you create an asynchronous http reque
  • CXF:通过 SOAP 发送对象时如何排除某些属性?

    我使用 Apache CXF 2 4 2 当我将数据库中的某个对象返回给用户时 我想排除一些属性 例如密码 我怎样才能做到这一点无需创建临时的班级 有这方面的注释吗 根据 tomasz nurkiewicz 评论我应该使用 XmlTrans
  • 使用自定义比较器在 Java 中创建 SortedMap

    我想创建一个TreeMap在 Java 中具有自定义排序顺序 排序后的键是字符串 需要根据第二个字符进行排序 这些值也是字符串 示例地图 Za FOO Ab Bar 您可以像这样使用自定义比较器 Comparator
  • 你能快速告诉我这个伪代码是否有意义吗?

    我相信我的代码现在是万无一失的 我现在将写出伪代码 但我确实有一个问题 为什么 DRJava 要求我返回 if 语句之外的内容 正如你所看到的 我为 ex 写了 return 1 只是因为它问了 但是它永远不会返回该值 谁可以给我解释一下这
  • 如何使用 Jest 从 ElasticSearch 获取索引列表

    我正在尝试使用 Jest 检索索引列表 但我只得到 Stats statistics new Stats Builder build result client execute statistics 如何从结果中检索索引列表 除了统计之外

随机推荐

  • swagger设置字段required必填

    swagger注解中需要设置请求参数的字段是必须填写的还是非必须的 我们需要添加下面的配置 只需要在对象的字段中增加下面的代码 ApiModelProperty value 自建应用的corpid required true 显示效果如下
  • Vulnhub实战-prime1

    前言 VulnHub 是一个面向信息安全爱好者和专业人士的虚拟机 VM 漏洞测试平台 它提供了一系列特制的漏洞测试虚拟机镜像 供用户通过攻击和漏洞利用的练习来提升自己的安全技能 本次 我们本次测试的是prime1 一 主机发现和端口扫描 查
  • Python 、Pychorm 、 Opencv安装及环境变量的配置--opencv不能调用cv2

    Python Pychorm Opencv安装及环境变量的配置 Python安装及环境变量的配置 1 Python下载安装 官网下载Python安装包 Python官网 https www python org Python在安装时 提示可
  • flex 一行两个_flex 方式的布局你用对了吗?

    对于 CSS 中的 flex 弹性布局 曾经我在公众号里写过 一点点对 flex 布局有关的看法 和 又一次想说 flex 布局挖坑给你 信吗 印象中最深的一个点 是我在文中提到的对于 flex 的这个属性中的三个属性值怎么用的话题 如果你
  • 初等变换法求解线性方程组

    初等变换 通过初等行 列同理 变换把增广矩阵变为简化阶梯型矩阵的线性方程组求解算法 具体步骤 枚举每一列 找到枚举的当前列绝对值最大数的所在行 将该行换到最上面一行 第r行 将该行第一个数 该行第c列元素 消成1 将该行第一个元素 该行第c
  • PCB中如何区分电源线和信号线

    在电路设计中 我们需要区分电源线和信号线 电源线主要负责传输电能和提供稳定的电压给整个电路系统 而信号线则传输各种数据 信息和控制电路 当我们开始画 PCBA 时 通常会采用颜色编码来区分电源线和信号线 以下是一些常见的方法 电源线通常使用
  • 常用邮件客户端软件设置

    文章来源 http service mail qq com cgi bin help subtype 1 id 28 no 371 2 您可以使用支持POP3的客户端软件 例如Foxmail或Outlook 收发您的邮件 请配置您的电子邮件
  • CentOS7搭建httpd服务

    文章目录 httpd 1 httpd安装 使用yum安装 httpd命令 curl命令 编译安装httpd 2 4 2 httpd常用配置 访问控制法则 3 虚拟主机 相同ip不同端口的配置 不同IP相同端口配置 相同IP相同端口不同域名配
  • QT Creator工具介绍及使用

    一 QT的基本概念 QT主要用于图形化界面的开发 QT是基于C 编写的一套界面相关的类库 如进程线程库 网络编程的库 数据库操作的库 文件操作的库等 如何使用这个类库 类库实例化对象 构造函数 gt 学习类库中方法 函数 的使用 gt 后台
  • 今天觉得自己太菜了!!!!

    今天觉得自己太菜了 转载于 https www cnblogs com zhangzheny archive 2007 01 24 629460 html
  • 如何检测tensorflow是否使用CPU还是GPU计算

    输入如下命令 python import tensorflow as tf sess tf Session config tf ConfigProto log device placement True 若包含CPU信息 则使用了CPU 若
  • GBDT 学习

    花絮 最近加班疯掉了 比九九六还要多 不行啊 这个便宜一定要从老板身上占回来 另外不知道朋友们是怎么学习一个新算法的 不过我一般是直接百度很多关于这个算法的博客来看 你会发现有些地方可能相互补充 有的地方可能互相矛盾 这种情况可不少见 总之
  • python是一门面向对象的编程语言_python面向对象(面向对象、面向过程、类、参数self)...

    年轻人 你渴望力量吗 你渴望拥有对象吗 让我们面向对象 重建 家园 吧 一 面向对象的简介 众所周知 python是一门面向对象的编程语言 但是 你知道什么是面向对象吗 在说面向对象之前 我们先来说一说什么是对象 之前的博客有简单涉入 一
  • ANT 安装使用及build.xml文档模板

    一 安装 下载地址 http archive apache org dist ant 0 前提 已经正确设定了JAVA HOME的系统变量 1 直接解压 到D apache ant 1 7 2 系统环境变量中设置 path 加上 D apa
  • chatgpt赋能powershell

    最近chatgpt非常火爆 获得超高曝光度的同时 也让大家对ai和ai工具有了新的认识 关于chatgpt 可以参考这篇文章 今天主要推荐一个可以与powershell集成的ai工具 其后端也是openai的服务 可以有效提高工作效率 Po
  • vue3 element plus NavMenu 导航无法选中(同级页面跳转,导航无法高亮)

    需求 导航选中的是公司信息 当跳转到公司信息编辑的时候 上面的导航公司信息还是要选中状态 点击导航 回到信息展示页面 下面是我的路由 在vue2里面 使用element ui 像下面这样做是可以的 动态给el menu item的index
  • 第十一届蓝桥杯大赛软件类省赛第二场 Java 大学 B试题 B: 寻找 2020 解答

    说明 以下介绍三种方法 三者的区别仅在于文件读取方式不同 方法1直接把 txt文件复制粘贴到console控制台上 使用了Scanner类 方法2和方法3都是从电脑文件夹读取文件 但是具体读取细节有些差别 方法2使用了io接口的三个包 Fi
  • Lua 数据类型 —— 表

    一 简介 表永远是匿名的 表本身和保存表的变量之间没有固定关系 对于一个表而言 当程序不再有变量指向他时 垃圾收集器会最终删除这个表并重用其占用的内存 Lua 不会进行隐藏拷贝或创建新表 操作的都是指向表的指针 二 元素 1 键 表的键可以
  • 2.7 深入理解ContentProvider

    第7章 深入理解ContentProvider 7 1 概述 本章重点分析ContentProvider SQLite Cursor query close函数的实现及ContentResolver openAssetFileDescrip
  • 线程安全(上)

    前言 在多线程中 并不是说知道怎么使用就完事了 学习完如何使用多线程之后 我们了解到多线程的并发性和随机调度的特性是我们程序员不容易控制的 所以一旦操作不当就会带来许多安全问题 那我们就开始学习线程安全吧 目录 1 使用多线程带来的风险 硬