획득 위 챗 accesstoken 이 끌 어 낸 자바 다 중 스 레 드 병행 문제

배경:
      access_token 은 공중전화 의 전체 국면 에서 유일한 어음 으로 공중전화 가 각 인 터 페 이 스 를 호출 할 때 access 를 사용 해 야 합 니 다.token。개발 자 는 잘 저장 해 야 합 니 다.access_token 의 저장 소 는 최소 512 개의 문자 공간 을 유지 해 야 합 니 다.access_token 의 유효기간 은 현재 2 시간 입 니 다.정시 에 갱신 해 야 합 니 다.중복 획득 은 지난번 에 얻 은 access 를 가 져 옵 니 다.token 실효.
1、    appsecrect,       access_token           。              access_token          ,        ,     access_token       ;
2、  access_token         expire_in   ,   7200     。                     access_token。      ,              access_token,                  ,  access_token   ,              ;
3、access_token              ,                   ,         access_token   ,          API    access_token       ,    access_token     。

, servlet servlet access_token , : servlet web , servlet init access_token, , 2 access_token。 :
1)servlet
public class InitServlet extends HttpServlet 
{
	private static final long serialVersionUID = 1L;

	public void init(ServletConfig config) throws ServletException 
	{
		new Thread(new AccessTokenThread()).start();  
	}

}

 2)스 레 드 코드:
public class AccessTokenThread implements Runnable 
{
	public static AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	//         access_token
				if(token != null){
					accessToken = token;
				}else{
					System.out.println("get access_token failed------------------------------");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	//   7000 
				}else{
					Thread.sleep(60 * 1000);	//   access_token null,60     
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}
}

  3)AccessToken 코드:
public class AccessToken 
{
	private String access_token;
	private long expire_in;		// access_token    ,    
	
	public String getAccess_token() {
		return access_token;
	}
	public void setAccess_token(String access_token) {
		this.access_token = access_token;
	}
	public long getExpire_in() {
		return expire_in;
	}
	public void setExpire_in(long expire_in) {
		this.expire_in = expire_in;
	}
}

 4)servlet 웹 xml 에서 의 설정
  <servlet>
    <servlet-name>initServlet</servlet-name>
    <servlet-class>com.sinaapp.wx.servlet.InitServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>

initServlet 에 load-on-startup=0 이 설정 되 어 있 기 때문에 모든 servlet 전에 시작 할 것 을 보증 합 니 다.
다른 servlet 는 access 를 사용 해 야 합 니 다.token 은 AccessTokenThread.accessToken 만 호출 하면 됩 니 다.
다 중 스 레 드 병발 문제:
1)위의 실현 은 아무런 문제 가 없 는 것 같 지만 자세히 생각해 보면 AccessTokenThread 류 의 accessToken 은 동시 방문 문제 가 존재 합 니 다.AccessTokenThread 는 2 시간 마다 한 번 씩 업데이트 되 지만 많은 스 레 드 로 읽 을 수 있 습 니 다.이것 은 전형 적 인 읽 기와 쓰기 가 적은 장면 이 고 하나의 스 레 드 만 있 습 니 다.동시 읽 기와 쓰기 가 존재 하 는 이상 위의 코드 는 틀림없이 문제 가 있 을 것 이다.
     일반적으로 생각 하 는 가장 쉬 운 방법 은 synchronized 로 처리 하 는 것 입 니 다.
public class AccessTokenThread implements Runnable 
{
	private static AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	//         access_token
				if(token != null){
					AccessTokenThread.setAccessToken(token);
				}else{
					System.out.println("get access_token failed");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	//   7000 
				}else{
					Thread.sleep(60 * 1000);	//   access_token null,60     
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}

	public synchronized static AccessToken getAccessToken() {
		return accessToken;
	}

	private synchronized static void setAccessToken(AccessToken accessToken) {
		AccessTokenThread2.accessToken = accessToken;
	}
}

 accessToken 은 private 가 되 었 고 setAccessToken 도 private 가 되 었 으 며 동기 화 synchronized 가 accessToken 에 접근 하 는 방법 을 추 가 했 습 니 다.
