多线程、线程并发安全

多线程、线程并发安全、锁

进程和线程

进程是操作系统内执行的计算机程序实例是操作系统分配资源的基本单位,

线程是进程内部按单一顺序执行的控制流,可以理解为进程的子任务,线程是cpu的调度的基本单位

进程和线程的关系

  • 一个进程包含一个或多个线程,至少有一个
  • 进程拥有的私有空间地址,仅能被他的线程访问
  • 一个线程一定属于某个进程
  • 线程只能访问进程所拥有的资源

单线程:所有的任务按照一个接一个的顺序执行,如果该任务发生阻塞,整个程序会被阻塞,

多线程:允许同时执行多个任务,每个任务有一个单独的线程负责完成

Java的多线程

每一个Java程序实际上就是一个JVM进程,JVM进程用一个线程俩执行main方法,称为主线程,在主线程中启动多个线程的称为子线程,JVM还负责垃圾回收等其他工作的子线程

Thread类

Thread是Java中代表线程的类,

相关方法

  • long getId():返回该线程的标识符
  • String getName();返回线程的名称
  • int getPriority():返回线程的优先级
  • Thread.state.getState():获取线程的状态
  • boolean isAlive():测试线程是否处于活动状态
  • boolean isDaemon():测试线程是否为守护线程
  • boolean isInterrupted():测试线程是否中段

创建 线程构造方法

  • public Thread():分配一个新的线程对象
  • public Thread(String name):分配一个新的线程对象,指定名字
  • public Thread(Runnable):分配一个到指定目标的新的线程对象
  • public static Thread currentThread():返回当前正在执行的线程对象引用
  • public String getName();返回当前线程的名称
  • public void start():启动线程,调用该线程run的方法
Runnable

Runnable 是接口,在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。

1.定义实现Runnable接口
2.覆盖Runnable接口中的run方法,将线程要运行的代码存放在run方法中。
3.通过Thread类建立线程对象。
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5.调用Thread类的start方法开启线程并调用Runnable接口子类run方法。

run方法和start方法的区别

在Java中, Thread 类中的 run() 方法和 start() 方法是创建和启动线程的两个重要方法,它们的区别如下:

run() 方法

  • run() 方法是 Thread 类中的一个普通方法,用于定义线程的执行逻辑。
  • 在使用继承 Thread 类创建线程时,需要重写 run() 方法,将线程要执行的代码逻辑写在 run() 方法中。
  • 当调用 Thread 对象的 start() 方法时,会启动新的线程,并在新的线程中调用 run() 方法执行线程的逻辑。
  • 在单线程环境中,直接调用 run() 方法并不会启动一个新线程,而是在当前线程中执行 run() 方法的逻辑。

start() 方法

  • start() 方法是 Thread 类中的一个特殊方法,用于启动一个新的线程。
  • 当调用 Thread 对象的 start() 方法时,会创建一个新的线程,并在新的线程中调用 run() 方法执行线程的逻辑。
  • 在多线程环境中,start() 方法会启动新线程并与当前线程并行执行,而不会阻塞当前线程。

线程的状态

线程的五种状态
  • 新建状态(new):一个对象被创建出来时,该线程处于新建状态
  • 就绪状态(Runnable):当调用一个线程的对象的start()方法时,该对象处于就绪状态
  • 运行状态(Running):当CPU执行一个线程的Run方法时;该对象是运行状态
  • 阻塞状态(Blocked):当一个运行的线程因各种原因暂停或是停止运动时,该对象处于阻塞状态
  • 终止状态(TERMINATED):当一个线程完成任务或是其他原因终止时,该对象处于终止状态

线程调度会将CPU运行时间分为若干个时间片并分配给每个线程,拿到时间片的线程会被CPU执行,此时该线程处于运行状态,当时间片用完之后,该线程重新返回到就绪状态,一个线程在其的生命周期里,可能多次在运行状态和就绪状态之间转换

线程的状态操作
  • sleeep():控制某个线程休眠一段时间
  • join():控制某个线程等待另一个线程执行完成之后再执行
  • yield():控制一个线程从运行状态转换为就绪状态
  • interrupt():向线程发烧一个中断通知
后台进程

=进程包括用户进程和后台进程

后台进程是指程序运行时在后台提供的一种通用服务进程,也就是为其他的线程提供服务的线程,成为守护线程,后台线程并不属于程序中不可或缺的一部分,所以,当用户线程结束时,会终止进程中的所有后台进程,

