JUC并发编程
- 五、多线程锁
- 5.1、synchronized实现同步的基础
- 5.2、公平锁与非公平锁
- 5.3、可重入锁
- 5.4、死锁
- 5.4.1、概念
- 5.4.2、案例
- 5.4.3、查看死锁
- 六、Callable接口
- 6.1、概述
- 6.2、FutureTask概述和原理
- 6.3、案例
- 七、辅助类
- 7.1、减少计数CountDownLatch
- 7.2、循环棚栏CyclicBarrier
- 7.3、信号灯Semaphore
五、多线程锁
5.1、synchronized实现同步的基础
Java中的每一个对象都可以作为锁。
具体表现为以下3种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是synchonized括号里配置的对象
5.2、公平锁与非公平锁
//创建非公平锁
//可能一个线程吃掉所有任务 其他线程被“饿死” 但效率高
private final ReentrantLock lock = new ReentrantLock(false);
//创建公平锁
//每个线程都领到任务 但效率降低
private final ReentrantLock lock = new ReentrantLock(true);
5.3、可重入锁
同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而发生阻塞
class RLock {
public synchronized void add(){
add();
}
}
public class Demo06 {
public static void main(String[] args) {
new RLock().add();
}
}
此时不会堵塞 只会栈溢出java.lang.StackOverflowError
5.4、死锁
5.4.1、概念
两个或者两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象。如果没有外力干涉,他们无法再执行下去。
产生死锁原因:
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
5.4.2、案例
import java.util.concurrent.TimeUnit;
public class DeadLock {
static Object o1=new Object();
static Object o2=new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"持有o1,试图获取o2");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"获取o2");
}
}
},"aa").start();
new Thread(()->{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"持有o2,试图获取o1");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"获取o1");
}
}
},"bb").start();
}
}
5.4.3、查看死锁
- jps
先输入jps -l
找到进程号 - jstack
jvm自带堆栈跟踪工具
输入jstack 进程号
在最后提示Found xxx deadlock.
发现xx个死锁
六、Callable接口
6.1、概述
通过使用Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即run ( )完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口。
Runnable | Callable | |
---|---|---|
是否有返回值 | 无 | 有 |
是否抛出异常 | 无 | 有 |
实现方法 | run() | call() |
6.2、FutureTask概述和原理
FutureTask类 实现了RunnableFuture接口,而RunnableFuture 继承了Runnable和Future接口,所以本质上 FutureTask是Runnable的一种实现。
6.3、案例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//比较Callable Runable
class MyThread1 implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable{
@Override
public Integer call() throws Exception {
return 200;
}
}
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建Runable线程
new Thread(new MyThread1(),"aa").start();
//创建Callable线程
//不能直接替换runnable的方式,因为Thread类的构造方法根本没有Callable.
//FutureTask构造可以传递callable
//创建FutureTask线程
FutureTask<Integer> futureTask1=new FutureTask<>(new MyThread2());
//lambda简化FutureTask线程
FutureTask<Integer> futureTask2=new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName()+" in Callable");
return 1024;
});
new Thread(futureTask2,"bb").start();
while (!futureTask2.isDone()){
System.out.println("wait");
}
System.out.println(futureTask2.get());
System.out.println(futureTask1.get());
System.out.println(Thread.currentThread().getName()+" over");
}
}
七、辅助类
7.1、减少计数CountDownLatch
CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。
- CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
- 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
- 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
案例:6个同学陆续离开教室后才可以关门
正常写法
public class CountDownLatchDemo {
public static void main(String[] args) {
//6个同学陆续离开教室之后
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread( ).getName()+"号同学离开了教室");
},String.valueOf(i)).start();
}
System.out.println(Thread.currentThread( ).getName()+"锁门了");
}
}
会出现问题:
更改之后:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//创建CountDownLatch
CountDownLatch countDownLatch=new CountDownLatch(6);
//6个同学陆续离开教室之后
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread( ).getName()+"号同学离开了教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待
countDownLatch.await();
System.out.println(Thread.currentThread( ).getName()+"锁门了");
}
}
7.2、循环棚栏CyclicBarrier
允许一组线程全部等待彼此达到共同屏障点的同步辅助。循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
例子:集齐7颗龙珠就可以召唤神龙
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(NUMBER,()->{
System.out.println("已经集齐7颗龙珠,可以召唤神龙了");
});
for (int i = 1; i <=7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread( ) .getName()+"星龙被收集到了");
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
7.3、信号灯Semaphore
一个单向隧道能同时容纳10个小汽车或5个卡车通过(1个卡车等效与2个小汽车), 而隧道入口记录着当前已经在隧道内的汽车等效比重. 比如1个小汽车和1个卡车, 则隧道入口显示3. 若隧道入口显示10表示已经满了. 当汽车驶出隧道之后, 隧道入口显示的数字则会相应的减小. 于这个示例相符合场景非常适合用信号量.
案例:抢车位,6部汽车3个停车位
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread( ) .getName()+"抢到了车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread( ) .getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}