그럼 여기까지 면 완벽 하지 않 을까요?문제 없 는데?자세히 생각해 보면 문제 가 있 습 니 다.문 제 는 AccessToken 류 의 정의 에 있어 서 Public set 방법 을 제공 합 니 다.그러면 모든 스 레 드 는 AccessTokenThread.getAccessToken()을 사용 하여 모든 스 레 드 가 공유 하 는 accessToken 을 얻 은 후에 모든 스 레 드 가 속성 을 수정 할 수 있 습 니 다!!!이것 은 틀림없이 옳지 않 고,해 서 는 안 된다.
2)해결 방법 1:
    우 리 는 AccessTokenThread.getAccessToken()방법 으로 accessToken 대상 의 copy,사본 을 되 돌려 줍 니 다.그러면 다른 스 레 드 는 AccessTokenThread 클래스 의 accessToken 을 수정 할 수 없습니다.다음 과 같이 AccessTokenThread.getAccessToken()방법 을 수정 하면 됩 니 다.
	public synchronized static AccessToken getAccessToken() {
		AccessToken at = new AccessToken();
		at.setAccess_token(accessToken.getAccess_token());		
		at.setExpire_in(accessToken.getExpire_in());
		return at;
	}

 AccessToken 류 에서 clone 방법 을 실현 할 수도 있 고 원 리 는 모두 같다.물론 setAccessToken 도 private 가 되 었 다.
3)해결 방법 2:
    우리 가 AccessToken 의 대상 을 수정 하지 말 아야 하 는 이상,우 리 는 왜 accessToken 을'불가 변 대상'으로 정의 하지 않 습 니까?관련 수정 사항 은 다음 과 같 습 니 다.
public class AccessToken 
{
	private final String access_token;
	private final long expire_in;		// access_token    ,    
	
	public AccessToken(String access_token, long expire_in)
	{
		this.access_token = access_token;
		this.expire_in = expire_in;
	}
	
	public String getAccess_token() {
		return access_token;
	}
	
	public long getExpire_in() {
		return expire_in;
	}
}

 위 에서 보 듯 이 AccessToken 의 모든 속성 은 final 형식 으로 정의 되 었 고 구조 함수 와 get 방법 만 제공 합 니 다.이렇게 되면 다른 스 레 드 는 AccessToken 의 대상 을 얻 은 후에 수정 할 수 없습니다.수정 요구 AccessTokenUtil.freshAccessToken()에서 되 돌아 오 는 AccessToken 의 대상 은 참 이 있 는 구조 함수 로 만 만 만 만 들 수 있 습 니 다.또한 AccessTokenThread 의 setAccessToken 도 private 로 수정 해 야 합 니 다.getAccessToken 은 복사 본 을 되 돌려 줄 필요 가 없습니다.
가 변 적 이지 않 은 대상 은 반드시 아래 의 세 가지 조건 을 만족 시 켜 야 한다.
a)대상 이 생 성 된 후 상 태 를 수정 할 수 없습니다.
b)대상 의 모든 도 메 인 은 final 형식 입 니 다.
c)대상 은 정확하게 만 들 어 졌 습 니 다(즉,대상 의 구조 함수 에서 this 인용 은 일출 이 발생 하지 않 았 습 니 다).
4)해결 방법 3:
    더 좋 고,더 완벽 하고,더 효율 적 인 방법 은 없 을 까?해결 방법 2 에서 AccessTokenUtil.freshAccessToken()은 가 변 적 이지 않 은 대상 으로 돌아 간 다음 에 private 의 AccessTokenThread.setAccessToken(AccessToken accessToken)방법 으로 값 을 부여 합 니 다.이 방법의 synchronized 동기 화 는 어떤 작용 을 했 습 니까?대상 이 가 변 적 이지 않 고 하나의 스 레 드 만 setAccessToken 방법 을 호출 할 수 있 기 때문에 이곳 의 synchronized 는'상호 배척'역할 을 하지 않 습 니 다(하나의 스 레 드 만 수정 되 었 기 때 문 입 니 다).단지'가시 성'을 확보 하 는 역할 을 했 을 뿐 다른 스 레 드 에 대한 수정 을 볼 수 있 습 니 다.즉,다른 스 레 드 가 접근 하 는 것 은 모두 최신 accessToken 대상 입 니 다.그리고'가시 성'은 volatile 을 사용 하여 진행 할 수 있 기 때문에 이곳 의 synchronized 는 필요 하지 않 을 것 입 니 다.우 리 는 volatile 을 사용 하여 대체 할 것 입 니 다.관련 수정 코드 는 다음 과 같 습 니 다.