通过setDaemon(true)方法来设置为后台线程,必须在start之前设定,

进程并发安全

概述

一个进程下的多个线程共享该进程的被分配到的内存空间,意味着一个进程下的数据可以被多个线程同时访问,

  • 常见的共享数据:对象的属性和类的静态变量
  • 非共享数据:方法中声明的局部变量,方法的形参
线程互斥

临界区

临界区是一个受保护的区,限制多个线程不能同时进入这个区域,当有一个线程进入临界区时,其他的线程必须等待该线程离开临界区 之后,再进入临界区,

线程互斥指不同线程通过竞争进入临界区,将异步操作变为同步操作,

synchronized关键字

用于程序中设置临界区,已实现线程的互斥,称为“同步锁”,

synchronized (锁对象){
    //需要同步访问的代码
}

当一个线程因为synchronized关键字进入阻塞状态时,称为同步阻塞状态

synchronized方法

与同步代码块不同,同步方法将子线程要访问的代码放到一个方法中,默认的锁为this,既当当前对象,

访问修饰符 synchronized  放回类型 方法名(){
    
}

可以用来修饰静态方法,既静态同步方法,此时锁定的是类的对象,每个类都有唯一的一个类对象,通过类名.class访问,

静态同步方法

访问修饰符 synchronized  static 放回类型 方法名(){
    
}

只有锁对象相同时,才能实现线程互斥

synchronized的实现原理

synchronized 是通过对象的锁(也称为监视器monitor)来实现的,

  1. 当一个线程要进入synchronized代码块中,会先申请持有目标对象的锁
  2. 如果该线程申请成功,则进入synchronized代码块中并执行其中的内容
  3. 此时如果其他线程想要进入synchronized代码块,会因无法持有目标对象的锁激怒额阻塞状态
  4. 当一个线程执行synchronized代码块的内容时,会退出synchronized代码块并释放持有对象的锁
  5. 之前申请该对象的锁的所有线程会争该锁,得到锁的线程结束阻塞进入synchronized代码块,其他线程继续保持阻塞状态,
死锁

死锁是指多个线程因竞争资源而造成的一种僵局(相互等待)

死锁的原因

  1. 互斥条件:指线程对已经获取的资源进行排他性使用,
  2. 不可被剥夺的条件:是指线程获取到的资源自己使用玩之前不是被其他线程抢占、
  3. 请求并持有条件:一个线程至少拥有一个资源,又提出新的资源请求,为新资源已被其他线程占有吗,所以当前线程发生阻塞,并且不释放自己的资源。
  4. 环路等待:存在环形链,

API线程安全

StringBuffer的关键方法都是用了synchronized关键字进行同步控制,确保多线程同时访问同一个StringBuilder对象时不会出现数据不一致活并发问题
String、StringBuffer和StringBuilder的比较
(1)StringBuilder和StringBuffer非常类似,均可代表可变的字符序列,而且方法也一样
(2)String:不可变字符序列,效率低,但复用率高
(3)StringBuffer:可变字符序列,效率较高,线程安全
(4)StringBuilder:可变字符序列,效率最高,线程不安全
String、StringBuffer和StringBuilder的选择
1、如果字符串存在大量的修改操作,一般使用StringBuffer或StringBuilder
2、如果字符串存在大量的修改操作,并在单线程的情况下,使用StringBuilder
3、如果字符串存在大量的修改操作,并在多线程的情况下,使用StringBuffer
4、如果字符串很少修改,被多个对象引用,使用String

volatile关键字

可以用来修饰字段(成员变量),既规定线程对该变量的访问均需要从内存中获取,对该变量的修改也必须同步刷新到共享内存中,保证资源的可见性,
volatile与synchronized的区别
两者的使用可以避免线程的安全问题

  • 原理不同:volatile保证共享数据的可见性,synchronized通过设置临界区实现线程的 互斥,
  • 用法不同:volatile用于修饰 成员变量,synchronized用于修饰代码块和方法
  • 功能不同:volatile仅能实现变量修改的可见性,不能保证原子性,synchronized既能保证修改的可见性,也能保证原子性,
多线程协作

线程同步
wait():导致当前线程进入阻塞状态,并释放持有的锁
notify():随机唤醒在此锁上等待的 线程,
notifyAll():唤醒所有在此锁等待 线程

并发工具包

JUC==>java.util.concurrent,
五个模块:atomic(原子操作),locks(锁和条件变量),collections(阻塞队列和线程安全集合),executor(线程池),tool(线程同步工具)

