您现在的位置是:主页 > news > 日本真人做的视频网站/知名seo公司

日本真人做的视频网站/知名seo公司

admin2025/6/7 8:59:31news

简介日本真人做的视频网站,知名seo公司,可以做用户画像的网站,个人网站名字取名怎么做一 线程实现 线程是比进程更轻量级的调度执行单位,引入线程可以把一个进程资源分配和调度执行分开,各个线程既可以共享进程资源(内存地址、文件i/o等),又可以独立调度。 线程有三种实现方式:使用内核线程实…

日本真人做的视频网站,知名seo公司,可以做用户画像的网站,个人网站名字取名怎么做一 线程实现 线程是比进程更轻量级的调度执行单位,引入线程可以把一个进程资源分配和调度执行分开,各个线程既可以共享进程资源(内存地址、文件i/o等),又可以独立调度。 线程有三种实现方式:使用内核线程实…

 

 线程实现

线程是比进程更轻量级的调度执行单位,引入线程可以把一个进程资源分配和调度执行分开,各个线程既可以共享进程资源(内存地址、文件i/o等),又可以独立调度。

线程有三种实现方式:使用内核线程实现、使用用户线程实现、使用用户线程加轻量级进程混合实现。

 

1  使用内核线程(Kernel-Level Thread,KLT)实现

1)   概念

由操作系统内核完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核被称为多线程内核(Multi-Thread Kernel)。

程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口,即轻量级进程(Light Weight Process, LWP)。轻量级进程就是我们通常意义上的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。

2)   线程模型

f06b8934f7ef39d67c645eaa7eee3930b622c832

LWP和KLT是1:1的关系。

3)   优点

每个轻量级进程都有一个独立的调度丹玉,既是一个轻量级进程在系统调用中被阻塞了,也不会影响整个进程继续工作。

4)    缺点

由于是基于内核线程实现的,所以各种线程操作,如创建、析构以及同步,都需要进行系统调用。系统调用的代价相对较高,需要在用户态和内核态中来回切换,性能损失相对较大。

每个轻量级进程都需要一个内核线程支持,而内核线程数量有效并且需要占用一定的内核资源(如内核现成的栈空间)

 

2  使用用户线程(User Thread,UT)实现

1)   概念

广义上将,一个线程只要不是内核线程,就可以认为是用户线程,按照这个标准将LWP是用户线程,不过它始终是建立在KLT的基础上,许多操作都需要系统调用,效率也会受到影响。

狭义上的用户线程指完全建立在用户空间的线程,系统不能感知线程存在;用户线程的建立、同步、销毁、调度完全在用户态中完成,不需要内核的帮助。

2)   线程模型

847e509917075318d439778c682aa6cabc327bb5

3)   优点

如果实现得当,这种线程不需要切换到内核态,因此操作可以非常快速且性能损耗低,可以支持规模更大的线程数量。例如:有些高性能的数据库中多线程就是使用UT方式实现的。

4)   缺点

因为没有系统内核的支持,线程的创建、同步、销毁、调度都是需要考虑的问题;由于操作系统只将处理器资源分配到进程,那诸如阻塞如何处理、多处理器系统中如何将用户线程映射到其他的处理器上等问题解决起来会非常困难,甚至不可能完成,这也导致了使用UT的程序实现起来都比较复杂。

使用UT的程序越来越少,Java曾经使用过UT,不过后来放弃了。

 

3  使用用户线程加轻量级进程混合实现

1)   概念与优缺点

既存在用户线程,也存在轻量级进程;用户线程还是完全建立在用户空间中,因此线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发;而操作的轻量级进程(LWP)作为用户线程和内核线程之间的桥梁,这样可以使用内核线程提供的线程调度功能以及处理器映射,并且用户线程的系统调用通过LWP完成,大大降低了整个进程被完全阻塞的风险。

2)   线程模型

37cbd1adf916f4bc25f09dfb5e43f81448387c03

UT和LWP的关系是M:N

 

4  JAVA线程的实现

JDK1.2之前,是基于被称为“绿色线程(Green Threads)”的用户线程实现的;JDK1.2及其之后版本中,是基于操作系统原生线程模型来实现的。