public class AccessTokenThread implements Runnable 
{
	private static volatile AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	//         access_token
				if(token != null){
					AccessTokenThread2.setAccessToken(token);
				}else{
					System.out.println("get access_token failed");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	//   7000 
				}else{
					Thread.sleep(60 * 1000);	//   access_token null,60     
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}

	private static void setAccessToken(AccessToken accessToken) {
		AccessTokenThread2.accessToken = accessToken;
	}
    public static AccessToken getAccessToken() {
         return accessToken;
     } }

 이렇게 고 칠 수도 있다.
public class AccessTokenThread implements Runnable 
{
	private static volatile AccessToken accessToken;
	
	@Override
	public void run() 
	{
		while(true) 
		{
			try{
				AccessToken token = AccessTokenUtil.freshAccessToken();	//         access_token
				if(token != null){
					accessToken = token;
				}else{
					System.out.println("get access_token failed");
				}
			}catch(IOException e){
				e.printStackTrace();
			}
			
			try{
				if(null != accessToken){
					Thread.sleep((accessToken.getExpire_in() - 200) * 1000);	//   7000 
				}else{
					Thread.sleep(60 * 1000);	//   access_token null,60     
				}
			}catch(InterruptedException e){
				try{
					Thread.sleep(60 * 1000);
				}catch(InterruptedException e1){
					e1.printStackTrace();
				}
			}
		}
	}

	public static AccessToken getAccessToken() {
		return accessToken;
	}
}

 이렇게 고 칠 수도 있다.
public class AccessTokenThread implements Runnable 
{
    public static volatile AccessToken accessToken;
    
    @Override
    public void run() 
    {
        while(true) 
        {
            try{
                AccessToken token = AccessTokenUtil.freshAccessToken();    //         access_token
                if(token != null){
                    accessToken = token;
                }else{
                    System.out.println("get access_token failed");
                }
            }catch(IOException e){
                e.printStackTrace();
            }
            
            try{
                if(null != accessToken){
                    Thread.sleep((accessToken.getExpire_in() - 200) * 1000);    //   7000 
                }else{
                    Thread.sleep(60 * 1000);    //   access_token null,60     
                }
            }catch(InterruptedException e){
                try{
                    Thread.sleep(60 * 1000);
                }catch(InterruptedException e1){
                    e1.printStackTrace();
                }
            }
        }
    }
}

accesToken 은 Public 로 바 뀌 었 습 니 다.AccessTokenThread.accessToken 에서 직접 방문 할 수 있 습 니 다.그러나 후기 유 지 를 위해 서 는 대중 으로 바 꾸 지 않 는 것 이 좋다.
사실 이 문제 의 관건 은 다 중 스 레 드 동시 방문 환경 에서 공유 대상 을 어떻게 정확하게 발표 하 느 냐 하 는 것 이다.
 
사실 우 리 는 Executors.new Scheduled ThreadPool 을 사용 하여 해결 할 수 있 습 니 다.
public class InitServlet2 extends HttpServlet 
{
    private static final long serialVersionUID = 1L;

    public void init(ServletConfig config) throws ServletException 
    {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(new AccessTokenRunnable(), 0, 7200-200, TimeUnit.SECONDS);
    }
}
public class AccessTokenRunnable implements Runnable 
{
    private static volatile AccessToken accessToken;
    
    @Override
    public void run() 
    {
        try{
            AccessToken token = AccessTokenUtil.freshAccessToken();    //         access_token
            if(token != null){
                accessToken = token;
            }else{
                System.out.println("get access_token failed");
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }

    public static AccessToken getAccessToken() 
    {
        while(accessToken == null){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return accessToken;
    }
    
}

accessToken 가 져 오기 방식 이:AccessTokenRunnable.getAccessToken()으로 바 뀌 었 습 니 다.

좋은 웹페이지 즐겨찾기