CountDownLatch
用于线程键的等待和协调,它基于一个计数器,可以让一个或多个线程等待其他线程完成之后再继续执行,
核心概念是一个初始计数值,既在创建CountDownLatch对象时指定计数值,每个线程完成自己的任务时,调用CountDownLatch的countDown()方法,将计数器减1,而等待线程可以调用wait()方法类阻塞等待,直到计数器变为0,

CyclicBarrier
CyclicBarrier(循环屏障)用于线程间的同步,使一组线程能够等待彼此到达共同的屏障点在执行,
CyclicBarrier核心概念是一个屏障点和一个计数点,创建CyclicBarrier对象时需要指定计数器的初始值和到达屏障带你要执行的任务,每个线程到达屏障点前调用await()方法进行等待,当有线程调用await()方法时,计数器减1,当计数器为0,所有的线程被唤醒,可以继续执行后续操作,计数器也会重置为初始值,
Semaphore
Semaphore(信号量),用于控制对资源的访问权限
核心概念:许可证和计数器

  • 创建Semaphore对象,指定许可证的数量,
  • 访问资源之前,调用acquire()方法,获取许可证,如果许可证大于0,线程继续执行,许可证数量减1,
  • 如果许可证数量为零,线程会被阻塞,知道其他的线程释放许可证,
  • 线程使用完资源之后 ,需要调用release()方法释放许可证,许可证数量加1 ,

线程池

线程池中维护多个线程,等待监督程序分配任务并并发执行

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性能

ExecutorService
ExecutorService是一个java的API,可以简化异步模式下的运行任务,ExecutorService会自动提供一个线程池和一个用于其分配任务的API,
ExecutorService接口继承Executor 接口,在Executor的基础上扩展了对Executor进行控制的方法,如:提供submit()方法用于Runnable 任务,invokeAll()方法用于批量提交任务,shutdown()用于停止启动新的任务;

ExecutorService的接口的 实现类包括ThreadPoolExecutor 和 ScheduledThreadPoolexecutor

  • Executors.newSingleThreadEcecutor():创建一个使用单个工作线程在无界队列上运行的Executor,
  • Executors.newScheduledThreadPoolexecutor(int corePoolSize):创建一个线程池,配置给定的延迟后运行,或则定期运行
  • Eecutors.newFixedThreadPool(int nThreads):创建一个线程池,该线程池复用在共享无界队列上运行的固定数量的线程
  • void execute(Runnable command):在将来的 某个时间执行给定的任务
获取并发任务的结果

先前学习了两种创建线程的方法,直接继承Thread 类和实现 Runnable接口,这两种存在缺陷,在这行任务完成之后无法直接获取任务的执行结果,想要获取任务的结果,必须通过共享变量或则使用线程通信的方式来达到效果,
Callable接口
在并发任务执行完之后可以便捷的获取任务的执行结果
使用Callable接口被称为java中第三种创建线程的方式Callable接口与Runnable接口相似,代替线程的工作单元,该接口只有一个call()方法,含义与run()方法相似,与run()方法不同的是call()方法可以返回指定泛型类对象并且可以抛出Exception类及其子类的异常,

Future接口
Future表示异步计算的结果

  • cancel()用来取消任务,取消成功则返回true,反之,返回false
  • isCancelled():表示任务是否取消成功,如果任务正常完成前被取消成功,则返回true,
  • isDone():表示任务是否已经完成,若任务完成,返回true
  • get():用来获取执行的结果,但是会产生阻塞,一直等到任务完成才返回结果
  • get(long timeout,TimeUnit unit):用来获取执行的结果,在指定的时间内没能获取到结果,接返回null,

FutureTask
FutureTask是Future接口的基础实现
FutureTask常用来封装Callable接口和Runnable接口,也可以用来作为一个任务提交到线程池中运行,

锁是一种同步语:一种在有许多的池执行时强制限制对资源的访问的机制,旨在强制执行互斥并发策略,

请添加图片描述

synchronized的分类

  1. 多个线程竞争synchronized锁的时候,不会按照先来后到的顺序,所以是非公平锁
  2. synchronized 会锁住同步资源,所以属于悲观锁
  3. 一个线程可以重复的获取synchronized的锁,所以synchronized是克重入锁
  4. 多个线程不能同时持有synchronized的锁,属于排他锁,
Lock接口及实现