在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了java虚拟机的线程模型是怎样的,这一点在不同的平台上无法达成一致。线程模型只对线程的并发规模和操作成本产生影响,对java编码和程序运行来说是透明的。

 

 

 线程调度

线程调度分为协同式线程调度(Cooperative Threads Scheduling)和抢占式线程调度(Preemptive Threads Scheduling)。

1  协同式线程调度

线程的执行时间由线程本身来控制,线程把自己的工作执行完成了以后,要主动通知系统切换到另一个线程上。

优点:实现简单,线程切换是现成自己克制的,所以没有什么线程同步问题。

缺点:线程执行时间不可控,如果一个线程编写的有问题,一直不通知操作系统切换,那么程序将会一直阻塞到哪里。

 

2  抢占式线程调度

线程由系统分配执行时间,线程的切换不由线程本身决定。Java中Thread.yield()可以让出cpu执行时间,但无法主动获取cpu执行时间。

优点:线程的执行时间由系统控制,不会因为一个线程阻塞而导致整个进程阻塞。

Java使用的是抢占式线程调度方式。

 

 

 线程状态的转换

1  状态转换模型

38142d54b49fdcfe88c680463bbd1017c2954267

2  主要方法

notify() 唤醒在此对象监视器上等待的单个线程。

notifyAll() 唤醒在此对象监视器上等待的所有线程。

wait() 让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒后进入Runnable状态。

wait(long timeout) 让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量,当前线程被唤醒后进入Runnable状态。

wait(long timeout, int nanos) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

注意wait()方法会让当前线程释放它所持有的锁。

 

 

 

 java的线程安全

1  线程安全程度

按照线程安全的“安全程度”由强到弱来排序,java中的共享数据可以分为:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。

1)   不可变

java中使用final修饰的数据是不可变的。除此之外,String,枚举,以及Number的部分子类也是不可变的。

因为不可变,所以这类型的数据时线程安全的。

2)   绝对线程安全

一个类如果要达到绝对线程安全,即不管运行时环境如何,调用者都不需要任何额外的同步措施,通常来说需要付出很大的代价,甚至是不切实际的。

需要说明的是,就算一个类的所有方法都是用synchronized修饰,也并不能说此类是觉得线程安全的。例如:Vector是线程安全的,add()、get()、remove()方法都加了synchronized,但如果同时向Vector中add、get、remove,同样可能出现异常。

在java中常见的线程安全类,均不属于次范畴,

 

3)   相对线程安全

对这个对象的单独操作是线程安全的,我们在调用的时候不需要做额外的保障措施。但是对一些特定顺序的连续操作,可能需要在调用端使用同步手段来保障正确性。例如上面讲的Vector的示例。

这是我们通常意义上的线程安全,java中说的线程安全基本上都属于此范畴。

 

4)   线程兼容

线程兼容指对象本身并不是线程安全的,但是可以通过在调用端正确的使用同步手段来保证对象在并发环境中可以安全的使用。Java中说的非线程安全的类均属于次范畴。

 

5)   线程对立

线程对立指如论是否采取了同步手段,在多线程并发环境中保证正确性。

Thread的suspend()和resume()就属于这种,如果suspend()中断的线程就是要执行resume()的那个线程,那么肯定会死锁。这两个方法在目前的JDK中已经被标记为@Deprecated。

 

2  线程安全实现方法

1)   互斥同步(Mutual Exclusion & Synchronization)

同步是指多个线程并发访问共享数据时,保证共享数据在同一时刻只能被一个线程使用。互斥是实现同步的一种手段,常见的手段有:临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)。这也就是我们常说的阻塞同步(Blocking Synchronization)。

互斥同步是一种悲观的并发策略,总是认为如果不去做正确的同步措施,就可能出问题,无论共享的数据是否真的会出现出现竞争,都要进行加锁操作(这里这段描述是概念模型,不过JVM会进行一些优化,优化掉一些不必要的加锁操作)。这种方式和后面说的非阻塞同步有着本质的却别。

A    synchronized

Java中最常见的互斥同步手段是使用synchronized关键字。在上面“java线程的实现”部分提到过,java线程映射到操作系统的原生线程上,如果要阻塞、唤醒一个线程,都需要操作系统来帮忙完成,这就需要从用户态切换到内核态,这种状态转换耗时可能比用户代码执行时间还要长,所以synchronized是一个重量级的操作。

