Background:
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。相关代码如下: :
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 ) web.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 を設定するため、他のすべてのサーブレットよりも前に開始されることが保証されます。 他のサーブレットが access_token を使用したい場合は、AccessTokenThread.accessToken を呼び出すだけで済みます。
はマルチスレッドの同時アクセスの問題を引き起こします :
1) 上記の実装には問題がないようですが、よく考えてみると、AccessTokenThread クラスの accessToken には同時アクセスの問題があります。 AccessTokenThread によってのみ 2 時間ごとに使用されます。これは 1 回更新されますが、多くのスレッドが読み取りを行い、書き込みは少なくなり、1 つのスレッドのみが書き込みを行うという典型的なシナリオです。同時読み取りと書き込みがあるため、上記のコードには問題があるはずです。 一般的に思いつく最も簡単な方法は、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 がプライベートになり、setAccessToken もプライベートになり、accessToken にアクセスする同期メソッドが追加されました。 それで、これで完璧ですか?問題ないでしょうか?よく考えてみると、問題は AccessToken クラスの定義にあり、すべてのスレッドが AccessTokenThread.getAccessToken() を使用して、すべてのスレッドで共有できるようになります。プロパティを変更してください。 ! ! !そして、これは間違いなく間違っており、行うべきではありません。
2) 解決策 1:
AccessTokenThread.getAccessToken() メソッドが accessToken オブジェクトのコピーを返すようにして、他のスレッドが 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 のすべてのプロパティは最終型として定義され、コンストラクターと get メソッドのみが提供されます。この場合、他のスレッドは AccessToken オブジェクトを取得した後にそれを変更できません。この変更では、AccessTokenUtil.freshAccessToken() で返される AccessToken オブジェクトがパラメーターを含むコンストラクターを通じてのみ作成できることが必要です。同時に、AccessTokenThread の setAccessToken も private に変更する必要があり、getAccessToken はコピーを返す必要はありません。 不変オブジェクトは次の 3 つの条件を満たさなければならないことに注意してください: a) オブジェクトの状態は作成後に変更できません b) オブジェクトのすべてのフィールドは最終型です。正しく作成されました (つまり、オブジェクトのコンストラクターで this 参照がエスケープされません)。 4) 解決策 3
: 他にもっと優れた、より完璧で、より効率的な方法はありますか?解決策 2 では、AccessTokenUtil.freshAccessToken() が不変オブジェクトを返し、プライベート AccessTokenThread.setAccessToken(AccessToken accessToken) メソッドを呼び出して値を割り当てます。この方法では、同期はどのような役割を果たしますか?オブジェクトは不変であり、1 つのスレッドのみが setAccessToken メソッドを呼び出すことができるため、ここでの synchronized は「相互排他」の役割を果たしません (1 つのスレッドのみがオブジェクトを変更できるため)。「可視性」を確保する役割のみを果たします。他のスレッドから見えるようにします。つまり、他のスレッドが最新の accessToken オブジェクトにアクセスできるようにします。 「可視性」を確保するには volatile を使用できるため、ここでは 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来访问。但是为了后期维护,最好还是不要改成public.
其实这个问题的关键是:在多线程并发访问的环境中如何正确的发布一个共享对象。
其实我们也可以使用Executors.newScheduledThreadPool来搞定:
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();
更多由获取微信access_token引出的Java多线程并发问题相关文章请关注PHP中文网!

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

WebStorm Mac版
便利なJavaScript開発ツール

DVWA
Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

MantisBT
Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

ホットトピック



