Concorrencia com Thread e banco de dados

Olá a todos do GUJ, eu tenho o seguinte problema com concorrência usando Thread.
O código abaixo deve Pegar o ultimo número de uma coluna de uma tabela e adicionar +1 para salvar novamente no banco, o problema é esse número não pode se repetir e por causa da thread ele esta se repetindo.

O banco e isso .

----------------------------
---- Items ----------------+
id | numeroCupom | qtdItem |
============================

Não posso usar auto_incremente em qtdItem já que se o número do cupom mudar a quatidade de items tem que ser zerada. O professor que passou esse exercicio disse que tem duas maneiras de consertar usando o select for isert e outra forma que não lembro, eu tentei com a select for insert porém continuou inserindo qtdItem duplicadas.

O código atual insere algo no banco tipo:

----------------------------
---- Items ----------------+
id | numeroCupom | qtdItem |
1  | 5           | 1       |
2  | 5           | 2       |
3  | 5           | 2       |

O correto seria

----------------------------
---- Items ----------------+
id | numeroCupom | qtdItem |
1  | 5           | 1       |
2  | 5           | 2       |
3  | 5           | 3       |
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package concorrencia;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Francisco
 */
public class Concorrencia {
    
    private static Connection conectar(){
        try {
            Class.forName("com.mysql.jdbc.Driver");
            return DriverManager.getConnection("jdbc:mysql://localhost/fiscal", "jp", "");
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(Concorrencia.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SQLException ex) {
            Logger.getLogger(Concorrencia.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return null;
    }
    
    private static int getProximoId(Connection con, int cupomId) throws SQLException{
        PreparedStatement stmt = con.prepareStatement("select max(nr_item) from item_cupom where cupom_id = ?");
        stmt.setInt(1, cupomId);
        ResultSet rs = stmt.executeQuery();
        
        try{
            if(rs.next()){
                return rs.getInt(1) + 1;
            } else {
                return 1;
            }
        } finally {
            rs.close();
            stmt.close();
        }
    }
    
    public static void inserirItem(Connection con, int cupomId, boolean demorar) throws SQLException, InterruptedException{
        int nrItem = getProximoId(con, cupomId);
        
        if(demorar) {
            Thread.sleep(200);
        }
        
        PreparedStatement pstmt = con.prepareStatement("insert into item_cupom(cupom_id, nr_item) values(?, ?);");
        pstmt.setInt(1, cupomId);
        pstmt.setInt(2, nrItem);
        pstmt.execute();
        pstmt.close();
    }
    
    private static boolean seraQueVaiDemorar(){
        double valor = Math.random();
        return valor > 0.8;
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Connection con = conectar();
        
        for (int i = 0; i < 2; i++) {
            new Thread() {
                @Override
                public void run() {
                    for(int i = 0; i < 300; i++){
                        try {
                            inserirItem(con, 5, seraQueVaiDemorar());
                        } catch (Exception ex) {
                            Logger.getLogger(Concorrencia.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                }
            }.start();
        }
    }
}

No método inserirItem, acredito que se você sincronizar a chamada ao método getProximoId, usando o cupomId como monitor, funcionará da forma como você quer.

Esse é um exercicio feito examente para dar esse tipo de erro, a proposta e conserta o erro sem alterar o metodo que força o delay da thread.