>  기사  >  Java  >  Spring에서 싱글톤 모드와 스레드 안전성 간의 모순 해결

Spring에서 싱글톤 모드와 스레드 안전성 간의 모순 해결

不言
不言앞으로
2018-10-22 17:30:084424검색

이 기사의 내용은 Spring에서 싱글톤 모드와 스레드 안전성 사이의 모순을 해결하는 내용입니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

Spring 프레임워크를 사용할 때 멀티스레딩 문제를 모르거나 무시하는 사람이 얼마나 됩니까?

프로그램을 작성하거나 단위 테스트를 할 때 멀티스레딩 테스트 환경을 시뮬레이션하는 것이 쉽지 않기 때문에 멀티스레딩 문제가 발생하기 어렵습니다. 그런 다음 여러 스레드가 동일한 Bean을 호출하면 스레드 안전 문제가 발생합니다. Spring의 Bean 생성 모드가 싱글톤이 아닌 경우에는 이러한 문제가 발생하지 않습니다.

하지만 잠재적인 취약점을 고려하지 않으면 프로그램의 숨은 킬러가 되어 모르는 사이에 터지게 됩니다. 더욱이 프로그램을 사용하기 위해 제공할 때 프로덕션 환경에서 트리거하는 것은 일반적으로 매우 번거로운 작업입니다.

Spring은 ThreadLocal을 사용하여 스레드 안전 문제를 해결합니다.

우리는 일반적으로 상태 비저장 Bean만 다중 스레드 환경에서 공유할 수 있다는 것을 알고 있습니다. Spring에서는 대부분의 Bean을 싱글톤 범위로 선언할 수 있습니다. 이는 Spring이 ThreadLocal을 사용하여 일부 Bean(예: RequestContextHolder, TransactionSynchronizationManager, LocaleContextHolder 등)에서 스레드로부터 안전하지 않은 상태를 처리하여 이를 스레드로부터 안전하게 만들기 때문입니다. Stateful Bean은 여러 스레드 간에 공유될 수 있기 때문입니다.

일반 웹 애플리케이션은 프리젠테이션 계층, 서비스 계층, 지속성 계층의 세 가지 수준으로 구분됩니다. 해당 로직은 서로 다른 계층에 작성되며 하위 계층은 인터페이스를 통해 상위 계층에 대한 함수 호출을 엽니다. 정상적인 상황에서 요청 수신부터 응답 반환까지의 모든 프로그램 호출은 동일한 스레드에 속합니다.

ThreadLocal은 스레드 안전 문제를 해결하는 좋은 아이디어입니다. 각 스레드에 대한 독립적인 변수 복사본을 제공하여 변수에 대한 동시 액세스의 충돌 문제를 해결합니다. 많은 경우 ThreadLocal은 스레드 안전 문제를 해결하기 위해 동기화된 동기화 메커니즘을 직접 사용하는 것보다 더 간단하고 편리하며 결과 프로그램의 동시성이 더 높습니다.

코드가 있는 프로세스에서 동시에 실행 중인 여러 스레드가 있는 경우 이러한 스레드가 이 코드를 동시에 실행할 수 있습니다. 각 실행의 결과가 단일 스레드 실행의 결과와 동일하고 다른 변수의 값도 예상과 동일하면 스레드로부터 안전합니다. 즉, 클래스나 프로그램에서 제공하는 인터페이스는 스레드에 대한 원자적 작업이거나 여러 스레드 간의 전환으로 인해 인터페이스의 실행 결과에 모호성이 발생하지 않으므로 동기화 문제를 고려할 필요가 없습니다. 스레드 안전 문제는 전역 변수와 정적 변수로 인해 발생합니다.

