《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(一)

简介: 《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全

1、什么是 JUC


1.1 JUC简介


在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。


f13db41b3ceb9e508016f16e13a65c11.png


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 更多的功能。


c146a25b30f99b1a23e0907f0d917d0d.png


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方法,是操作系统的方法.

具体什么时候创建由操作系统决定.



相关文章
|
16天前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
30 6
|
2月前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
41 1
[Java]线程生命周期与线程通信
|
29天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
37 3
|
28天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
23 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
20 2
|
2月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
34 2
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
22 1
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
39 1