Lock接口提供了与synchronized关键字类似的功能,只是在使用时显示的获取和释放锁,可中断的获取锁和超时获取锁,

Lock lock=new Lock;
lock.lock;//获取锁
lock.unlock;//释放锁

Lock接口提供的并且synchronized关键字所不具备的主要特性如下:

1,尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
2, 能被中断地获取锁:获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
,3,超时获取锁:在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回

Lock接口的常用方法

  • lock():获取锁,调用该方法的当前线程会尝试获取锁,获得锁后,从该方法返回
  • lockInterruptibly():可中断地获取锁,该方法会响应中断,即在锁的获取中可以中断当前线程
  • tryLock():尝试非阻塞的获取锁,调用该方法后立刻返回,如果能否获取则返回true,否则返回false
  • tryLock(long time, TimeUnit unit):超时的获取锁,超时时间结束,返回false
  • unlock():释放锁

ReentrantLock
ReentrantLock,顾名思义,就是支持冲进入的锁,它表示该锁能够支持一个线程对资源的重复加锁

读写锁
但是目前讲到的锁都是排他锁:即同一时间仅能有一个线程获取锁。在一写多读场景中,读线程之前也会阻塞,同一时间仅能有一个线程读取票房数据,这大大降低了读的效率:

ReadWriteLock
java中使用ReadWriteLock接口作为读写锁的父接口,该接口定义了两个方法:

  • Lock writeLock():返回该读写锁对中的写锁对象
  • Lock readLock():返回该读写锁对中的读锁对象

ReentrantReadWriteLock作为ReadWriteLock接口的实现类

  • getReadLockCount():返回当前读锁被获取的次数,该次数不等于获取读锁的线程数
  • getReadHoldCount():返回当前线程获取读锁的次数
  • isWriteLocked():判断写锁是否被获取

getWriteHoldCount():返回当前写锁被获取的次数

网络基础

通信协议

在计算机网络中实现通信必须有一定的规则和约定,将这些规则和约定称为通信协议。通信协议中对计算机的传输速率、传输代码、传输控制步骤和出错标准等做了统一规定。为了让网络中的计算机进行通信,通信双方必须遵守通信协议,才能完成信息交换。不同的应用需要遵守对应的通信协议。

TCP/IP 协议和 UDP 协议
TCP/IP协议是一个协议族,包括IP协议、TCP协议和IMCP协议。

IP协议是TCP/IP协议的核心。IP协议为上层提供统一的IP数据报,提供无连接的、不可靠的、尽力而为的数据报投递服务。

TCP是传输层的一种可靠的、面向连接的传输协议。每个TCP连接只能是点对点的,只能连接两个端点,可以提供全双工通信。由于通信双方必须事先建立连接,其特点是效率低,但数据传输比较安全。

UDP是一种无连接的传输层协议,提供的是非面向连接的、不可靠的数据流传输。“无连接”就是在通信前不必与对方先建立连接,不管对方状态就直接发送。其特点是传输效率高,但数据传输不安全,容易丢包。

IP地址和域名
P地址(IP Address)为互联网上的每个网络和每台主机分配一个逻辑地址,用于标识其网络身份。每个IP地址在整个网络中都是唯一的。IP地址分为IPv4和IPv6两个版本。

IPV4版本的IP地址使用32位数字构成,由4个8位的二进制数组成,每8位之间用圆点隔开,通常用点分十进位表示,如125.123.23.46。

IPv6版本的IP地址使用128位数字表示一个地址,采用十六进制数表示。

有时还会用到一个特殊的IP地址——127.0.0.1。将127.0.0.1称为回送地址,主要用于网络软件测试及本地主机进程间通信,使用回送地址发送数据时,不进行任何网络传输,只在本地主机进程间通信。

域名用于识别 Internet 资源,例如计算机、网络和服务,其基于文本的标签比 Internet 协议中使用的IP地址更容易记住。例如,百度的IP地址为220.181.38.150,百度的域名是www.baidu.com,后者更容易被使用者记住。

** 端口号**

一个IP地址标识一台计算机,每台计算机又有很多网络通信程序在运行,提供网络服务或进行通信,这就需要不同的端口进行通信。如果把IP地址比作电话号码,那么端口就是分机号码,进行网络通信时不仅要指定IP 地址,还要指定端口号。
TCP/IP系统中的端口号是一个16位的数字,它的范围是0~65535,小于1024的端口号保留给预定义的服务,如HTTP是80,FTP是21, Telnet是23,Email是25,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值