线程复习(自用)

进程是操作系统资源分配的最小单元,线程是运算调度的最小单元,更轻量级。线程池预先创建线程,提高效率,减少资源消耗。多线程同步防止数据不一致,常用synchronized关键字。死锁是多个进程因争夺资源形成的僵局。
摘要由CSDN通过智能技术生成

DATE:2023/2/12

AUTHOR:@V4yn3

一.进程

进程是操作系统资源分配的最小单元。一个进程拥有的资源有自己的堆,栈,页表,文件描述符等信息。从编程角度来看,可以把它看作是一个类或者是一个PCB(Process Control Block)进程控制块的结构体。

进程是操作系统对一个正在运行的程序的一种抽象,可以把进程看作程序运行的一次运行过程。

二.线程

线程是操作系统能够进行运算调度的最小单元。它被包含在进程中,是进程中实际运行的单位。一个进程中可以并发多个线程,每个线程执行不同的任务。

虽然多进程也能实现并发编程,但是线程比进程更轻量。

线程的状态有五种:新建(new)、就绪(start())、运行(分配到cpu)、阻塞和死亡

线程的优势

1.创建线程比创建进程更快

2.销毁线程比销毁进程更快

3.调度线程比调度进程更快

在java语言中:线程A和线程B的堆内存方法区内存共享。但是栈内存独立一个线程一个栈

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

对于单核的CPU来说,不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是多个事情同时在做。

多线程实现的三种方法

一.继承Thread

二.实现Runnable接口

三.实现Callable接口

配合Future/FutureTask使用 。

两者的区别

1、最大的区别,runnable没有返回值,而实现callable接口的任务线程能返回执行结果 2、callable接口实现类中的run方法允许异常向上抛出,可以在内部处理,try catch,但是runnable接口实现类中run方法的异常必须在内部处理,不能抛出

三.线程池

概念:

线程池就是首先创建一些线程,他们的集合称之为线程池。线程池在系统启动时会创建大量空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行结束后线程不会销毁死亡,而是再次返回到线程池中成为空闲状态。等待执行下一个任务。

线程池的工作机制:

在线程池的编程模式下,任务是分配给整个线程池的,而不是直接提交给某个线程,线程池拿到任务后,就会在内部寻找是否有空闲的线程,如果有,则将任务交个某个空闲线程。

使用线程池的原因:

多线程运行时,系统不断创建和销毁新的线程,成本非常高,会过度的消耗系统资源,从而可能导致系统资源崩溃,使用线程池就是最好的选择。

线程池的创建:

线程池的创建方式共7种 总的来说可以分为两类

通过 ThreadPoolExecutor 创建的线程池; 通过 Executors 创建的线程池。

线程池的创建⽅式总共包含以下 7 种(其中 6 种是通过 Executors 创建的,1 种是通过ThreadPoolExecutor 创建的):

  1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;

  2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;

  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;

  4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;

  5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;

  6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。

  7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。

    corePoolSize(核心线程数);maximumPoolSize(最大线程数);keepAliveTime(存活时间);Unit(存活时间的时间单位);workQueue(工作队列);threadFactory(线程工厂);handler(拒绝策略);

Java中线程安全的基本数据结构

string;HashTable;ConcurrentHashMap;Vector;stringBuffer

四.死锁

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

解决死锁的基本方法:

以确定的顺序获得锁;超时放弃;顺序上锁,反向解锁,不要回头;

五.线程同步

为何要使用同步?

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

同步的方式

1.同步方法

即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

代码如: 
public synchronized void save(){}

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

2.同步代码块

即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

代码如: 
synchronized(object){ 
​
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。 
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。 

3.使用特殊域变量(volatile)实现线程同步

使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值.简单来说就是每一次都会拿最新的值,保证线程同步。

六.多线程通讯方式

主要涉及三个方法这三个方法都需要配合 synchronized ⼀起使用):

  • wait() / wait(long timeout):让当前线程进入等待状态。

  • notify():唤醒当前对象上⼀个休眠的线程(随机)。

  • notifyAll():唤醒当前对象上的所有线程。

wait() 使用

wait 执行流程:*使当前执行代码的线程进行等待. (把线程放到等待队列中)

释放当前的锁 满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 结束等待的条件:*其他线程调用该对象的 notify 方法.

wait 等待时间超时 (wait 方法提供⼀个带有 timeout 参数的版本, 来指定等待时间). 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

notify 使用

notify 方法是唤醒等待的线程:

方法 notify() 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到") 在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

notiyAll 使用

notify() 方法只是唤醒某⼀个等待线程. 使用 notifyAll() 方法可以⼀次唤醒所有的等待线程。

七.其他

sleep()和wait()的区别

sleep()是Thread类中的静态方法,而wait()是Object类中的成员方法; sleep()可以在任何地方使用,而wait()只能在同步方法或同步代码块中使用 sleep()不会释放锁,而wait()会释放锁,并需要通过notify()/notifyAll()重新获取锁。**

synchronized与Lock的区别

synchronized是Java关键字,在JVM层面实现加锁和解锁;Lock是一个接口,在代码层面实现加锁和解锁 synchronized可以用在代码块上、方法上;Lock只能写在代码里。 synchronized在代码执行完或出现异常时自动释放锁;Lock不会自动释放锁,需要在finally中显示释放锁。 synchronized会导致线程拿不到锁一直等待;Lock可以设置获取锁失败的超时时间。 synchronized无法得知是否获取锁成功;Lock则可以通过tryLock得知加锁是否成功。 synchronized锁可重入、不可中断、非公平;Lock锁可重入、可中断、可公平/不公平,并可以细分读写锁以提高效率

乐观锁和悲观锁的区别

1、悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

2、乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,期间该数据可以随便被其他人读取,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值