Deadlock issue with Striped.lock() - java

I'm using Stripe.lock() from com.google.guava.guava-28.0-jre.jar
public class NamedLock {
private static final Logger LOGGER = Logger.getLogger(NamedLock.class);
private Striped<Lock> locks;
public NamedLock() {
locks = Striped.lock(1023);
}
/**
* Test method
*
* #param args
* #throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
NamedLock namedLock = new NamedLock();
Runnable runnable = () -> {
namedLock.lock("aAp1h0000004oEcCAI");
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
}
namedLock.lock("aAT1h0000001OzCGAU");
};
new Thread(runnable).start();
TimeUnit.SECONDS.sleep(2);
namedLock.lock("aAp1h0000004oFRCAY");
namedLock.lock("aAp1h0000004oKYCAY");
}
/**
* Acquires the lock for the Names.
*
* #param names
* the Names
*/
public void lock(String... names) {
String key = Arrays.toString(names);
LOGGER.debug("Locking with Key : " + key);
Lock lock = locks.get(key);
LOGGER.debug("Acquiring Lock : " + lock);
lock.lock();
LOGGER.debug("Lock acquired : " + lock);
}
/**
* Releases the lock acquired for the Names.
*
* #param names
* the Names
*/
public void unlock(String... names) {
String key = Arrays.toString(names);
LOGGER.debug("Unlocking with Key : " + key);
Lock lock = locks.get(key);
LOGGER.debug("Releasing Lock : " + lock);
lock.unlock();
LOGGER.debug("Lock released : " + lock);
}
}
Result for the program :
| DEBUG | 2019-08-13 14:58:33.973 | Thread-0 | NamedLock:61 | Locking with Key : [aAp1h0000004oEcCAI]
| DEBUG | 2019-08-13 14:58:33.975 | Thread-0 | NamedLock:63 | Acquiring Lock : com.google.common.util.concurrent.Striped$PaddedLock#ac92ce3[Unlocked]
| DEBUG | 2019-08-13 14:58:33.975 | Thread-0 | NamedLock:65 | Lock acquired : com.google.common.util.concurrent.Striped$PaddedLock#ac92ce3[Locked by thread Thread-0]
| DEBUG | 2019-08-13 14:58:35.973 | main | NamedLock:61 | Locking with Key : [aAp1h0000004oFRCAY]
| DEBUG | 2019-08-13 14:58:35.974 | main | NamedLock:63 | Acquiring Lock : com.google.common.util.concurrent.Striped$PaddedLock#574caa3f[Unlocked]
| DEBUG | 2019-08-13 14:58:35.980 | main | NamedLock:65 | Lock acquired : com.google.common.util.concurrent.Striped$PaddedLock#574caa3f[Locked by thread main]
| DEBUG | 2019-08-13 14:58:35.981 | main | NamedLock:61 | Locking with Key : [aAp1h0000004oKYCAY]
| DEBUG | 2019-08-13 14:58:35.982 | main | NamedLock:63 | Acquiring Lock : com.google.common.util.concurrent.Striped$PaddedLock#ac92ce3[Locked by thread Thread-0]
| DEBUG | 2019-08-13 14:58:38.976 | Thread-0 | NamedLock:61 | Locking with Key : [aAT1h0000001OzCGAU]
| DEBUG | 2019-08-13 14:58:38.982 | Thread-0 | NamedLock:63 | Acquiring Lock : com.google.common.util.concurrent.Striped$PaddedLock#574caa3f[Locked by thread main]
The program never terminates and goes into DeadLock state, waiting for each others lock though all the keys are different.

From the JavaDoc:
Note that if key1 is not equal to key2, it is not guaranteed that
striped.get(key1) != striped.get(key2)
So presumably more than one of your keys exists in the same stripe. The hash codes are relatively close together for some of them so it seems reasonable:
System.out.println("[aAp1h0000004oEcCAI]".hashCode()); // -1286359401
System.out.println("[aAp1h0000004oFRCAY]".hashCode()); // -1273429611
System.out.println("[aAp1h0000004oKYCAY]".hashCode()); // -1123819209
System.out.println("[aAT1h0000001OzCGAU]".hashCode()); // 1694776185
It sounds like Striped is not applicable to your use-case and that you need one lock per object.

Related

Java - Problem with the logger on console

