java 多线程 synchronized与lock的通信机制等问题,结合相应实例说明
1. 利用多线程实现如下需求:
写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z;
2. 使用synchronized 实现
public class Test13
{
public synchronized void printNum(){
// System.out.println(Thread.currentThread().getName() + "num:\t" + Thread.currentThread().getId());
for (int i = 1; i <= 52; i++)
{
System.out.print(i + ",");
if (i%2 == 0)
{
try
{
this.notify();
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public synchronized void printLetter(){
// System.out.println(Thread.currentThread().getName() + "letter:\t" + Thread.currentThread().getId());
for (int i = 0; i < 26; i++)
{
try
{
System.out.println((char)('A'+i));
this.notify();
if (i != 25)
{
this.wait();
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public class Test12
{
public static void main(String[] args) throws InterruptedException
{
//写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z;
// Test13 test = new Test13();
Test14 test = new Test14();
Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
try
{
test.printNum();
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
test.printLetter();
}
});
t1.start();
t2.start();
}
}
结果:
1,2,A
3,4,B
5,6,C
7,8,D
9,10,E
11,12,F
13,14,G
15,16,H
17,18,I
19,20,J
21,22,K
23,24,L
25,26,M
27,28,N
29,30,O
31,32,P
33,34,Q
35,36,R
37,38,S
39,40,T
41,42,U
43,44,V
45,46,W
47,48,X
49,50,Y
51,52,Z
2.使用 Lock 实现
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test14
{
private Lock lock = new ReentrantLock(true);
private Condition condition = lock.newCondition();
public void printNum() throws InterruptedException
{
try
{
System.out.println("num 运行了");
// if (true)
if (lock.tryLock(1, TimeUnit.SECONDS))
{
System.out.println("num 获得锁了");
// lock.lock();
for (int i = 1; i <= 52; i++)
{
System.out.print(i + ",");
if (i % 2 == 0)
{
try
{
condition.signal();
if (i != 52)
{
condition.await();
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void printLetter()
{
try
{
System.out.println("Letter 运行了");
// if (true)
if (lock.tryLock(1, TimeUnit.SECONDS))
{
System.out.println("Letter 获得锁了");
// lock.lock();
for (int i = 0; i < 26; i++)
{
try
{
System.out.println((char) ('A' + i));
condition.signal();
if (i != 25)
{
condition.await();
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}
结果 :
Letter 运行了
num 运行了
Letter 获得锁了
A
num 获得锁了
1,2,B
3,4,C
5,6,D
7,8,E
9,10,F
11,12,G
13,14,H
15,16,I
17,18,J
19,20,K
21,22,L
23,24,M
25,26,N
27,28,O
29,30,P
31,32,Q
33,34,R
35,36,S
37,38,T
39,40,U
41,42,V
43,44,W
45,46,X
47,48,Y
49,50,Z
51,52,
明明是先 t1.start(); 但是这里先运行,先得到锁的却是t2. 这里用的是公平锁 Lock lock = new ReentrantLock(true); 保证先申请的线程先获得锁. 这说明线程的运行顺序是由JVM随机分配的,并不是我们代码中先开启就先运行.
二、解读:
1. synchronized 底层使用的也是 reentrantLock ,默认是非公平锁(先申请锁的线程不一定先获得锁),synchronized 使用的线程间的通信机制用的是 wait/notify ,在使用 notify() 或者 notifyAll() 进行通知时,被通知的线程或者顺序是由JVM随机选择的.
2. reentrantLock 默认非公平锁,线程间使用 condition 进行通信.利用 Condition 可以实现 "选择性通知" ,condition.await() 进行阻塞; condition.signal(); 进行唤醒
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
如上所示,同一个lock 可以产生多个 condition对象.可以用不同的 comdition对象去操作不同的线程.
问题:
在上面的运用场景中当使用 synchronized 时每次都是t1线程先运行,先获得锁.但是使用 lock的 trylock方法时,运行却是相当的不确定,有时是t1先运行,先获得锁,有时是t2. 但是不使用trylock方法而改使用lock.lock()方法时,试了很多次,跟 使用 synchronized 时是一样的.前面有提到线程的运行顺序是JVM随机分配的,在这里似乎又难以解释这种现象.谜!
public class Test15 extends Thread { @Override public void run() { super.run(); System.out.println(Thread.currentThread().getName() + ": 运行了"); } }
public class Test16 { public static void main(String[] args) { Thread[] arr = new Thread[10]; for (int i = 0; i < arr.length; i++) { arr[i] = new Test15(); arr[i].setName("线程: " + i); } for (int i = 0; i < arr.length; i++) { arr[i].start(); } } }
结果:
线程: 0: 运行了 线程: 1: 运行了 线程: 4: 运行了 线程: 2: 运行了 线程: 3: 运行了 线程: 6: 运行了 线程: 8: 运行了 线程: 7: 运行了 线程: 5: 运行了 线程: 9: 运行了
顺序开启10个线程,但是线程的运行顺序和开启顺序是不一致的.