There was a time when computer programs were expected to run unattended. A program was a set of instructions and input data that was fed into the computer, and if everything went well some output data came out of the other end. f(x) = y. Oh, those halcyon days. Today's programs are expected to react to all kinds of events. Sometimes those events are a result of user interaction, like button presses and mouse clicks; other times they come from internal events, like timers.
Modern programming languages that deal with user interaction have provided varying degrees of abstraction for signalling these events to the program and specifying responses to them. In Java, this is typically done using a the listener pattern. If you use Java you are probably already aware of how this pattern works, but to summarize: An object that produces events (such as an object representing a button in a GUI) maintains a list of listener objects that are notified when certain events take place.
The listener pattern makes some assumptions. It assumes that the listener object will be able to obtain a valid reference to an event producer so that it can register itself with the producer, and that this reference will still be valid (or a new valid reference can be obtained) if it wants to unregister itself at a later point. It assumes that the event producer will always have a valid reference to the listener. A final assumption, this time more implicit, is that the event producer and the listener are part of the same process and that it won't matter if the listener fails to unregister itself from the producer if the program stops unexpectedly because the producer will also disappear.
These assumptions fall apart when you start using OSGi. An object in one bundle may wish to be notified of an event that is produced by another bundle. For example, assume that bundle P (producer) is responsible for reporting when a button has been pressed, and bundle L (listener) is interested in knowing when the button has been pressed. Using the listener pattern, one would expect bundle P to provide a service with a method by which listeners could register themselves for notification on button presses, and bundle L would create a listener object and register it with P's service. But what if bundle L starts before bundle P? It would not be able to register itself with the service, and would either have to give up, or ask to be notified when P starts. And what if bundle L registered its listener with P's service, and then crashed without unregistering the listener? P would have an invalid reference to the listener. We could write lots of code to try to make sure that these situations were handled correctly, but there's a better way.
Enter the whiteboard pattern. Instead registering a listener object with P, L creates an object that implements the interface LI (listener interface), providing a method that should be called when the event of interest occurs. When the event occurs, P requests a list of all of the services of type LI, and then calls the action method for each of those services. The burden of maintaining the relationships between producers and listeners is shifted to the OSGi framework. The advantage to doing this is that the OSGi framework is aware of bundle lifecycles and will unregister a bundle's services when the bundle stops. As a side benefit, the event producer no longer has to maintain a list of listeners, so its code becomes simpler.
For more information on this pattern, take a look at the whitepaper Listeners Considered Harmful: The "Whiteboard" Patern (careful, it's a PDF). And for an example of the whiteboard pattern in use, take a look at the apps Whiteboard Producer Example and Whiteboard Consumer Example.