📒博客首页:热爱编程的大李子 📒
🌞文章目的:JUC概述 | Lock接口 | 线程间通信 | 多线程锁总结🌞
🙏博主在学习阶段,如若发现问题,请告知,非常感谢🙏
💙同时也非常感谢各位小伙伴们的支持💙
🌈每日一语:我生来就是高山而非溪流 ,我欲于群峰之巅俯视平庸的沟壑。我生来就是人杰而非草芥,我站在伟人之肩藐视卑微的懦夫!🌈
💗感谢: 我只是站在巨人们的肩膀上整理本篇文章,感谢走在前路的大佬们!💗
🌟最后,祝大家每天进步亿点点! 欢迎大家点赞👍?收藏???评论💬支持博主🤞!🌟
1、什么是 JUC
1.1 JUC简介
在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。
1.2 进程与线程
**进程:**进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
进程/线程例子?
-
使用QQ,查看进程一定有一个QQ.exe的进程,我可以用QQ和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,QQ支持录入信息的搜索。 -
大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。 -
word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查
1.3 线程的状态
1.3.1 线程状态枚举类
Thread.State
public enum State {
NEW,(新建)
RUNNABLE,(准备就绪)
BLOCKED,(阻塞)
WAITING,(不见不散)
TIMED_WAITING,(过时不候)
TERMINATED;(终结)
}
1.3.2 wait/sleep的区别
功能都是当前线程暂停
- wait放开手去睡,放开手里的锁
- sleep握紧手去睡,醒了手里还有锁
1.4 并发与并行
1.4.1 串行模式
串行表示所有任务都一 一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。 串行是一次只能取得一个任务,并执行这个任务。
1.4.2 并行模式
并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上则依赖于多核CPU。
一句话:多项工作一起执行,之后再汇总
**例子:**泡方便面,一边电水壶烧水,一边撕调料倒入桶中
1.4.3 并发
并发(concurrent) 指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行。一句话:同一时刻多个线程在访问同一个资源,多个线程对一个点
**例子:**小米9 今天上午10点,限量抢购;春运抢票; 电商秒杀…
2、Lock 接口
2.1 Synchronized
2.1.1 Synchronized 关键字回顾
synchronized 是Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized 后面括号括起来的部分,作用的对象是这个类的所有对象。
2.1.2 售票案例
class Ticket {
private int number = 30;
public synchronized void sale() {
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" : 卖出第:"+(number--)+"张票, 剩下:"+number+"张");
}
}
}
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"CC").start();
}
}
如果一个代码块被synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: **1)**获取锁的线程执行完了该代码块,然后线程释放对锁的占有; **2)**线程执行发生异常,此时JVM 会让线程自动释放锁。 那么如果这个获取锁的线程由于要等待IO 或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock 就可以办到。
2.2 什么是Lock
2.2.1 Lock接口
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比synchronized 更多的功能。
Lock 与Synchronized 的区别
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
2.2.2 lock接口的常见方法
2.2.3 如何使用
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock();
try {
} finally {
lock.unlock()
}
}
}
2.3 使用Lock和Lambda Express改进卖票案例
package com.rg.lock;
import java.util.concurrent.locks.ReentrantLock;
class LTicket {
private int number = 30;
private final ReentrantLock lock = new ReentrantLock(true);
public void sale() {
lock.lock();
try {
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" : 卖出第:"+(number--)+"张票, 剩下:"+number+"张");
}
} finally {
lock.unlock();
}
}
}
public class LSaleTicket {
public static void main(String[] args) {
LTicket ticket = new LTicket();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"AA").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"BB").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"CC").start();
}
}
2.4 、总结创建线程的几种方式
-
继承Thread public class SaleTicket extends Thread 改进: java是单继承,资源宝贵,要用接口方式 -
使用 Thread(Runnable target, String name)
- 新建类实现runnable接口 — 这种方法会新增类,有更新更好的方法
class MyThread implements Runnable
new Thread(new MyThread,...)
- 匿名内部类 — 这种方法不需要创建新的类,可以new接口
new Thread(new Runnable() {
@Override
public void run() {
}
}, "your thread name").start();
new Thread(() -> {}, "your thread name").start();
错误的写法:
Thread t1 = new Thread();
t1.start();
补充:调用start时候,线程是否马上进行创建?
不一定,start()底层使用的是native方法,是操作系统的方法. 具体什么时候创建由操作系统决定.
3、Java8之lambda表达式复习
Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称 为 Lambda 操作符或剪头操作符。它将 Lambda 分为 两个部分:
- 左侧:指定了 Lambda 表达式需要的所有参数
- 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能
代码演示
package com.atguigu.thread;
@FunctionalInterface
interface Foo{
public int add(int x,int y);
default int div(int x,int y) {
return x/y;
}
public static int sub(int x,int y) {
return x-y;
}
}
public class LambdaDemo
{
public static void main(String[] args)
{
Foo foo = (x,y)->{
System.out.println("Hello!! lambda !!");
return x+y;
};
int result = foo.add(3,5);
System.out.println("******result="+result);
System.out.println("******result div="+foo.div(10, 2));
System.out.println("******result sub="+Foo.sub(10, 2));
}
}
4、线程间通信
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
我们来基本一道面试常见的题目来分析
场景: 两个线程,一个线程对当前数值加1,另一个线程对当前数值减1,要求 用线程间通信
4.1 synchronized实现
package com.rg.sync;
class Share {
private int number = 0;
public synchronized void incr() throws InterruptedException {
while(number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+" :: "+number);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
while(number != 1) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+" :: "+number);
this.notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
**结果分析:**当只有AA,BB线程时,一切运行正常, AA::1和BB::0 轮流执行
当增加CC、DD线程时,会出现虚假唤醒的情况,
原因分析:
4.2 Lock 方案
package com.rg.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Share{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() throws InterruptedException {
lock.lock();
try {
while (number != 0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+" :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void decr() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+" :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
4.3 线程间定制化调用通信
三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次.
接着
AA打印5次,BB打印10次,CC打印15次
…来10轮
实现代码
package com.rg.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareResource{
private int flag = 1;
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=1){
c1.await();
}
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
}
flag = 2;
c2.signal();
}finally {
lock.unlock();
}
}
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=2){
c2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
}
flag = 3;
c3.signal();
}finally {
lock.unlock();
}
}
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while (flag!=3){
c3.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
}
flag = 1;
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <= 10 ; i++) {
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10 ; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
5、多线程锁
经典的八锁问题
- 标准访问,先打印短信还是邮件
- 停4秒在短信方法内,先打印短信还是邮件
- 普通的hello方法,是先打短信还是hello
- 现在有两部手机,先打印短信还是邮件
- 两个静态同步方法,1部手机,先打印短信还是邮件
- 两个静态同步方法,2部手机,先打印短信还是邮件
- 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
- 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
参考代码
package com.rg.sync;
import java.util.concurrent.TimeUnit;
class Phone {
public static synchronized void sendEmail() throws Exception{
try {
TimeUnit.SECONDS.sleep(4);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("------sendEmail");
}
public synchronized void sendSMS()throws Exception{
System.out.println("------sendSMS");
}
public void sayHello(){
System.out.println("------sayHello");
}
}
public class Lock_8{
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(()->{
try {
phone2.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
总结
OK,今天关于 JUC的知识分享 就到这里,希望本篇文章能够帮助到大家,同时也希望大家看后能学有所获!!!
好了,我们下期见~
|