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 { /** * Thread state for a thread which has not yet started. */ NEW,(新建) /** * Thread state for a runnable thread. A thread in the runnable */ RUNNABLE,(准备就绪) /** * Thread state for a thread blocked waiting for a monitor lock. */ BLOCKED,(阻塞) /** * Thread state for a waiting thread. */ WAITING,(不见不散) /** * Thread state for a waiting thread with a specified waiting time. * </ul> */ TIMED_WAITING,(过时不候) /** * Thread state for a terminated thread. * The thread has completed execution. */ 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 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接口的常见方法
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待
关键字synchronized 与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的newContition()方法返回Condition 对象,Condition 类也可以实现等待/通知模式。
Condition 比较常用的两个方法:
await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
signal()用于唤醒一个等待的线程。
2.2.3 如何使用
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } 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//新建类实现runnable接口 new Thread(new MyThread,...)
- 匿名内部类 — 这种方法不需要创建新的类,可以new接口
new Thread(new Runnable() { @Override public void run() { } }, "your thread name").start();
- lambda表达式 — 这种方法代码更简洁精炼
new Thread(() -> {}, "your thread name").start();
错误的写法:
Thread t1 = new Thread(); t1.start();
补充:调用start时候,线程是否马上进行创建?
不一定,start()底层使用的是native方法,是操作系统的方法.
具体什么时候创建由操作系统决定.