技术/杨33
Java并发编程系列:
(一)Java并发编程,线程的使用
(二)Java并发编程之关键字synchronized
(三)Java并发编程之wait和notify、线程的设计模式使用
一、park和unpark方法
这两个方法是工具类LockSupport中的方法。
用法如下:
暂停当前线程:LockSupport.park();
恢复当前线程的运行:LockSupport.unpark(该参数为被暂停的线程对象);
二、线程的6种状态切换
Java线程的6种状态:
1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3.阻塞(BLOCKED):表示线程阻塞于锁。
4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。
视频教程:详细说明Java线程的状态是如何切换
三、线程的活跃性问题:死锁
锁的粒度没细分之前,并发度太低,影响代码效率。
但是锁的粒度细分后,也有好处和坏处:
死锁就是:t1线程获得A对象的锁,接下来想获取B对象的锁;t2线程获得B对象的锁,接下来想获取A对象的锁,如果t1、t2线程分别永远不释放持有的A对象、B对象锁,接下来,t1、t2线程分别想获取B对象、A对象的锁,这两个t1、t2线程将永远的等待下去,永远被阻塞,这就是死锁的现象。
一段死锁的代码如下:
public static void main(String[] args) {
//对象锁A
Object objectA = new Object();
//对象锁B
Object objectB = new Object();
//线程t1
Thread t1 = new Thread(() -> {
synchronized (objectA) {
System.out.println("t1线程取得A对象的锁");
synchronized (objectB) {
System.out.println("t1线程取得B对象的锁");
}
}
}, "t1");
//线程t2
Thread t2 = new Thread(() -> {
synchronized (objectB) {
System.out.println("t2线程取得B对象的锁");
synchronized (objectA) {
System.out.println("t2线程取得A对象的锁");
}
}
}, "t2");
t1.start();
t2.start();
}
运行结果如图:
在编码过程中,发现使用Junit单元测试不支持多线程测试,比如下面代码:
@org.junit.Test
public void threadTest(){
//对象锁A
Object objectA = new Object();
//对象锁B
Object objectB = new Object();
//线程t1
Thread t1 = new Thread(() -> {
synchronized (objectA) {
System.out.println("t1线程取得A对象的锁");
synchronized (objectB) {
System.out.println("t1线程取得B对象的锁");
}
}
}, "t1");
//线程t2
Thread t2 = new Thread(() -> {
synchronized (objectB) {
System.out.println("t2线程取得B对象的锁");
synchronized (objectA) {
System.out.println("t2线程取得A对象的锁");
}
}
}, "t2");
t1.start();
t2.start();
}
结果控制台打印的结果如下图:
为什么线程没结束,就系统退出呢?
那是因为单元测试的底层逻辑是使用了TestRunner类的main方法,即使还有其他的线程在运行,main也会调用System.exit(0),System.exit()是系统调用,通知系统立即结束jvm的运行,即使jvm中有线程在运行,jvm也会停止的。
四、定位死锁的方式
两种方式分别是:
1、使用jconsole工具
2、使用jps和jstack
五、Java对synchronized的优化
在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。
到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比ReentrantLock差。官方也表示,他们也更支持synchronized,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。
六、ReentrantLock
它是在内置锁synchronized无法满足需求的情况下,ReentrantLock可作为一种高级的工具来使用,否则还是优先使用synchronized。
相对于synchronized,它具备以下特点:
与synchronized一样,都支持可重入。
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果不支持可重入,那么第二次获取锁时,自己也会被锁挡住。
ReentrantLock语法:
//ReentrantLock对象
ReentrantLock reentrantLock = new ReentrantLock();
//获取锁
reentrantLock.lock();
try {
//临界区
} finally {
//释放锁
reentrantLock.unlock();
}
ReentrantLock的特点使用代码操作:
1、线程可中断,说明线程在等待获取锁的过程中,可以使用方法interrupt()方法中止等待
代码如下:lockInterruptibly()方法
//ReentrantLock对象
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
System.out.println("尝试获取锁");
//这儿使用的方法是lockInterruptibly()
reentrantLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("被打断了,中断阻塞线程的运行");
//被中断,就结束该方法
return;
}
try {
System.out.println("获取到锁");
}finally {
//释放锁
reentrantLock.unlock();
}
});
//主线程获取到锁,让线程t1阻塞
reentrantLock.lock();
t1.start();
//中断正在阻塞的线程t1
System.out.println("打断阻塞的线程t1");
t1.interrupt();
}
控制台打印结果:
打断阻塞的线程t1
尝试获取锁
被打断了,中断阻塞线程的运行
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at Test.lambda$main$0(Test.java:45)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
2、设置超时时间:tryLock()方法
该方法还有一种有入参的方式:reentrantLock.tryLock(2, TimeUnit.SECONDS),表示,等待2秒钟。
//ReentrantLock对象
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("尝试获取锁");
//这儿使用的方法是tryLock()
if (!reentrantLock.tryLock()) {
System.out.println("获取不到锁");
return;
}
try {
System.out.println("获取到锁");
}finally {
//释放锁
reentrantLock.unlock();
}
});
//主线程先获取到锁,让线程t1阻塞,获取不到锁
reentrantLock.lock();
t1.start();
}
控制台打印结果:
尝试获取锁
获取不到锁
Process finished with exit code 0
3、多个条件变量,来存放处于等待状态的线程队列
使用流程:
//ReentrantLock对象
static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) {
//多个条件condition1、condition2
Condition condition1 = reentrantLock.newCondition();
Condition condition2 = reentrantLock.newCondition();
reentrantLock.lock();
//进入休息室等待锁
condition1.await();
condition2.await();
//唤醒其中一个等待的线程
condition1.signal();
//唤醒所有等待的线程
condition1.signalAll();
}
七、使用ReentrantLock的tryLock()来解决上面标题三的死锁问题
代码如下:
//ReentrantLock对象A
static ReentrantLock reentrantLockA = new ReentrantLock();
//ReentrantLock对象B
static ReentrantLock reentrantLockB = new ReentrantLock();
public static void main(String[] args) {
//线程t1
Thread t1 = new Thread(() -> {
if (reentrantLockA.tryLock()) {
try {
System.out.println("t1线程取得A对象的锁");
if (reentrantLockB.tryLock()) {
try {
System.out.println("t1线程取得B对象的锁");
} finally {
reentrantLockB.unlock();
}
}
} finally {
reentrantLockA.unlock();
}
}
}, "t1");
//线程t2
Thread t2 = new Thread(() -> {
if (reentrantLockB.tryLock()) {
try {
System.out.println("t2线程取得B对象的锁");
if (reentrantLockA.tryLock()) {
try {
System.out.println("t2线程取得A对象的锁");
} finally {
reentrantLockA.unlock();
}
}
} finally {
reentrantLockB.unlock();
}
}
}, "t2");
t1.start();
t2.start();
}
控制台打印结果,没有再出现因为线程死锁,而导致程序无法结束运行
八、总结
使用synchronized或者ReentrantLock,可以达到共享资源的互斥效果,保证临界区中代码的原子性。
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777