i'm developing the Connect4 game in Java and i'm having problem with the Logger. I don't know why prints in different place ad between other kind of prints.
public void setPlacement(Move lastMove){
Logger.getGlobal().info("Player" + lastMove.getPlayerIndex() + " placed a checker in position : " + lastMove.toString());
display();
}
The method display() just prints the grid of the game. Here's the output of the above method :
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| - | - | - | - | - | - | - |
| - | - | - | - | - | - | - |
| - ago 01, 2019 6:10:24 PM it.unicam.cs.pa.connectFour.GameViewsetPlacement
INFO: Player1 placed a checker in position : column 4, row 5
| - | - | - | - | - | - |
| - | - | - | - | - | - | - |
| - | O | O | O | - | - | - |
| O | X | X | X | - | - | - |
*****************************
Can someone explain me why the logger acts like this?
The response to this question concerning System::out and System::err may help you. Specifically, your console is displaying two streams of output at the same time, with no ordering guarantees between messages sent to different streams.
One way to enable Loggers to write to a standard output stream is to create your own StreamHandler and configure your Logger to send input to the handler instead. You may also have to disable parent handlers to avoid duplicate output. You may also want to ensure that output is proactively sent to the desired output stream, as so:
Handler h = new StreamHandler(System.out, formatter)
{
public void publish(LogRecord record)
{
if(record == null)
return;
super.publish(record);
super.flush();
}
};

