Variáveis (estáticas ou não) só ficam thread safe se:
1. Todos os seus acessos forem sincronizados;
2. Toda sua sincronização se basear no mesmo monitor;
Se você usa um método não estático, cada sincronização terá um monitor diferente, o que viola a condição 2. Ou seja, se você tem 2 objetos A e B, a sincronização de A será feita sobre o objeto A, e a sincronização de B sobre o objeto B. Mas, como a variável estática é compartilhada por A e B, uma sincronização "local" não serve pois duas threads diferentes podem obter monitores diferentes, e usar a variável estática ao mesmo tempo, de maneira compartilhada. E isso acaba com a sua segurança.
Para resolver esse problema, você também precisa de um monitor compartilhado. Ou seja, basta usar um objeto que a e b compartilhem. O java sugere o uso do objeto Class() da sua classe, mas isso não é obrigatório. O importante apenas é que ambas as instâncias compartilhem o mesmo objeto, por exemplo:
public class TestSeven extends Thread {
private static int x; // UM CAMPO STATIC.
private static int[0] y = new int[0]; //Um objeto static qualquer.
public void doThings() {
synchronized(y) { //Note que y é compartilhado entre todos os objetos da classe.
int current = x;
current++;
x = current;
}
public void run() {
doThings();
}
}
Deixar o método doThings() estático tem o mesmo efeito, por por padrão o Java irá fazer a sincronização em TestSeven.class, que também é compartilhado por ambos os objetos.