a)     特性与功能

  • synchronized是java的关键字,由jvm支持。
  • 支持重入,即一个线程在获取对象的锁以后可以再次对此对象上锁。
  • 修饰普通方法,锁是当前实例对象;修饰静态方法,锁是当前类的class对象;包裹代码块,锁是括号中的对象。
  • 不支持等待中断。
  • synchronized是非公平的。

 

b)     原理

关于synchronized关键字对字节码指令的影响以及深层原理探究在另外一篇博客中有详细描述,见:《互斥同步-锁》地址:https://yq.aliyun.com/articles/414939

 

B    ReentrantLock

在java.util.concurrent包中引入了并发实现,例如ReentrantLock、ReentrantReadWriteLock等。其中ReentrantLock和synchronized很相似,下面简要的讨论一下此类。

ReentrantLock是一种非常常见的临界区处理手段,通过在执行代码前上锁保证同一时间只有一个线程能执行指定的代码块。

a)     ReentrantLock的特性与功能如下:

  • ReentrantLock是java api层面的实现,有Unsafe支持。
  • 支持重入。
  • 支持公平锁、非公平锁,默认是非公平锁。公平锁指:多个线程在等待同一个线程的锁时,必须按照申请所得时间顺序来获取锁。非公平锁指:在锁被释放时,任何一个等待锁的线程都有机会获取锁。
  • 支持等待中断。例如:A线程获取对象O的锁, B线程等待获取O的锁,当B长时间无法获取锁时,B可以放弃获取锁。
  • 锁可以绑定多个条件。线程进入临界区,却发现在某一条件满足之后才能执行,条件对象就是用来管理那些已经获得了锁,但是却不能做有用工作的线程。一个ReentrantLock对象可以同时绑定多个Condition对象。

 

 

b)    主要方法

lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁。

tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

tryLock(long timeout,TimeUnit unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

 

c)    示例

公平、非公平