java swing jtable add two table from database in single table [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I have table named tbl_income and tbl_expenses...
tbl_income contain four column and four row and tbl_expenses contain four column and three row link in picture below.
I want to mank single table in java swing JTable link picture in below.
how can i make like this.... sorry for bad english..
This is what i've tried:
DefaultTableModel model = (DefaultTableModel) tbldailybook.getModel();
try {
Statement stmt = db.cn.createStatement();
ResultSet rs = stmt.executeQuery("select * from tblincome,tblexpenses");
while (rs.next()) {
model.addRow(new Object[]{rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(6), rs.getString(7), rs.getString(8)});
}
} catch (Exception e) {
}
tblincome have single row but row reply
This is a solution with separate extraction of the two tables and their integration into a common TableModel.
public class MyTableModel extends AbstractTableModel implements TableModel {
private List<Data> inclomeList;
private List<Data> expenseList;
private void setData(List<Data> list, Data data) {
int rows = getRowCount();
int row = list.size();
list.add(data);
if(row < rows) {
fireTableRowsUpdated(row, row);
}
else {
fireTableRowsInserted(row, row);
}
}
public void setIncomeData(Data data) {
if(inclomeList == null) {
inclomeList = new ArrayList<>();
}
setData(inclomeList, data);
}
public void setExpenseData(Data data) {
if(expenseList == null) {
expenseList = new ArrayList<>();
}
setData(expenseList, data);
}
#Override
public String getColumnName(int column) {
switch (column) {
case 0:
case 3:
return "Date";
case 1: return "Income";
case 4: return "Expenses";
case 2:
case 5:
return "Amount";
default:
return super.getColumnName(column);
}
}
#Override
public int getRowCount() {
if(inclomeList == null || expenseList == null) {
if(inclomeList != null) {
return inclomeList.size();
}
else if(expenseList != null) {
return expenseList.size();
}
return 0;
}
return Math.max(inclomeList.size(), expenseList.size());
}
#Override
public int getColumnCount() {
return 6;
}
#Override
public Object getValueAt(int row, int column) {
Data inclome = null;
Data expense = null;
if(inclomeList != null && inclomeList.size() > row) {
inclome = inclomeList.get(row);
}
if(expenseList != null && expenseList.size() > row) {
expense = expenseList.get(row);
}
switch (column) {
case 0: return inclome != null ? inclome.getDate() : "";
case 1: return inclome != null ? inclome.getName() : "";
case 2: return inclome != null ? inclome.getAmount() : "";
case 3: return expense != null ? expense.getDate() : "";
case 4: return expense != null ? expense.getName() : "";
case 5: return expense != null ? expense.getAmount() : "";
}
return null;
}
public void update() {
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
Database database = new Database();
inclomeList = database.getData(Database.TBL_INCOME);
expenseList = database.getData(Database.TBL_EXPENSES);
return null;
}
#Override
protected void done() {
try {
get();
fireTableDataChanged();
}
catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
};
worker.execute();
}
}
Interface for working with the database:
public class Database {
public static final String TBL_INCOME = "tbl_income";
public static final String TBL_EXPENSES = "tbl_expenses";
private Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://192.168.40.5/test", "root", "");
}
public List<Data> getData(String tbl_name) throws SQLException {
try (Connection connection = getConnection()) {
String query = "select * from " + tbl_name;
try(ResultSet rs = connection.createStatement().executeQuery(query)) {
List<Data> list = new ArrayList<>();
while (rs.next()) {
Data data = new Data();
data.setDate(rs.getDate("date"));
data.setName(rs.getString("particular"));
data.setAmount(rs.getDouble("amount"));
list.add(data);
}
return list;
}
}
}
}
MainFrame.java
public class MainFrame extends JFrame {
private JTable table = new JTable(new MyTableModel());
public MainFrame() throws HeadlessException {
super("MainFrame");
createGUI();
}
private void createGUI() {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setPreferredSize(new Dimension(600, 400));
JButton addI = new JButton("+");
addI.addActionListener(e -> ((MyTableModel)table.getModel()).setIncomeData(new Data()));
JButton addE = new JButton("+");
addE.addActionListener(e -> ((MyTableModel)table.getModel()).setExpenseData(new Data()));
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
panel.add(addI);
panel.add(addE);
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane, BorderLayout.CENTER);
add(panel, BorderLayout.PAGE_END);
pack();
setLocationRelativeTo(null);
((MyTableModel)table.getModel()).update();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MainFrame().setVisible(true));
}
}
+----+------------+----------------+--------+
| id | dateValue | particular | amount |
+----+------------+----------------+--------+
| 1 | 2017-02-02 | Cash Deposit | 1 |
| 2 | 2017-02-03 | Cheque Deposit | 2 |
| 3 | 2017-02-08 | Cash Deposit | 3 |
| 4 | 2017-02-07 | Product Sales | 4 |
+----+------------+----------------+--------+
+----+------------+------------+--------+
| id | dateValue | particular | amount |
+----+------------+------------+--------+
| 1 | 2017-05-07 | Factory | 6 |
| 2 | 2017-06-02 | Staff | 7 |
| 3 | 2017-06-03 | Travel | 8 |
+----+------------+------------+--------+
I then used select * from income left join expenses on income.id = expenses.id to query the database and got:
+----+------------+----------------+--------+----+------------+------------+--------+--+
| id | dateValue | particular | amount | id | dateValue | particular | amount | |
+----+------------+----------------+--------+----+------------+------------+--------+--+
| 1 | 2017-02-02 | Cash Deposit | 1 | 1 | 2017-05-07 | Factory | 6 | |
| 2 | 2017-02-03 | Cheque Deposit | 2 | 2 | 2017-06-02 | Staff | 7 | |
| 3 | 2017-02-08 | Cash Deposit | 3 | 3 | 2017-06-03 | Travel | 8 | |
| 4 | 2017-02-07 | Product Sales | 4 | | | | | |
+----+------------+----------------+--------+----+------------+------------+--------+--+
Which based on the currently available information is probably the best I can do
You could the use something like Most simple code to populate JTable from ResultSet or Retrieving Data from JDBC Database into Jtable to build the JTable ... as available examples
if second table have more row than table one .. record will not display which are more id in second table – raman dangol 8 hours ago
Also, if there are inconsistent IDs, the records will be lost. That's why I said this was not a trivial question
Then in those cases, something like FULL OUTER JOIN would be more useful. However, based on the information I have, full outer join is not supported in MySQL, because that would be useful. However, based on How to do a FULL OUTER JOIN in MySQL? we could do something like...
select * from income left join expenses on income.id = expenses.id union select * from income right join expenses on income.id = expenses.id where income.id is null
which can generator something like
+----+------------+----------------+--------+----+------------+------------+--------+
| id | datevalue | particular | amount | id | datevalue | particular | amount |
+----+------------+----------------+--------+----+------------+------------+--------+
| 1 | 2017-02-02 | Cash Deposit | 1.0 | 1 | 2017-05-07 | Factory | 6.0 |
| 2 | 2017-02-03 | Cheque Deposit | 2.0 | 2 | 2017-06-02 | Staff | 7.0 |
| 3 | 2017-02-03 | Cash Deposit | 3.0 | 3 | 2017-06-03 | Travel | 8.0 |
| 4 | 2017-02-03 | Product Sales | 4.0 | 4 | 2017-10-01 | Test 1 | 10.0 |
| 5 | 2017-10-02 | Test 2 | 20.0 | | | | |
+----+------------+----------------+--------+----+------------+------------+--------+
Or, if you prefer to keep things aligned to their "selected sides", something like
select income.id, income.datevalue, income.PARTICULAR, income.AMOUNT,
expenses.id, expenses.datevalue, expenses.PARTICULAR, expenses.AMOUNT
from income join expenses on income.id = expenses.id
union all
select income.id, income.datevalue, income.PARTICULAR, income.AMOUNT,
null, null, null, null
from INCOME where not exists (select expenses.id from expenses where expenses.id = income.id)
union all
select null, null, null, null,
expenses.id, expenses.datevalue, expenses.PARTICULAR, expenses.AMOUNT
from expenses where not exists (select income.id from income where income.id = expenses.id)
Which can generate something like...
+----+------------+----------------+--------+----+------------+------------+--------+
| id | datevalue | particular | amount | id | datevalue | particular | amount |
+----+------------+----------------+--------+----+------------+------------+--------+
| 1 | 2017-02-02 | Cash Deposit | 1.0 | 1 | 2017-05-07 | Factory | 6.0 |
| 2 | 2017-02-03 | Cheque Deposit | 2.0 | 2 | 2017-06-02 | Staff | 7.0 |
| 3 | 2017-02-03 | Cash Deposit | 3.0 | 3 | 2017-06-03 | Travel | 8.0 |
| 4 | 2017-02-03 | Product Sales | 4.0 | 4 | 2017-10-01 | Test 1 | 10.0 |
| | | | | 5 | 2017-10-02 | Test 2 | 20.0 |
+----+------------+----------------+--------+----+------------+------------+--------+
At the end of the day, it's still a database issue.
For simplicity, you could create one or more database views in order to simply the query

