A reoccurring conversation in the newsgroups revolves around the thread safety of static methods (Shared in VB.NET). Is a static method thread safe? Is a static method a bottleneck? These questions are important questions to answer, because the integrity, quality, and performance of your software will depend upon the answers. In this article we will examine the behavior of a static method to gain a better understanding of how the method works.
First, let’s go shopping.
Let’s say I take a grocery cart of 10 items into a checkout lane at my local grocery store. The clerk takes each item from my cart and computes a total cost for all the items. Unless there was a human error (or an item was priced incorrectly), we would expect the clerk’s cash register to compute the correct total.
The clerk is like a thread. Like a thread in an application, the clerk executes a set of instructions to compute the total cost of my grocery items. The clerk will process one cart of items at a time, and each will make use of a cash register to tally the items.
Now picture a different scenario. Picture 5 checkout lanes, each lane with one clerk, but only one shared cash register for the entire store. If multiple clerks are ringing up items on the shared cash register for different shoppers at the same time, nobody will receive the correct total. My clerk might ring up two items and then pause for a second, which allows a second clerk to come in and add another shopper’s item to the current list in the cash register. The total cost for items in my cart would be incorrect. This is an analogy of what can happen if a method is not thread safe – corrupted data.
The solution to the shared cash register problem is to ensure a clerk will have exclusive possession of the single cash register while totaling my order. No other clerks are allowed to use the register until my 10 items are finished. These steps will produce the correct results, but as you might imagine, it can make for long lines at the checkout. Everyone is waiting on this one cash register to come free. This is an analogy of what can happen to the scalability of an application when threads are locked while waiting for a shared resource.
There is a delicate balance to strike when writing code that is both high performance and thread safe with shared resources. Many books and research appears are devoted to the topic, so let’s just start with some basics. When is a method thread safe, and why is everyone so concerned with the static and Shared keywords? Let’s first understand what differentiates a static class member from an instance member
Static and Instance members
Marking a class member as static or shared means the member is associated with the type (the class), and not an instance of the type. Syntactically, a non-static method is only available if you have an instance of a class. Let’s pretend we have a class by the name of Store, with a non-static method OpenForBusiness.
Store groceryStore = new Store(); groceryStore.OpenForBusiness();
Now let’s make the OpenForBusiness method static. We can only invoke the method using the type name.
This last example should look odd. We are invoking OpenForBusiness on a type by using the class name. How do we know what store it will open? Will it open all stores? Static methods typically go against object oriented programming designs because they do not apply behavior to a specific object - this is a big semantic difference, a difference in meaning. In many cases you’ll see static methods as shortcuts to common functionality. Static methods can encapsulate instructions you want to execute without creating an instance of a class. The .NET libraries contain many examples of static shortcut methods, like File.Open and String.Format.
Although there are syntactic and semantic differences between static and non-static methods at compile and design time, underneath the covers a static method is just like any other method (there are some simplifications in this paragraph, but stick with me). The method encapsulates instructions. Threads execute these instructions. These instructions exist in only one place in memory. It makes no difference how many objects are created, or how many threads are executing the instructions, they only exist once, which is a good thing considering how many threads are running on my machine right now. This is how instructions work, data works very differently.
Now think for an moment about fields (variables) inside of a type, like a simple integer field. If the field is non-static, the field will exist for every instance of the class. If we add an integer field named ID to our Store class, every store will have an ID.
If you mark a field as static, only one instance will ever exist, regardless of how many objects are instantiated (this single instance can exist even when zero instances are created). If the ID field in our store is marked as static, there is only one place to store an ID number for all stores. It’s like the shared cash register in the beginning of the article. It’s not the static methods that you have to watch out for – it’s the static fields. With that thought in mind, let’s dig into some code in Part II of this article.