Se questo è Java, per prima cosa dai un'occhiata lunga a java.util.concurrent. Cercare di reinventare ciò che è stato già risolto crea così tanti problemi sottili. Sembra semplice, ma è incredibilmente difficile farlo correttamente.
Stai attento a pensare a quello che stai facendo. Sembra che tu stia chiedendo un thread per produrre dati e l'altro thread per consumare i dati. Non vuoi bloccare, quindi non hai altra scelta che fare il secondo thread ripetutamente. Ci sono tutti i tipi di modelli di design stabiliti per questo. Chiediti: (1) il primo thread produce un "evento" booleano (come "è successo"); (2) l'evento deve essere ripristinato e si ripresenta; (3) combinerai l'evento con i dati (come un booleano più una stringa); e così via. Ognuna di queste considerazioni aggiunge complessità per la sincronizzazione tra i thread.
Un difetto comune per (3) sopra, è un metodo o blocco mutex sincronizzato per chiedere "ci sono dati", quindi sbloccare, quindi il secondo thread pensa che ora desideri i dati e chiede "dammi quei dati". Tra "ci sono dati" e "dammi quei dati", il primo thread può reimpostare lo stato su nessun evento presente e reimpostare i dati su nulla. Il secondo thread richiede ora qualcosa che non esiste.
Piuttosto che dare la caccia a tutte queste interazioni sottili, cerca di riutilizzare un modello di progettazione e utilizzare un'utilità concorrente consolidata.