각 스레드에 전역 변수와 정적 변수에 대한 읽기 작업만 있고 쓰기 작업은 없는 경우, 일반적으로 이 전역 변수는 여러 스레드가 동시에 쓰기 작업을 수행하는 경우 일반적으로 스레드로부터 안전합니다. 동기화로 간주됩니다. 그렇지 않으면 스레드 안전성이 영향을 받을 수 있습니다.
1) 읽기 작업만 있기 때문에 상수는 항상 스레드로부터 안전합니다.
2) 각 메서드 호출 전에 새 인스턴스를 생성하면 공유 리소스에 액세스할 수 없으므로 스레드로부터 안전합니다.
3) 지역 변수는 스레드로부터 안전합니다. 메소드가 실행될 때마다 공유 리소스가 아닌 별도의 공간에 로컬 변수가 생성되기 때문입니다. 지역 변수에는 메소드 매개변수 변수와 메소드 내부 변수가 포함됩니다.

상태가 있다는 것은 데이터 저장 기능이 있다는 것을 의미합니다. Stateful 객체(Stateful Bean)는 데이터를 저장할 수 있고 스레드로부터 안전하지 않은 인스턴스 변수가 있는 객체입니다. 메서드 호출 간에는 상태가 유지되지 않습니다.

Stateless는 일회성 작업이므로 데이터를 저장할 수 없습니다. Stateless 객체(Stateless Bean)는 인스턴스 변수가 없는 객체로, 데이터를 저장할 수 없으며 스레드로부터 안전합니다.

Stateful 객체:

Stateless Bean은 불변 모드에 ​​적합합니다. 이 기술은 싱글톤 모드이므로 인스턴스를 공유할 수 있고 성능을 향상시킬 수 있습니다. Stateful Bean은 다중 스레드 환경에서 안전하지 않으므로 Prototype 프로토타입 모드가 적합합니다. 프로토타입: Bean에 대한 각 요청은 새로운 Bean 인스턴스를 생성합니다.

Struts2의 기본 구현은 프로토타입 모드입니다. 즉, 요청마다 새로운 Action 인스턴스가 생성되므로 스레드 안전성 문제가 없습니다. 액션의 라이프사이클이 Spring에 의해 관리된다면 범위는 프로토타입 범위로 구성되어야 합니다

스레드 안전 사례

SimpleDateFormat(이하 sdf) 클래스에는 Calendar 객체 참조가 내부에 있습니다. , sdf.parse(dateStr), sdf.format(date) 및 메소드 매개변수로 전달된 기타 날짜 관련 String, Date 등과 같은 sdf 관련 날짜 정보는 모두 참조되어 저장됩니다. 이로 인해 문제가 발생합니다. sdf가 정적이면 여러 스레드가 이 sdf를 공유하고 이 Calendar 참조도 공유하며 sdf.parse() 메서드를 관찰하면 다음 호출을 찾을 수 있습니다.

 Date parse() {
   calendar.clear(); // 清理calendar
   ... // 执行一些操作, 设置 calendar 的日期什么的
   calendar.getTime(); // 获取calendar的时间
 }

여기서 발생하는 문제는 스레드 A가 sdf.parse()를 호출하고, Calendar.clear() 이후 Calendar.getTime()이 실행되기 전에 스레드 B가 이때 다시 sdf.parse()를 호출한다는 것입니다. 스레드 B도 sdf.clear() 메서드를 실행하여 스레드 A의 달력 데이터가 지워졌습니다(실제로 A와 B가 동시에 지워졌습니다). 또는 A가 Calendar.clear()를 실행하고 일시 중지되었습니다. 이번에는 B가 sdf.parse()를 호출하기 시작하고 원활하게 종료됩니다. 이렇게 하면 A의 달력에 저장된 날짜가 나중에 B가 설정한 날짜가 됩니다. 이 문제 뒤에는 더 깊은 문제가 숨겨져 있습니다. 무상태(stateless): 무상태 메소드의 장점 중 하나는 다양한 환경에서 안전하게 호출할 수 있다는 것입니다. 메서드가 상태 저장형인지 여부를 측정하려면 전역 변수, 인스턴스 필드 등 다른 항목이 변경되는지 여부에 따라 달라집니다. format 메소드는 실행 프로세스 중에 SimpleDateFormat의 달력 필드를 변경하므로 상태 저장형입니다.

또한 시스템을 개발하고 설계할 때 다음 세 가지 사항에 주의해야 한다는 점을 상기시켜 줍니다.

공개 클래스를 직접 작성할 때는 댓글에 멀티 스레드 호출의 결과를 명확하게 설명해야 합니다.