源码如下,所以使用时可以通过参数决定是使用公平锁还是非公平锁。

    

    public ReentrantLock() {sync = new NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

 

 

简单示例

以下是一个简单的锁示例

    

    private ReentrantLock lock = new ReentrantLock();public void simpleCase() {//如果能够获取锁,那么直接往后执行,否则等待直到获取锁lock.lock();try {// TODO do the job} finally {lock.unlock();}}

 

等待中断

以下示例,如果等待10s还获取不到锁,将放弃获取锁。

    private ReentrantLock lock = new ReentrantLock();public void lockOrWait() throws InterruptedException{/*** 如果未获取到锁,则当前线程会被blocked,直到以下任何一件事情发生:* 当前线程获得锁;* 线程被其他线程interrupt;* 等待的时间到,即等待时间超过10s*/boolean locked = lock.tryLock(10L, TimeUnit.SECONDS) ;if( !locked ){return ;}try {//TODO do the job} finally {lock.unlock();}}

    

 

绑定多条件

conditionWait方法在获取锁以后,发现需要的资源尚未准备好,通过condition.await()方法释放其持有的锁并挂起当前线程。

另外一个线程执行conditionSignal方法,通过condition.signal()唤醒之前被挂起的线程,原有线程被唤醒,继续执行conditionWait方法。

private ReentrantLock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void conditionWait() throws InterruptedException {// 如果能够获取锁,那么直接往后执行,否则等待直到获取锁lock.lock();try {//资源是否已经都准备好了?boolean resourceReady = false;if( !resourceReady ){//await会让当前线程释放其持有的锁并挂起condition.await();}// TODO 执行业务逻辑} finally {lock.unlock();}}public void conditionSignal() throws InterruptedException {// 如果能够获取锁,那么直接往后执行,否则等待直到获取锁lock.lock();try {//资源是否已经都准备好了?boolean resourceReady = true;if( resourceReady ){condition.signal();}} finally {lock.unlock();}}

    

 

 

C    Semaphore

Semaphore是一种基于计数的信号量,可以设置一个阈值,线程来了以后申请获取许可信号,业务逻辑处理完毕以后归还;当Semaphore释放信号超过阈值后,如果一个线程过来申请许可信号,将会被阻塞。

a)     示例

    private Semaphore semp = new Semaphore(1);public void semaphore() throws InterruptedException {// 申请许可semp.acquire();try {// TODO 业务逻辑} finally {// 释放许可semp.release();}}

 

2)   非阻塞同步(Non-Blocking Synchronization)

这是一种基于冲突检测的乐观并发策略。即:先进性操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用情况,产生了冲突,那么就采用其他的补偿措施(最常见的就是重试直到成功)。这种策略许多实现都不需要将线程挂起,因此被称之为非阻塞同步。

注意:这种策略需要硬件指令集的支持。需要硬件保证操作和冲突检测两个操作是能作为一个原子性的操作完成。

A    常见的指令

  • 测试并设置(Test-and-Set)
  • 获取并增加(Fetch-and-Increment)
  • 交换(Swap)
  • 比较并交换(Compare-and-Swap,简称CAS)
  • 加载链接/条件存储(Load-Linked/Store-Conditional,简称LL/SC)

 

B    CAS

因为CAS使用的较多,所以接下来就重点讲述一下此指令。

CAS指令有3个操作数,分别是内存位置(java中指的是变量的内存地址,V表示),旧的预期值(用OV表示),新值(用NV表示)。CAS执行时,当且精当V符合OV时,处理器用NV更新OV,否则不执行更新操作。如论是否更新了V,都返回V的旧值。这整个操作是原子性的。

Java在JDK1.5以后支持CAS操作,由sun.misc.Unsafe提供支持,例如compareAndSwapInt()、compareAndSwapLong()等方法。虚拟机内部对这些方法做了特殊处理,即时编译出来的结果就是一条鱼平台相关的CAS指令,没有方法的调用过程,可以认为是无条件内联进去了。

关于内联可以参考博客:《虚拟机优化》中“几种常见的优化技术”部分内容,地址:https://yq.aliyun.com/articles/377214

a)     获取Unsafe实例

Unsafe并不是提供给用户程序调用的类,Unsafe.getUnsafe()的代码限制了只有启动类加载器(Bootstrap ClassLoader)加载的类才能访问它。所以我们要么使用java的api间接使用它,要么使用反射获取Unsafe的实例,以下是一个示例。

注意:以下代码在编译器中很可能会报错,网上有很多解决办法,我这里就不累述了。

    public static Unsafe getInstance() {Field instance = Unsafe.class.getDeclaredField("theUnsafe");instance.setAccessible(true);return (Unsafe) instance.get(Unsafe.class);} 

 

 

b)    源码分析

以下代码来自AtomicInteger。我们可以看到原子的inc操作是通过CAS和失败重试来实现的。

    public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}}public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}


 

c)     ABA问题

如果一个变量V初始值是A,当他准备修改的时候检测到他还是A,那么此时我们能说变量V没有被其他线程修改么?其实不能,因为存在这种可能:在检测变量V前,V先被修改成B然后又被修改成A,而此时CAS会认为变量V没有被修改。

大部分情况下ABA不影响我们逻辑的执行,所以一般不会出问题。

 

3)   无同步方案

同步是在多线程争用共享数据时,保证共享数据正确性的一种手段,如果多线程之间本身就不涉及共享数据,那么他们无需任何同步就是线程安全的。常用的手段有:可重入代码、线程本地存储。

A    可重入代码(Reentrant Code)

可重入代码的一些公共特征,例如:不依赖存储在堆上的数据、公共的系统资源、公共的数据(如DB、缓存中的数据);用到的状态量都是由参数传入;不调用非可重入的方法等。如果一个方法,他的返回结果是可以预测的,只要输入了相同的数据,就能返回相同的结果,那么他就是满足可重入性要求的,当然也是线程安全的。

 

B    线程本地存储(Thread Local Storage)

如果一段代码中所需要的数据必须与其他代码共享,那就要这些共享数据的代码是否能够保证在同一个线程中执行,如果能保证,那么可以通过把共享数据放在同一个线程中。

Java中可以通过ThreadLocal类来实现。每个线程Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以ThreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V对,ThreadLocal对象时当前线程的ThreadLocalMap的访问入口,每个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值可以在线程K-V值中找到对应的本地线程变量。

 

 

五 博客

虚拟机优化

https://yq.aliyun.com/articles/377214


互斥同步-锁

https://yq.aliyun.com/articles/414939