并发编程01:基础篇
1.1 线程基础
- 1把锁:synchronized
- 2个并:
- 并行(concurrent):是在同一实体上的多个事件,是在一台机器上“同时”处理多个任务,同一时刻,其实是只有一个事情再发生。
- 并发(parallel):是在不同实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家都在做事情,你做你的,我做我的,各干各的。
- 3个程:
- 进程:在系统中运行的一个应用程序,每个进程都有它自己的内存空间和系统资源
- 线程:也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元。
- 管程:Monitor(锁),也就是我们平时所说的锁。Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码,JVM中同步是基于进入和退出监视器(Monitor管程对象)来实现的,每个对象实例都会有一个Monitor对象,Monitor对象和Java对象一同创建并销毁,底层由C++语言实现。
- 线程分类(一般不做特别说明配置,默认都是用户线程):
- 用户线程:是系统的工作线程,它会完成这个程序需要完成的业务操作。
- 守护线程:是一种特殊的线程为其他线程服务的,在后台默默地完成一些系统性的任务,比如垃圾回收线程就是最典型的例子。守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以假如当系统只剩下守护线程的时候,守护线程伴随着JVM一同结束工作。
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始运行," + (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
t1.setDaemon(true);//通过设置属性Daemon来设置当前线程是否为守护线程
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 主线程结束");
}
}
//输出:t1 开始运行,守护线程
//main 主线程结束--->在main主线程结束后,守护线程会伴随着JVM一同结束工作,
//即使还有循环没有结束
1.2 JUC三大工具类
1.2.1 CountDownLatch(加计数器)
流程:
- CountDownLatch countDownLatch = new CountDownLatch(8);
- countDownLatch.countDown(); 一个线程出来一个人,计数器就 -1
- countDownLatch.await(); 阻塞的等待计数器归零
- 执行后续步骤
样例:
//减计数器
public class CountDownLatchDemo {
//样例说明:8个同学离开教室后,班长关门
public static void main(String[] args) {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch=new CountDownLatch(8);
for(int i=1;i<=7;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
//计数-1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
try {
countDownLatch.await();//阻塞等待计数器归零
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("班长锁门");
}
}
总结:
CountDownLatch使用给定的计数进行初始化。 由于调用了countDown方法,每次-1, await方法会一直阻塞到当前计数达到零,然后释放所有等待线程,并且任何后续的await调用都会立即返回。 这是一种一次性现象——计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier 。
CountDownLatch一个有用属性是它不需要调用countDown线程在继续之前等待计数达到零,它只是阻止任何线程通过await,直到所有线程都可以通过。
1.2.2 CyclicBarrier(减计数器)
流程:
- 创建CyclicBarrier对象
- CyclicBarrier cyclicBarrier = new CyclicBarrier(count, new MyRunnable());
- 编写业务代码
- cyclicBarrier.await(); //在线程里面等待阻塞,累加1,达到最大值count时,触发我们传入进去MyRunnable执行。
样例:
//加计数器
public class CyclicBarrierDemo {
//样例说明:抽奖10次后必中,即第11次必中奖
public static void main(String[] args) {
CyclicBarrier cyclicBarrier=new CyclicBarrier(10,()->{
System.out.println("已经抽奖10次,下一次必中奖");
});
//10次抽奖
for(int i=1;i<=10;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 抽奖一次");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
总结:CyclicBarrier和CountDownLatch其实非常相似,CyclicBarrier表示加法,CountDownLatch表示减法。
区别还是有的:
CyclicBarrier只能够唤醒一个任务,CountDownLatch可以唤起多个任务。
CyclicBarrier可以重置,重新使用,但是CountDownLatch的值等于0时,就不可重复用了。
1.2.3 Semaphore(信号灯)
流程:
- 创建信号灯
Semaphore semaphore = new Semaphore(6); // 6个位置 - 等待获取信号灯
semaphore.acquire();//等待获取许可证 - 业务代码
- 释放信号
semaphore.release();//释放资源
样例:
//信号灯
public class SemaphoreDemo {
//样例说明:6辆车,停到3个停车位
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);//设置许可数量
//6台汽车
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();;
}
}
}
总结:在获得一个项目之前,每个线程必须从信号量中获得一个许可,以保证一个项目可供使用。 当线程完成该项目时,它会返回到池中,并且将许可返回给信号量,允许另一个线程获取该项目。