Reputation: 1144
I'm working on an App that has objects that must be available to all instances but also have synchronized access for certain methods within the object.
For instance I have this object:
public class PlanetID implements Serializable {
public PlanetID() {
id = 0;
}
public long generateID() {
id++;
return id;
}
private long id;
}
It's a simple object that creates a long (id) in series. It's necessary that this object generate a unique id every time. At the moment I have a static synchronized method that handles the Datastore access and storage along with the MemCache access and storage. It works for this particular method but I can already see issues with more complex objects that require a user to be able to access non-synchronized variables along with synchronized variables.
Is there some way to make an object global and allow for both synchronized methods and non-synchronized methods along with the storage of the object when those synchronized objects are accessed?
EDIT: I think people focused too much on the example I gave them and not on the bigger question of having a global variable which can be accessed by all instances and having synchronized access to specific methods while allowing asynchronous access to others.
Here's a better example in hopes it makes things a big more clearer.
Ex.
public class Market implements Serializable {
public Market() {
mineral1 = new ArrayList<Listing>();
mineral2 = new ArrayList<Listing>();
mineral3 = new ArrayList<Listing>();
mineral4 = new ArrayList<Listing>();
}
public void addListing(int mineral, String userID, int price, long amount) { //Doesn't require synchronized access
switch (mineral) {
case MINERAL1:
mineral1.add(new Listing(userID, price, amount));
break;
case MINERAL2:
mineral2.add(new Listing(userID, price, amount));
break;
case MINERAL3:
mineral3.add(new Listing(userID, price, amount));
break;
case MINERAL4:
mineral4.add(new Listing(userID, price, amount));
break;
}
}
public void purchased(int mineral, String userID, long amount) { //Requires synchronized access
ArrayList<Listing> mineralList = null;
switch (mineral) {
case MINERAL1:
mineralList = mineral1;
break;
case MINERAL2:
mineralList = mineral2;
break;
case MINERAL3:
mineralList = mineral3;
break;
case MINERAL4:
mineralList = mineral4;
break;
}
Listing remove = null;
for (Listing listing : mineralList)
if (listing.userID == userID)
if (listing.amount > amount) {
listing.amount -= amount;
return;
} else{
remove = listing;
break;
}
mineralList.remove(remove);
Collections.sort(mineralList);
}
public JSONObject toJSON(int mineral) { //Does not require synchronized access
JSONObject jsonObject = new JSONObject();
try {
switch (mineral) {
case MINERAL1:
for (Listing listing : mineral1)
jsonObject.accumulate(Player.MINERAL1, listing.toJSON());
break;
case MINERAL2:
for (Listing listing : mineral2)
jsonObject.accumulate(Player.MINERAL2, listing.toJSON());
break;
case MINERAL3:
for (Listing listing : mineral3)
jsonObject.accumulate(Player.MINERAL3, listing.toJSON());
break;
case MINERAL4:
for (Listing listing : mineral4)
jsonObject.accumulate(Player.MINERAL4, listing.toJSON());
break;
}
} catch (JSONException e) {
}
return jsonObject;
}
public static final int MINERAL1 = 0;
public static final int MINERAL2 = 1;
public static final int MINERAL3 = 2;
public static final int MINERAL4 = 3;
private ArrayList<Listing> mineral1;
private ArrayList<Listing> mineral2;
private ArrayList<Listing> mineral3;
private ArrayList<Listing> mineral4;
private class Listing implements Serializable, Comparable<Listing> {
public Listing(String userID, int price, long amount) {
this.userID = userID;
this.price = price;
this.amount = amount;
}
public JSONObject toJSON() {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("UserID", userID);
jsonObject.put("Price", price);
jsonObject.put("Amount", amount);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return jsonObject;
}
@Override
public int compareTo(Listing listing) {
return (price < listing.price ? -1 : (price == listing.price ? 0 : 1));
}
public String userID;
public int price;
public long amount;
}
}
Upvotes: 0
Views: 699
Reputation: 2477
Whilst technically possible, you should heed the advice of other answers and use the services supplied by Google, such as datastore and memcache.
However, you could use a single backend which contains your data, then use your favourite RPC method to read and write data into the shared object. You will need to be aware that although it doesn't happen often, backends are not guaranteed not to die randomly - so you could lose all the data in this object.
Upvotes: 0
Reputation: 15143
With GAE, the Java language is NOT going to hide all the datastore abstractions for you.
Stop thinking in terms of global variables and methods. These are Java language constructs. Start thinking in terms of datastore constructs - entities, datastore accesses, and transactions.
On GAE, your code will be simultaneously running on many servers, they will not share global variables, the "shared data" is in the datastore (or memcache)
An entity is an object in the datastore. You can make datastore fetches from anywhere in your code, so they can replace your global variables. You define transactions within your methods to synchronize datastore accesses and ensure a transaction only happens once. You can use transactions in some methods, and don't use transactions when you don't need synchronization.
You shouldn't need your global ArrayLists of minerals. When you handle a purchase, you essentially need a transaction where you fetch a listing from the datastore, or create it if it doesn't exist, update the user, and write it back to the datastore. You probably want to read up on the datastore before continuing.
Upvotes: 1
Reputation: 80340
As noted in comments - you can use transactions to achieve this:
SomeObject
entity with index
and someText
properties.Edit:
(removed section on sharded counters as they do not guarantee )
Note that above solution has a write bottleneck at about 1 write/s. If you need a higher performance solution you could look into using backend instances.
Upvotes: 0
Reputation: 2477
Have a look at DatastoreService.allocateIds - it doesn't matter whether or not you're actually using the generated ID to write a datastore entity, you can get unique long numbers out of this in an efficient manner.
Please note however that they're not guaranteed to be in sequence, they're only guaranteed to be unique - the question doesn't state being sequential as a requirement.
public class PlanetID implements Serializable
{
private DatastoreService ds;
public PlanetID()
{
ds = DatastoreServiceFactory.getDatastoreService();
}
public long generateID()
{
return ds.allocateIds("Kind_That_Will_Never_Be_Used", 1L).getStart().getId();
}
}
Upvotes: 0
Reputation: 9106
Other approach beside transaction is to use a single backend instance to keep your global object, and have all access to the object synchronized there. All other instances need to access this backend instance using URLFetch to get the state of the object.
This is a horrible performance bottleneck though if your app want to scale up smoothly, please don't use it, I'm just pointing out alternative approaches. In fact, if possible, please kindly avoid the need to have a synchronized global object on a distributed application in the first place.
Upvotes: 0