Bueno, despues de pasarme una hora mirando, voy a pasarte la forma correcta de hacer esto.
La clave es que al hacer un Thread.run() realmente no estas usando el Objeto Thread solo invocando al metodo run() del mismo.
Así, para actuar sobre el objeto, debes invocar al Thread.start()
Bien, lo que ocurre es que el println, no tiene una sincrinización tal como tu la pretendias, lo he podido solucionar con un sleep y el bucle conjunto.
Te dejo la solución final que yo he encontrado, y si te parece ya podemos pedir a los Administradores que cierren el hilo.
PILA
import java.util.Stack;
public class Pila {
Stack <Character> pila = new Stack<Character>();
private char c;
public synchronized char sacar(){
if(pila.empty()){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
c = pila.pop();
notify();
return c;
}
public synchronized char poner(char c){
if (!pila.empty()){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
pila.push(c);
notify();
return c;
}
}
PRODUCTOR
import java.util.Random;
public class Productor extends Thread {
private Pila pila;
private String alfabeto = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private char c;
private int i;
private Random random = new Random();
public Productor (Pila pila, int cant, int ms){
this.pila = pila;
}
public Productor (Pila p, int i){
this.pila = p;
this.i = i;
}
public synchronized void run (){
c = alfabeto.charAt(random.nextInt(26));
pila.poner(c);
System.out.println("Productor "+i+": "+c);
}
}
CONSUMIDOR
public class Consumidor extends Thread {
private Pila pila;
private int i;
public Consumidor (Pila pila, int i){
this.pila = pila;
this.i= i;
}
public synchronized void run (){
char c;
c = pila.sacar();
System.out.println("Consumidor "+i+": "+c);
}
}
MAIN
public class Main {
public static void main(String[] args) {
Pila pila = new Pila();
for(int i=1; i<11; i++){
Productor pr = new Productor(pila, i);
Consumidor cs = new Consumidor (pila, i);
pr.start();
cs.start();
try{ Thread.sleep(10);
}catch(InterruptedException e){}
}
}
}