How to answer all SIP Calls automatically in Java

I want automatically answer all sip calls.
When I respond with trying or ringing, the transaction is in a proceeding state,
but when I send the OK response, the transaction is in a terminated state.
This is my code:
public void processRequest(RequestEvent requestReceivedEvent)
{
Address contactAddress = myAddressFactory.createAddress("sip:" + myIP + ":" + myPort);
myContactHeader = myHeaderFactory.createContactHeader(contactAddress);
Request req = requestReceivedEvent.getRequest();
myGUI.display("<<< " + req.toString());
String method = req.getMethod(); //bad request type.
FromHeader from = (FromHeader) req.getHeader("From");
Response response = null;
try
{ //Reply with OK
response = myMessageFactory.createResponse(200, req);
ToHeader toHeader = (ToHeader) response.getHeader(ToHeader.NAME);
toHeader.setTag("888"); //Identifier, specific to your application
ServerTransaction st = mySipProvider.getNewServerTransaction(req);
response.addHeader(myContactHeader);
st.sendResponse(response);
System.out.println("Ok response: " + st.getState());
}
catch (Exception e)
{
e.printStackTrace();
}
}
Thanks in advance.
INVITE server transactions to go terminated state after 200 OK. It's normal. Your call is answered and you shouldn't worry about it.
(see diagram of state machine from RFC)
|INVITE
|pass INV to TU
INVITE V send 100 if TU won't in 200ms
send response+-----------+
+--------| |--------+101-199 from TU
| | Proceeding| |send response
+------->| |<-------+
| | Transport Err.
| | Inform TU
| |--------------->+
+-----------+ |
300-699 from TU | |2xx from TU |
send response | |send response |
| +------------------>+
| |
INVITE V Timer G fires |
send response+-----------+ send response |
+--------| |--------+ |
| | Completed | | |
+------->| |<-------+ |
+-----------+ |
| | |
ACK | | |
- | +------------------>+
| Timer H fires |
V or Transport Err.|
+-----------+ Inform TU |
| | |
| Confirmed | |
| | |
+-----------+ |
| |
|Timer I fires |
|- |
| |
V |
+-----------+ |
| | |
| Terminated|<---------------+
| |
+-----------+
Figure 7: INVITE server transaction

Can't alter table(blocked) while hibernate is connected to mysql server

