目录
01,什么是JUC?
所谓JUC就是java并发工具包:java.util.concurrent
学习JUC主要学习三个包的使用:
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
02,回顾多线程
进程和线程
进程:
- 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)
- 进程不依赖于线程而独立存在,一个进程中可以启动多个线程
- Java默认有几个线程有2 个 ,mian、GC
线程:
- 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。
- 线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
java开启多线程的两种方法
第一种:通过继承Thread类创建线程类
通过继承Thread类来创建并启动多线程的步骤如下:
1、定义一个类继承Thread类,并重写Thread类的run()方法,
run()方法的方法体就是线程要完成的任务,因此把run()称为线程的行体
2、创建该类的实例对象,即创建了线程对象
3、调用线程对象的start()方法来启动线程
注意:也可以使用匿名内部类的方式创建线程
- 一般的实现举例:
//使用这种方式不能达到资源共享的目的
public class Test {
public static void main(String[] args) {
//第二步:
MyThread myThread=new MyThread();
//第三步:
myThread.start();
}
}
//第一步:
class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程执行的内容。。。");
}
}
- 使用匿名内部类的方式实现
public class Test {
public static void main(String[] args) {
//方式一,使用匿名继承Thread
new Thread("Thread_A") {
@Override
public void run() {
System.out.println("Thread_A线程执行的内容。。。");
}
}.start();
//方式二,使用lambda表达式
new Thread(()->{
System.out.println("Thread_B线程执行的内容。。。");
},"Thread_B").start();
}
}
第二种,通过实现Runnable接口创建线程类
这种方式创建并启动多线程的步骤如下:
1、定义一个类实现Runnable接口;
2、创建该类的实例对象obj;
3、将obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;
4、调用线程对象的start()方法启动该线程;
//注意:使用这种方式可以达到资源共享的目的
public class Test {
public static void main(String[] args) {
//第二步:
MyThread myThread=new MyThread();
//第三步:
Thread thread=new Thread(myThread,"Thread_A");
//第四步:
thread.start();
//第三步和第四步的合并写法
new Thread(myThread,"Thread_B").start();
}
}
//第一步:
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("线程执行的内容。。。");
}
}
线程的状态:
- Thread类中有一个
state
方法,在该方法中使用枚举的方式列出了一个线程有多少中状态
public enum State {
NEW, //新建
RUNNABLE, //运行
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //超时等待
TERMINATED; //终止
}
线程等待:wait和sleep的对比
1、来自不同的类:
- wait是Object类中的方法
- sleep是Thread类中的方法
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
但是它的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待
锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
2、关于锁被释放
- wait会释放锁
- sleep不会释放锁
3、使用的范围不同
- wait必须在同步代码块中
- sleep可以在任何地方休眠
03,java.util.concurrent.locks.Lock(Lock锁)
Synchronized
在说Lock锁之前,先说一下Synchronized关键字
- 之后我们在使用多线程的时候,就
不要再把资源放在线程类里
例如下面这种:
public class Test001 {
public static void main(String[] args) {
TestThread testThread=new TestThread();
testThread.start();
}
}
class TestThread extends Thread{
private int ticket=50;//资源
@Override
public void run() {
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+ticket);
}
}
}
- 把资源和线程类分开,使用的时候直接把资源丢进线程中即可
- 为了安全给sale方法解锁,使用
synchronized
关键字
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发:多线程操作同一个资源,把资源直接丢入线程
Ticket ticket=new Ticket();
new Thread(()->{//每个线程都买60次
for (int i=0;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i=0;i<60;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=0;i<60;i++){
ticket.sale();
}
},"C").start();
}
}
//资源类
class Ticket{
//定义票数
private int ticket=50;
//synchronized 本质:排队
public synchronized void sale(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":sale:"+(ticket--)+" ticket,剩余:"+ticket);
}
}
}
Lock锁
-
Lock是
java.util.concurrent.locks
包下的一个接口
,这个包下面一共三个接口
-
Lock接口有三个实现类
ReentrantLock :可重入锁
ReentrantReadWriteLock:可重入读写锁的两个内部类
- ReentrantReadWriteLock.ReadLock:可重入读写锁的读锁
- ReentrantReadWriteLock.WriteLock :可重入读写锁的写锁
- 使用方法:我们使用Lock接口的一个实现类
ReentrantLock
使用Lock锁的三部曲:
1、 Lock lock=new ReentrantLock();
2、 lock.lock(); // 加锁
3、 在finally代码块中使用:lock.unlock();// 解锁
- 示例:
public class SaleTiketDemo02 {
public static void main(String[] args) {
//并发:多线程操作同一个资源,把资源直接丢入线程
Ticket2 ticket=new Ticket2();
new Thread(()->{//每个线程都买60次
for (int i=0;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i=0;i<60;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=0;i<60;i++){
ticket.sale();
}
},"C").start();
}
}
//资源类
class Ticket2{
//定义票数
private int ticket=50;
//从底层源码可以看出,不加参数为非公平锁,加参数True为公平锁
Lock lock=new ReentrantLock();//第一步
public void sale(){
lock.lock();//第二步:
try {//业务代码
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":sale:"+(ticket--)+" ticket,剩余:"+ticket);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- ReentrantLock :可重入锁
我们来看一下这个类的构造方法
//有两个构造方法
//第一个构造方法,不穿参数,默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//第二个构造方法,传参数,true为公平锁,false为非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:有先来后到的说法,要排队
非公平锁:可以插队
Synchronized 和 Lock 区别
- 1、Synchronized 内置的Java关键字, Lock 是一个Java接口
- 2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- 3、Synchronized 会自动释放锁(被修饰的类或者方法执行结束会自动释放),lock 必须要手动释放锁!如果不释放锁,死锁
- 4、Synchronized 线程 1(获得锁之后阻塞)、线程2(等待,傻傻的等),Lock锁就不一定会等待下去
- 5、Synchronized
可重入锁
,不可以中断的,非公平 - 6,Lock
可重入锁
,可以判断锁,非公平还是公平(可以自己设置,参考ReentrantLock的构造方法
) - 7、Synchronized 适合锁
少量
的代码同步问题,Lock 适合锁大量
的同步代码
04,线程之间的通讯(生产者和消费者问题)
- 生产者和消费者的问题,其实就是线程之间的通信问题
- 线程之间的通信问题就是,等待唤醒机制
synchronized版本
- 案例分析:
synchronized版本的生产消费问题:synchronized ,wait,notify
线程交替执行,线程A,B,C,D操作一个变量num(值在0-1之间)
* 线程A,C执行:num+1
* 线程B,D执行:num-1
四个线程:两个生产者线程(A和C),两个消费者线程(B和D)
问题:
当生产者线程A生产完之后,可能会唤醒生产者C,这时候num的值就有可能为2
当消费者线程B消费完之后,可能会唤醒消费者D,这时候num的值就有可能为-1
解决办法:
方法一:如果使用【if】的话,只会判断一次,可能还会出现上面的问题(虚假唤醒)
方法二:如果使用【循环】的话,把等待放在循环中,每次其他线程执行完,唤醒一个线程的时候都会进行判断
结论:
使用if会出现虚假唤醒解决不了上面的问题(上面列出的问题),使用while循环则可以
为何使用if不行的具体原因:
当调用wait()方法的时候,线程会放弃对象锁,线程A调用wait方法释放了对象锁,刚好唤醒了线程C,
此时线程C会执行加1操作,当线程C执行结束刚好又唤醒线程A,由于线程A已经执行完了if语句,
就不会再次判断,接着继续执行,从而出现上面的为2的情况,使用while则需要再次判断,可以避免上面的问题。
- 实例代码:
public class test01 {
public static void main(String[] args) throws InterruptedException{
//并发:多个线程共享一个资源资源
//创建多个线程,把资源直接丢入线程
Data data=new Data();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"A:producer").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B:consumer").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"C:producer").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"D:consumer").start();
}
}
/*等待,业务,通知*/
class Data{//数据类,资源类
private int num=0;
//+1
public synchronized void increment() throws InterruptedException{
while(num!=0){
this.wait();//等待
}
num++;//业务
System.out.println(Thread.currentThread().getName()+"==>"+num);
this.notifyAll();//通知
}
//-1
public synchronized void decrement()throws InterruptedException{
while(num==0){
this.wait();//等待
}
num--;//业务
System.out.println(Thread.currentThread().getName()+"==>"+num);
this.notifyAll();//通知
}
}
- 虚假唤醒是如何产生的
把 while (num != 0) {}
换成 if (num == 0) {}
就会出现虚假唤醒。官方文档有标注;
- 为什么if判断会出现虚假唤醒?
1. 因为if只会判断一次
2. 而while每次都会判断
JUC 版本
- 案例分析:
JUC 版本的生产消费问题:Lock,await,single
Lock:代替synchronized关键字的作用
通过lock.newCondition创建一个条件
condition.await:代替wait
condition.single:代替notify
通过刚才的演示可以发现,这种实现的效果和使用synchronized,wait,notify的效果是一样的
注意:里面也要使用while进行判断
任何一种新的技术绝不会只是对原来技术的一种覆盖,肯定有它的优势,和对原来技术的补充,
public class Test02 {
public static void main(String[] args) throws InterruptedException{
//并发:多个线程共享一个资源资源
//创建多个线程,把资源直接丢入线程
Data2 data=new Data2();
new Thread(()->{
for(int i=0;i<15;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"A:producer").start();
new Thread(()->{
for(int i=0;i<15;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B:consumer").start();
new Thread(()->{
for(int i=0;i<15;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"C:producer").start();
new Thread(()->{
for(int i=0;i<15;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"D:consumer").start();
}
}
/*等待,业务,通知*/
class Data2{ //数据类,资源类
private int num=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
//+1
public void increment() throws InterruptedException{
try {
lock.lock();
while(num!=0) {
condition.await();//等待
}
num++;//业务
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition.signalAll();//通知
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public synchronized void decrement()throws InterruptedException{
try {
lock.lock();
while(num==0){
condition.await();//等待
}
num--;//业务
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition.signalAll();//通知
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
java.util.concurrent.locks.Condition(精准唤醒)
java.util.concurrent.locks
一共有三个接口
Lock
Condition
ReadWriteLock
- 示例:
//Condition:实现精准的通知和唤醒作用
/*A执行完调用B
* B执行完调用C
* C执行完调用A
* */
public class Test03 {
public static void main(String[] args) throws InterruptedException{
//并发:多个线程共享一个资源资源
//创建多个线程,把资源直接丢入线程
Data3 data=new Data3();
new Thread(()->{
for(int i=0;i<12;i++){
try {
data.printA();
} catch (Exception e) {
e.printStackTrace();
}
}},"Thread_A").start();
new Thread(()->{
for(int i=0;i<12;i++){
try {
data.printB();
} catch (Exception e) {
e.printStackTrace();
}
}},"Thread_B").start();
new Thread(()->{
for(int i=0;i<12;i++){
try {
data.printC();
} catch (Exception e) {
e.printStackTrace();
}
}},"Thread_C").start();
}
}
/*等待,业务,通知*/
class Data3{ //数据类,资源类
private int flag=1;//flag为1 A线程执行,2 B 线程执行,3 C 线程执行
Lock lock=new ReentrantLock();
Condition conditionA=lock.newCondition();
Condition conditionB=lock.newCondition();
Condition conditionC=lock.newCondition();
public void printA(){
try {
lock.lock();
while (flag!=1){
conditionA.await();//等待
}
System.out.println(Thread.currentThread().getName()+".....AAAAA....");
flag=2;//唤醒B线程
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
try{
lock.lock();
while (flag!=2){
conditionB.await();//等待
}
System.out.println(Thread.currentThread().getName()+".....BBBBB....");
flag=3;//唤醒B线程
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
try {
lock.lock();
while (flag!=3){
conditionC.await();//等待
}
System.out.println(Thread.currentThread().getName()+".....CCCCC....");
flag=1;//唤醒B线程
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}