스레드 환경에서 , 모든 공유 변수 변수의 스레드 안전성에 주의하세요

클래스와 메소드를 설계할 때 최선을 다해 상태 비저장으로 설계해야 합니다

Solution

1. 새 인스턴스 만들기:

참고: SimpleDateFormat을 사용해야 하는 새 인스턴스를 만듭니다. 스레드 안전 문제가 있는 개체를 공유에서 로컬 비공개로 변경할 때마다 멀티 스레딩 문제를 피할 수 있지만 개체 생성 부담도 가중됩니다. 일반적인 상황에서는 성능에 미치는 영향이 그리 명확하지 않습니다.

2. 동기화 사용: SimpleDateFormat 객체 동기화

public class DateSyncUtil {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      
    public static String formatDate(Date date)throws ParseException{
        synchronized(sdf){
            return sdf.format(date);
        }  
    }
    
    public static Date parse(String strDate) throws ParseException{
        synchronized(sdf){
            return sdf.parse(strDate);
        }
    } 
}
참고: 스레드가 많을 때 한 스레드가 이 메서드를 호출하면 이 메서드를 호출하려는 다른 스레드가 차단됩니다. 크면 성능에 일정한 영향을 미칩니다.

3. ThreadLocal 사용:

public class ConcurrentDateUtil {
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    }
    public static String format(Date date) {
        return threadLocal.get().format(date);
    }
}
또는

ThreadLocal<DateFormat>(); 
 
    public static DateFormat getDateFormat()   
    {  
        DateFormat df = threadLocal.get();  
        if(df==null){  
            df = new SimpleDateFormat(date_format);  
            threadLocal.set(df);  
        }  
        return df;  
    }  
    public static String formatDate(Date date) throws ParseException {
        return getDateFormat().format(date);
    }
    public static Date parse(String strDate) throws ParseException {
        return getDateFormat().parse(strDate);
    }   
}

참고: ThreadLocal을 사용하면 메소드 단독 사용에 비해 공유 변수를 배타적 변수로 바꿀 수 있습니다. . 성능 요구 사항이 비교적 높은 경우 일반적으로 이 방법을 권장합니다.

4. JDK를 버리고 다른 라이브러리의 시간 형식 지정 클래스 사용:

빠르고 스레드에 안전한 SimpleDateFormat이라고 주장하는 Apache Commons에서 FastDateFormat을 사용하지만 불행히도 날짜 문자열은 형식 지정만 가능합니다. 파싱.

Joda-Time 클래스 라이브러리를 사용하여 시간 관련 문제를 처리하세요

간단한 스트레스 테스트를 수행해 보세요. 방법 1이 가장 느리고 방법 3이 가장 빠릅니다. 그러나 가장 느린 방법 1도 일반적으로 성능이 좋습니다. 하나와 방법 2로 충분하므로 현시점에서는 시스템의 병목 현상이 발생하기 어렵습니다. 간단한 관점에서는 방법 1이나 방법 2를 사용하는 것이 좋습니다. 필요한 경우 약간의 성능 향상을 추구하는 경우에는 캐싱을 위해 ThreadLocal을 사용하는 방법 3을 고려해 볼 수 있습니다.

Joda-Time 클래스 라이브러리는 시간 처리에 적합하므로 사용을 권장합니다.

Summary

기사 시작 부분의 질문으로 돌아가서: "Spring 프레임워크를 사용할 때 멀티스레딩 문제를 모르거나 무시하는 사람이 얼마나 됩니까?" 》

사실 코드는 누구나 작성할 수 있는데, 건축가가 작성한 코드의 효과가 왜 그렇게 다른가요? 본인은 고려하지 않았는데 건축가가 고려한 이런 작은 문제여야 합니다.

건축가는 더 넓은 지식을 갖고 있고, 더 구체적인 상황을 보았고, 다양한 문제를 해결하는 데 더 많은 경험을 가지고 있습니다. 건축가의 사고와 습관만 익혀도 건축가가 되려면 아직 멀었나요?

위 내용은 Spring에서 싱글톤 모드와 스레드 안전성 간의 모순 해결의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제