I have a table name test;
mysql> desc test;
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | 0 | |
| name | varchar(255) | YES | | NULL | |
| gendar | varchar(255) | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> select * from test;
+----+------+--------+
| id | name | gendar |
+----+------+--------+
| 0 | John | male |
+----+------+--------+
1 row in set (0.00 sec)
And have a hibernate Entity like this:
#Entity
#Table(name = "test",schema = "", catalog = "mydb")
public class TestEntity {
private int id;
private String name;
private String gendar;
#Id
#Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Basic
#Column(name = "gendar")
public String getGendar() {
return gendar;
}
public void setGendar(String gendar) {
this.gendar = gendar;
}
}
The hibernate.xml is:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost:3306/mydb</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.username">test</property>
<property name="connection.password">test</property>
<mapping class="me.armnotstrong.sql.TestEntity" />
<!-- DB schema will be updated if needed -->
<!-- <property name="hbm2ddl.auto">update</property> -->
</session-factory>
</hibernate-configuration>
The hibernate session is generated by this factory class:
public class HBSession {
private static final SessionFactory ourSessionFactory;
private static final ServiceRegistry serviceRegistry;
static {
try {
Configuration configuration = new Configuration();
configuration.configure();
serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
ourSessionFactory = configuration.buildSessionFactory(serviceRegistry);
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession() throws HibernateException {
Session session = ourSessionFactory.openSession();
return session;
}
}
And I just add a user to the db by this
public class TestHb {
public static void main(String[] argvs){
Session session = HBSession.getSession();
TestEntity testEntity = new TestEntity();
testEntity.setName("John");
testEntity.setGendar("male");
Transaction tx = session.beginTransaction();
session.save(testEntity);
tx.commit();
session.close();
}
}
After run the TestHB code above to add a user to mysql, the session seemed to just hang there, and won't colse, diagnose using netstat -nap just proved my guess, but it's ok, I think. In fact, this just simulate the condition of a long connection to the db in the product environment which hibernate make.
The question comes that, when I alter the table when hibernate connection still there, The mysql client just blocked. and wont do the alter action as presumed unless I restart the mysql service. and in product environment, after restart the mysql service, one thing come after another, The hibernate app will not work anymore.
So what should I do to alter the table and keep my hibernate also working?
as required by #Florent, here is some addition info come out of command SHOW FULL PROCESSLIST and SHOW OPEN TABLES;
mysql> show open tables;
+----------+----------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+----------+--------+-------------+
| test | merchant | 0 | 0 |
| test | test | 0 | 0 |
| test | orders | 0 | 0 |
| test | product | 0 | 0 |
| test | codes | 0 | 0 |
+----------+----------+--------+-------------+
5 rows in set (0.02 sec)
mysql> show full processlist;
+-----+--------+-----------------+--------+---------+------+-------+-----------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+--------+-----------------+--------+---------+------+-------+-----------------------+
| 42 | test | localhost:35790 | test | Sleep | 4 | | NULL |
| 43 | test | localhost:35801 | test | Sleep | 4 | | NULL |
| 44 | test | localhost:35802 | test | Sleep | 4 | | NULL |
| 45 | test | localhost:35803 | test | Sleep | 4 | | NULL |
| 46 | test | localhost:35804 | test | Sleep | 4 | | NULL |
| 157 | test | localhost:51516 | test | Sleep | 174 | | NULL |
| 161 | test | localhost:53988 | test | Sleep | 174 | | NULL |
| 180 | test | localhost:58501 | test | Sleep | 174 | | NULL |
| 192 | test | localhost:47228 | test | Sleep | 7217 | | NULL |
| 193 | test | localhost:49372 | test | Sleep | 4485 | | NULL |
| 196 | test | localhost | test | Sleep | 9256 | | NULL |
| 197 | test | localhost | test | Sleep | 4555 | | NULL |
| 198 | test | localhost:42411 | test | Sleep | 4485 | | NULL |
| 200 | test | localhost | test | Query | 0 | NULL | show full processlist |
+-----+--------+-----------------+--------+---------+------+-------+-----------------------+
14 rows in set (0.00 sec)

Why does ArrayBlockingQueue signal 'not full' after catching InterruptedException?

In ArrayBlockingQueue, inside the put method, why does it call notFull.signal() after catching InterruptedException? When the thread is going to terminate, why does it send out a 'not full' signal?
From the source:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
} finally {
lock.unlock();
}
}
Imagine the following scenario:
Threads 1 and 4 are waiting on notFull, ie, the queue is full, and the lock is released.
Thread 2 holds the lock, and is about to remove an element from the queue.
Thread 3 interrupts Thread 1.
Now imagine the following interleaving:
+-+--+--------+-----------+------- TIME +---+------------+---------------------->
| | | | | | | |
+---------+ +------------------+ +----------+ |
| Thread2 | | Thread2 | | Thread2 | |
| lock() | | notFull.signal() | | unlock() | |
+---------+ +------------------+ +----------+ |
| | | | |
+---------------------+ | | |
| Thread3 | | | |
| Thread1.interrupt() | | | |
+---------------------+ | | |
| | | |
+---------------+ +-------------+ +---------+ +----------------------+
| Thread1 | | Thread1 | | Thread1 | | Thread1 |
| interrupted() | | signalled() | | lock() | | InterruptedException |
+---------------+ +-------------+ +---------+ +----------------------+
What if InterruptedException wasn't caught, and Thread 1 was just to unlock and abandon the wait? What would happen to Thread 4, who was still waiting for a signal on notFull? The signal had already been sent by Thread 2, but it just so happened that the receiving thread, Thread 1, had been interrupted, and the signal was wasted.
In short: If the thread received a signal when it was also interrupted, it passes along the signal to another thread, so that it isn't lost. This prevents threads from waiting indefinitely for something that already happened.

Categories