


Java multi-thread concurrency issues caused by obtaining WeChat access_token
Feb 28, 2017 am 09:44 AMBackground:
access_token is the globally unique ticket of the public account. The public account needs to use access_token when calling each interface. Developers need to store it properly. At least 512 characters of space must be reserved for access_token storage. The validity period of access_token is currently 2 hours and needs to be refreshed regularly. Repeated acquisition will cause the last access_token to become invalid.
1、為了保密appsecrect,第三方需要一個(gè)access_token獲取和刷新的中控服務(wù)器。而其他業(yè)務(wù)邏輯服務(wù)器所使用的access_token均來(lái)自于該中控服務(wù)器,不應(yīng)該各自去刷新,否則會(huì)造成access_token覆蓋而影響業(yè)務(wù); 2、目前access_token的有效期通過(guò)返回的expire_in來(lái)傳達(dá),目前是7200秒之內(nèi)的值。中控服務(wù)器需要根據(jù)這個(gè)有效時(shí)間提前去刷新新access_token。在刷新過(guò)程中,中控服務(wù)器對(duì)外輸出的依然是老access_token,此時(shí)公眾平臺(tái)后臺(tái)會(huì)保證在刷新短時(shí)間內(nèi),新老access_token都可用,這保證了第三方業(yè)務(wù)的平滑過(guò)渡; 3、access_token的有效時(shí)間可能會(huì)在未來(lái)有調(diào)整,所以中控服務(wù)器不僅需要內(nèi)部定時(shí)主動(dòng)刷新,還需要提供被動(dòng)刷新access_token的接口,這樣便于業(yè)務(wù)服務(wù)器在API調(diào)用獲知access_token已超時(shí)的情況下,可以觸發(fā)access_token的刷新流程。 簡(jiǎn)單起見(jiàn),使用一個(gè)隨servlet容器一起啟動(dòng)的servlet來(lái)實(shí)現(xiàn)獲取access_token的功能,具體為:因?yàn)樵搒ervlet隨著web容器而啟動(dòng),在該servlet的init方法中觸發(fā)一個(gè)線程來(lái)獲得access_token,該線程是一個(gè)無(wú)線循環(huán)的線程,每隔2個(gè)小時(shí)刷新一次access_token。相關(guān)代碼如下: :
public class InitServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void init(ServletConfig config) throws ServletException { new Thread(new AccessTokenThread()).start(); } }
2) Thread code :
public class AccessTokenThread implements Runnable { public static AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 從微信服務(wù)器刷新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 code:
public class AccessToken { private String access_token; private long expire_in; // access_token有效時(shí)間,單位為妙 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 configuration in 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>Because initServlet sets load-on-startup=0, it is guaranteed to start before all other servlets. If other servlets want to use access_token, they only need to call AccessTokenThread.accessToken.
Leading to multi-thread concurrency issues:
1) There seems to be no problem with the above implementation, but if you think about it carefully, the accessToken in the AccessTokenThread class , it has the problem of concurrent access. It is only updated every 2 hours by AccessTokenThread, but there will be many threads to read it. It is a typical scenario of more reading and less writing, and only one thread writes. Since there are concurrent reads and writes, there must be a problem with the above code. The easiest way to think of is to use synchronized to handle it:public class AccessTokenThread implements Runnable { private static AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 從微信服務(wù)器刷新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 becomes private, and setAccessToken also becomes private , added a method to synchronize accessToken. So is it perfect now? Is there no problem? Thinking about it carefully, there is still a problem. The problem lies in the definition of the AccessToken class. It provides a public set method. Then all threads use AccessTokenThread.getAccessToken() to obtain the accessToken shared by all threads. Any thread can Modify its properties! ! ! ! And this is definitely wrong and shouldn't be done.
2) Solution 1:
We let the AccessTokenThread.getAccessToken() method return a copy of the accessToken object, so that other The thread cannot modify the accessToken in the AccessTokenThread class. Modify the AccessTokenThread.getAccessToken() method as follows:public synchronized static AccessToken getAccessToken() { AccessToken at = new AccessToken(); at.setAccess_token(accessToken.getAccess_token()); at.setExpire_in(accessToken.getExpire_in()); return at; }You can also implement the clone method in the AccessToken class. The principles are the same. Of course setAccessToken has also become private.
3) Solution 2:
Since we should not let the AccessToken object be modified, then why don’t we define accessToken as a "Immutable objects"? The relevant modifications are as follows:public class AccessToken { private final String access_token; private final long expire_in; // access_token有效時(shí)間,單位為妙 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; } }As shown above, all properties of AccessToken are defined as final types, and only constructors and get methods are provided. In this case, other threads cannot modify it after obtaining the AccessToken object. The modification requires that the AccessToken object returned in AccessTokenUtil.freshAccessToken() can only be created through the constructor with parameters. At the same time, setAccessToken of AccessTokenThread must also be changed to private, and getAccessToken does not need to return a copy. Note that immutable objects must meet the following three conditions: a) The state of the object cannot be modified after it is created; b) All fields of the object are final Type; c) The object is created correctly (that is, in the constructor of the object, the this reference does not escape);
4) Solution 3:
Is there any other better, more perfect, more efficient method? Let's analyze it. In solution two, AccessTokenUtil.freshAccessToken() returns an immutable object, and then calls the private AccessTokenThread.setAccessToken(AccessToken accessToken) method to assign the value. What role does synchronized synchronization play in this method? Because the object is immutable and only one thread can call the setAccessToken method, synchronized here does not play a "mutual exclusion" role (because only one thread can modify it), but only plays a role in ensuring "visibility". Make the modification visible to other threads, that is, let other threads access the latest accessToken object. To ensure "visibility", volatile can be used, so synchronized here should not be necessary. We use volatile to replace it. The relevant modification code is as follows:public class AccessTokenThread implements Runnable { private static volatile AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 從微信服務(wù)器刷新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; } }You can also change it like this:
public class AccessTokenThread implements Runnable { private static volatile AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 從微信服務(wù)器刷新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; } }It’s OK Change it like this:
public class AccessTokenThread implements Runnable { public static volatile AccessToken accessToken; @Override public void run() { while(true) { try{ AccessToken token = AccessTokenUtil.freshAccessToken(); // 從微信服務(wù)器刷新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,可以直接是一個(gè)AccessTokenThread.accessToken來(lái)訪問(wèn)。但是為了后期維護(hù),最好還是不要改成public.
其實(shí)這個(gè)問(wèn)題的關(guān)鍵是:在多線程并發(fā)訪問(wèn)的環(huán)境中如何正確的發(fā)布一個(gè)共享對(duì)象。
其實(shí)我們也可以使用Executors.newScheduledThreadPool來(lái)搞定:
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(); // 從微信服務(wù)器刷新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多線程并發(fā)問(wèn)題相關(guān)文章請(qǐng)關(guān)注PHP中文網(wǎng)!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

PHP is an open source scripting language that is widely used in web development and server-side programming, especially in WeChat development. Today, more and more companies and developers are starting to use PHP for WeChat development because it has become a truly easy-to-learn and easy-to-use development language. In WeChat development, message encryption and decryption are a very important issue because they involve data security. For messages without encryption and decryption methods, hackers can easily obtain the data, posing a threat to users.

In the development of WeChat public accounts, the voting function is often used. The voting function is a great way for users to quickly participate in interactions, and it is also an important tool for holding events and surveying opinions. This article will introduce you how to use PHP to implement WeChat voting function. Obtain the authorization of the WeChat official account. First, you need to obtain the authorization of the WeChat official account. On the WeChat public platform, you need to configure the API address of the WeChat public account, the official account, and the token corresponding to the public account. In the process of our development using PHP language, we need to use the PH officially provided by WeChat

With the popularity of WeChat, more and more companies are beginning to use it as a marketing tool. The WeChat group messaging function is one of the important means for enterprises to conduct WeChat marketing. However, if you only rely on manual sending, it is an extremely time-consuming and laborious task for marketers. Therefore, it is particularly important to develop a WeChat mass messaging tool. This article will introduce how to use PHP to develop WeChat mass messaging tools. 1. Preparation work To develop WeChat mass messaging tools, we need to master the following technical points: Basic knowledge of PHP WeChat public platform development Development tools: Sub

WeChat is currently one of the social platforms with the largest user base in the world. With the popularity of mobile Internet, more and more companies are beginning to realize the importance of WeChat marketing. When conducting WeChat marketing, customer service is a crucial part. In order to better manage the customer service chat window, we can use PHP language for WeChat development. 1. Introduction to PHP WeChat development PHP is an open source server-side scripting language that is widely used in the field of Web development. Combined with the development interface provided by WeChat public platform, we can use PHP language to conduct WeChat

In the development of WeChat public accounts, user tag management is a very important function, which allows developers to better understand and manage their users. This article will introduce how to use PHP to implement the WeChat user tag management function. 1. Obtain the openid of the WeChat user. Before using the WeChat user tag management function, we first need to obtain the user's openid. In the development of WeChat public accounts, it is a common practice to obtain openid through user authorization. After the user authorization is completed, we can obtain the user through the following code

As WeChat becomes an increasingly important communication tool in people's lives, its agile messaging function is quickly favored by a large number of enterprises and individuals. For enterprises, developing WeChat into a marketing platform has become a trend, and the importance of WeChat development has gradually become more prominent. Among them, the group sending function is even more widely used. So, as a PHP programmer, how to implement group message sending records? The following will give you a brief introduction. 1. Understand the development knowledge related to WeChat public accounts. Before understanding how to implement group message sending records, I

How to use PHP to develop WeChat public accounts WeChat public accounts have become an important channel for promotion and interaction for many companies, and PHP, as a commonly used Web language, can also be used to develop WeChat public accounts. This article will introduce the specific steps to use PHP to develop WeChat public accounts. Step 1: Obtain the developer account of the WeChat official account. Before starting the development of the WeChat official account, you need to apply for a developer account of the WeChat official account. For the specific registration process, please refer to the official website of WeChat public platform

With the development of the Internet and mobile smart devices, WeChat has become an indispensable part of the social and marketing fields. In this increasingly digital era, how to use PHP for WeChat development has become the focus of many developers. This article mainly introduces the relevant knowledge points on how to use PHP for WeChat development, as well as some of the tips and precautions. 1. Development environment preparation Before developing WeChat, you first need to prepare the corresponding development environment. Specifically, you need to install the PHP operating environment and the WeChat public platform
