当前位置: 首页 > news >正文

网页设计师常逛网站做网站最便宜多少钱

网页设计师常逛网站,做网站最便宜多少钱,网络服务提供者知道或者应当知道网络用户利用,广告联盟上怎么做网站并发基础 进程与线程 进程 进程#xff08;Process#xff09;是计算机中正在运行的程序。程序是一种静态的概念#xff0c;而进程是程序在执行过程中创建的动态实体。每个进程都有自己的内存空间、代码、数据和资源#xff0c;它也是操作系统进行任务调度和资源分配的基…并发基础 进程与线程 进程 进程Process是计算机中正在运行的程序。程序是一种静态的概念而进程是程序在执行过程中创建的动态实体。每个进程都有自己的内存空间、代码、数据和资源它也是操作系统进行任务调度和资源分配的基本单位。 也可以这样理解进程是正在运行一个软件或者脚本。 多个进程可以同时运行在计算机上彼此独立并且互不干扰。操作系统通过进程管理来控制和监视进程的创建、运行、暂停和终止等操作。进程还可以通过进程间通信机制来实现进程之间的数据交换和同步。 打开任务管理器运行的程序就是一个个进程。 线程  线程Thread是程序执行的最小单位它是进程的一部分。一个进程可以包含一个或多个线程。同一个进程共享同一块内存空间和其他资源它们可以同时执行不同的任务。每个线程都有自己的程序计数器、栈和一组寄存器这使得线程能够独立地执行代码。 线程的特点 轻量级相对于进程来说线程的创建和上下文切换开销较小。共享资源同一个进程中的线程可以共享内存和其他资源因此可以更方便地进行数据共享和通信。并发执行多个线程可以同时执行提高了程序的并发性和效率。 线程的使用可以提升程序的性能和响应性特别是在多核处理器上可以实现并行计算。但多线程编程也需要注意线程间的同步和共享数据的安全性问题以避免出现条件和数据不一致的情况。 线程的时间效率和空间效率都比进程要高。 并行与并发 并行 并行Parallel是指同时进行多个任务任务之间可以同时执行彼此独立。例如在多核处理器上可以同时执行多个线程或进程。 并发 并发Concurrent是指同时交替进行多个任务任务之间可能有依赖关系或竞争条件。例如在单核处理器上通过时间片轮转的方式多个线程或进程通过快速切换执行看起来是同时进行的但实际上是交替执行的并发中的任务可能需要依赖共享资源或竞争临界区资源。 并行是多个任务同时进行而并发是多个任务交替进行并且可能存在资源竞争和依赖。 并行和并发之间的区别在于任务是否可以同时执行以及是否需要竞争共享资源。并行通常需要硬件支持如多核处理器能同时执行多个任务。而并发则可以在单核处理器上通过时间片轮转等计数实现。 在实际应用中可以用并行提高计算性能和执行速度可适用于多线程编程或分布式计算而并发则可以提高资源利用率和系统吞吐量可适用于任务调度和资源管理。 多线程的必要性 CPU、内存主存、I/O读写这三者的处理速度有着极大的差异。为了平衡这三者的速度差异需要在计算机体系结构、操作系统、编译程序上进行优化。 CPU缓存优化 CPU(中央处理器是计算机的核心软件负责处理大部分的计算任务。然而CPU处理数据的速度远高于内存读取或写入数据的速度这种速度上的差异就会导致CPU在等待数据时无事可做从而浪费其处理能力。为了解决这个问题专门在CPU和内存之间增加了缓存Cache作为一个数据的临时存储区用于存放CPU预期会用到的数据。因为缓存是位于CPU和内存之间的临时存储区它的存取速度比内存要快得多所以能够有效地解决CPU和内存速度上的差异。当CPU需要读取或写入数据时它会首先查找缓存中是否有这些数据。如果有这称为”缓存命中“CPU就可以之间从缓存读取或写入数据从而避免了等待内存的消耗时间。如果没有这称为”缓存未命中“CPU就需要从内存中读取数据并同时将这些数据写入缓存以供后续使用。通过这种方式缓存能够有效地利用CPU的高速处理能力提高计算机的整体性能。 CPU增加缓存会导致可见性问题。 操作系统优化  操作系统增加了进程和线程的概念来实现分时复用CPU资源进而均衡CPU和I/O设备的速度差异。 进程是指计算机中正在运行的程序的实例。操作系统通过为每个进程分配一段独立的内存空间和一组资源如CPU时间片、文件描述符等来管理并控制进程的运行。通过轮流分配CPU时间片操作系统可以让多个进程交替运行从而实现了CPU的分时复用。线程是指进程中的一个独立执行单元。一个进程可以有多个线程它们共享该进程的资源和状态。每个线程都有自己的栈空间和程序计数器但它们共享同一进程的内存空间、文件描述符等。操作系统可以通过调度算法在不同的线程之间切换从而实现多个线程在单个进程中并发执行。通过引入进程和线程的概念操作系统可以将CPU时间片分配给不同的进程实现进程之间的轮流执行并提高CPU的利用率。同时操作系统可以通过线程的并发执行来隐藏I/O设备操作的等待时间提高系统的响应速度。操作系统通过增加进程和线程的概念实现了分时复用CPU资源使得多个进程和线程可以并发执行从而在CPU和I/O设备的速度差异中实现了均衡。 操作系统增加了进程、线程会导致原子性问题。  编译程序优化  编译程序优化指令执行次序使得缓存能够得到更加合理地利用。 编译程序优化指令执行次序是指在编译过程中编译器根据各种优化策略和规则自动调整和重排程序中指令的顺序以提高程序地运行效率和性能。编译器进行指令优化的主要目的是为了解决一下问题硬件资源利用通过优化调整指令次序可以更好地利用CPU的并行性提高CPU利用率减少资源浪费。例如通过指令调度让CPU同时执行多条无关的指令从而提高指令的并行等级。减少等待时间某些指令执行需要等待前面指令的结果这回导致CPU空闲等待。通过优化指令顺序可以尽可能地避免这种依赖减少CPU空闲时间提高运行效率。克服性能瓶颈例如优化内存访问指令地顺序可以减少缓冲区溢出或下溢地可能性避免因为内存访问导致的性能瓶颈。管线浪费现代CPU通常将指令执行过程分解成多个阶段并行执行以提高性能这就是所谓地管线技术。如果指令地执行顺序不能很好地匹配CPU管线就会导致管线阶段地闲置造成性能下降。 通过这些优化编译器可以帮助程序员在不需要手动干预地情况下自动提高程序地运行效率和性能使得程序在各种硬件平台上都能获得更好的运行效果。 编译程序优化指令执行次序会导致有序性问题。 线程安全问题 如果多个线程对同一个共享数据进行访问而不采取同步操作的话那么操作的结果是不一致的。 以下代码演示了1000个线程同时对cnt执行自增操作操作结束之后它的值有可能小于1000。 public class ThreadUnsafeExample {private int cnt 0;public void add() {cnt;}public int get() {return cnt;} }public static void main(String[] args) throws InterruptedException {final int threadSize 1000;ThreadUnsafeExample example new ThreadUnsafeExample();final CountDownLatch countDownLatch new CountDownLatch(threadSize);ExecutorService executorService Executors.newCachedThreadPool();for (int i 0; i threadSize; i) {executorService.execute(() - {example.add();countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println(example.get()); }// 输出结果992 为什么多线程操作会导致结果小于1000是什么导致了线程安全问题 导致多线程安全问题的主要原因有三个方面CPU缓存导致的可见性问题、进程或线程导致的原子性问题、编译器优化导致的有序性问题。 线程安全导致原因 多线程操作共享变量时才会引起线程安全问题。 可见性  可见性一个线程对共享变量的修改另外一个线程能够立刻看到。 CPU缓存会导致可见性规则打破举例 //线程1执行的代码 int i 0; i 10;//线程2执行的代码 j i; 假如执行线程1的是CPU1执行线程2的是CPU2.。当线程1执行 i10 这句时会先把i的初始值加载到CPU1的高速缓存中然后赋值为10那么在CPU1的高速缓存当中i的值变为10了却没有立即写入到主存当中。此时线程2执行 ji, 它会先去主存读取i的值并加载到CPU2的缓存当中注意此时内存当中的i值还是0那么就会使得j的值为0而不是10。 线程1对变量i修改了之后线程2没有立即看到线程1修改的值所以CPU缓存会导致可见性问题。 原子性 原子性即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断要么就都不执行。 分时复用会引起原子性打破举例 int i 1;// 线程1执行 i 1;// 线程2执行 i 1; 这里需要注意的是 i1 需要三条CPU指令 将变量i从内存中读取到CPU寄存器在CPU寄存器中执行 i1 操作将最后的结果i写入内存缓存机制导致可能写入的是CPU缓存而不是内存。 由于CPU分时复用线程切换的存在线程1执行了第一条指令后就切换到线程2执行假如线程2执行了这三条指令后再切换回线程1执行后续两条指令将造成最后写到内存中的i值是2而不是3。所以CPU分时复用会导致原子性问题。 有序性 有序性即程序执行的顺序按照代码的先后顺序执行。 重排序优化会打破程序的有序性举例 int i 0; boolean flag false; i 1; //语句1 flag true; //语句2 虽然上述代码语句2在语句1之后但是多线程下执行顺序不一定语句1、语句2这个顺序执行。重排序是指计算机系统如处理器、编译器在执行程序时按照某种规则重新调整指令或操作的顺序以提高程序性能或满足其他需求。 重排序可以分为三种类型编译器重排序、处理器重排序和内存系统重排序。 编译器重排序 在编译阶段编译器可能会对源代码中的指令重新排序、以优化代码的执行效率。编译器重排序不会改变程序的语义即保证最终的执行结果与源代码的顺序一致。编译器重排序可以通过指令级并行、循环展开、常量传播等技术来实现。处理器重排序在处理器执行指令时由于处理器采用了流水线技术它可以对指令进行重排序、以尽可能地利用处理器资源。处理器重排序可能包括指令级重排序乱序执行和内存访问重排序。指令级重排序是指处理器可以改变指令地执行顺序以提高指令地并行度和执行效率。内存访问重排序是指处理器可以改变对内存地读写操作的顺序以充分利用内存系统的各级缓存。内存系统重排序由于现代计算机系统中存在多级缓存、总线和内存等层次结构由此对于内存的读写操作也可能存在重排序。内存系统重排序可以通过缓存一致性协议和写缓冲区等技术实现。 重排序在一定程度上可以提高程序的执行速度和效率但必须在确保程序正确性和语义一致性的前提下进行。在并发编程中 重排序可能会引发数据竞争、原子性问题等多线程并发问题因此需要采取同步和内存屏幕等手段进行控制和保护。 编译程序优化的重排序下程序的有序性会打破。所以编译器优化和处理器重排序可能会导致有序性问题。 Java重排序流程 1属于编译器重排序2和3属于处理器重排序。这些重排序都可能会导致多线程程序出现内存可见性问题。 对于编译器JMM的编译器重排序规则会禁止特定类型的编译器重排序不是所有的编译器重排序都要禁止。 对于处理器排序JMM的处理器重排序规则会要求java编译器在生成指令序列时插入特定类型的内存屏障memory barriers, intel 称之为 fence指令通过内存屏障指令来禁止特定类型的处理器重排序不是所有的处理器重排序都要禁止。 Java解决并发问题 Java需要解决多线程并发安全问题就需要解决可见性、有序性、原子性这三个问题。 JMMjava内存模型规范了 JVM如何按需禁用缓存和编译优化的方法。 volatile、synchronized 和 final 关键字 volatile volatile是Java提供的一种轻量级的同步机制: 保证共享变量的可见性当一个线程修改了volatile变量的值新值对于其他线程来说是可以立即得知的。也就是说volatile变量在各个线程中是一致的这就是所谓的可见性。 禁止指令重排序有些场合下为了提高性能编译器和处理器可能会对输入代码进行优化它们会把存在数据依赖关系的操作重新进行排序。声明为volatile的变量编译器和处理器就不会对其进行重排序。 不提供原子性虽然volatile变量能够保证可见性和有序性但没办法保证复合操作的原子性。例如num这样的操作其实包含了多个子操作包括读取原有的值、进行加1操作、将新值写回到内存中。这三个子操作并不是原子性的也并不会因为volatile声明而变成原子操作。因此若需要保证原子性通常需要结合synchronized或者Atomic变量来使用。 synchronized  synchronized关键字在Java中被用来作为一种同步锁 保证线程安全synchronized可以修饰方法或者以同步代码块的形式来修饰代码段能够保证在同一时刻最多只有一个线程执行该代码从而保证了类实例的成员变量的线程安全。 保证可见性和有序性synchronized可以保证被其修饰的变量的修改能够及时地被其他线程看到从而避免了被其他线程看到从而避免出现数据不一致地情况。此外其还能够保证线程的执行是有序的防止出现指令重排的情况。 锁的释放与获取包括以下三种情况会释放锁。一是当前线程执行完同步代码就会释放掉锁。二是如果线程执行同步代码块的过程中出现了异常且异常被捕获也会导致锁的释放。三是当前线程在执行同步代码块的过程中执行了锁所属的对象的wait()方法这也会导致线程释放掉锁。 final final保证不可修改不变的内容不会引起多线程的安全问题 final修饰变量final修饰的变量表示常量它的值不能被修改。一旦赋值后就不能再改变。常量一般使用大写字母表示并使用下划线分割单词。 final修饰方法final修饰的方法不能被子类重写。这种方法在继承关系中起到了保护作用可以确保父类的方法行为不被子类修改。 final修饰类final修饰的类不能被继承即不能有子类继承该类。这样的类通常是不希望被修改和扩展的最终版本。 final修饰参数final修饰方法的参数表示该参数在方法内部不可修改。这可以用来保护方法内部的参数不被意外改变。 可见性、有序性、原子性的理解 可见性 Java提供了volatile关键字来保证可见性。 volatile关键字可以保证共享变量的可见性。当一个共享变量被volatile修饰式它的值的修改会立即被更新到主存中当其他线程需要读取该共享变量时它会去主存中获取最新的值。相比之下普通的共享变量是不能保证可见性因为其修改的值可能会延迟写入主存当其他线程需要读取时可能得到的仍然是旧值从而无法保证可见性。 通过synchronized和Lock保证可见性。 通过synchronized和Lock也能够保证可见性sychronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码并且在释放锁之前将会对变量的修改刷新到主存当中。因此可以保证可见性。 有序性 在Java中使用volatile关键字可以确保一定的有序性。此外也可以使用synchronized和Lock来确保有序性。显然synchronized和Lock保证同步代码每次只有一个线程执行这相当于让线程按顺序执行同步代码自然而然地保证了有序性。当然Java内存模型JMM通过Happens-Before规则来保证有序性。  原子性 在Java中对基本数据类型的读取和赋值操作是原子性操作即这些操作是不可被中断的要么执行要么不执行。 x 10; //语句1: 直接将数值10赋值给x也就是说线程执行这个语句的会直接将数值10写入到工作内存中 y x; //语句2: 包含2个操作它先要去读取x的值再将x的值写入工作内存虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作但是合起来就不是原子性操作了。 x; //语句3 x包括3个操作读取x的值进行加1操作写入新的值。 x x 1; //语句4 同语句3 也就是说只有简单的读取、赋值而且必须是将数字赋值给某个变量变量之间的相互赋值不是原子操作才是原子操作。 从上面的例子中可知Java内存模型只保证了基本读取和赋值是原子性操作如果要实现更大范围操作的原子性可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任意时刻只有一个线程执行该代码块那么自然就不存在原子性问题了从而保证了原子性。 Happens-Before规则 程序顺序规则Program Order Rule在同一个线程中按照程序的顺序前一个操作的结果对于后续操作是可见的。换句话说线程中的操作按照代码顺序执行。管程锁定规则Monitor Lock Rule一个unlock操作对于后续的lock操作是可见的。之前已经释放的锁之后的加锁操作可以感知到。volatile变量规则Volatile Variable Rule对一个volatile变量的写操作对于后续该变量的读操作是可见的。volatile关键字会禁止指令重排保证了写操作的可见性。线程启动规则Thread Start Rule一个线程的start操作对于其他线程中的后续操作是可见的。换句话说其他线程可以看到线程启动之后的操作。中断规则Thread Interrupt Rule一个线程中断的发生调用interrupt方法对于该线程的后续操作是可见的。线程终结规则Thread Termination Rule主线程的所有操作对于所有已经假如到该线程的子线程的join操作是可见的。换句话说主线程的操作对于子线程的join操作是可见的。线程中断规则Thread Interruption Rule对于在线程A中调用线程B的interrupt方法如果线程B捕获到该中断则线程A的所有操作对于线程B捕获中断之后的操作是可见的。对象终结规则Finalizer Rule一个对象的构造函数完成对该对象的初始化后对于finalilze方法的调用是可见的。 线程安全调度 线程安全可以从强到弱分为以下几个级别 不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。 不可变 不可变对象是线程安全的因为它们的状态在创建后不可更改。多个线程可以同时访问和使用不可变对象而无需任何同步控制。 final关键字修饰的基本数据类型。String枚举类型。Number部分子类如Long和Double等数值包装类型BigInteger和BigDecimal等大数据类型。但同为Number的原子类AtomicInteger和AtomicLong则是可变的。 绝对线程安全  绝对线程安全意味着对象的所有方法都是线程安全的可以多线程并发地访问和修改对下给你而不需要额外地同步控制。这通常是通过使用同步机制如synchroized关键字或使用Lock接口或线程安全的数据结构来实现的。 相对线程安全 相对线程安全需要保证对这个对象单独的操作是线程安全的在调用的时候不需要做额外的保障措施。但是对于一些特定顺序的连续调用就可能需要在调用端使用额外的同步手段来保证调用的正确性。在Java中大部分的线程安全类都属于这种类型例如Vector、HashTable、Collections的synchronizedCollection() 方法包装的集合等。 线程兼容 线程兼容意味着对象在单线程环境中是安全的但在多线程环境中可能会有问题。在多线程环境中访问和修改该对象时可能需要使用同步控制来确保多个线程之间的访问和修改顺序。这种级别通常需要开发人员来注意使用同步机制。Java API中大部分的类都是属于线程兼容的如与前面的Vector 和 HashTable相对应的集合类ArrayList和HashMap等。 线程对立 线程对立是指无论调用端是否采取了同步措施都无法在多线程环境中并发使用的代码。由于Java语言天生就具备多线程特性线程对立这种排斥多线程的代码是很少出现的而且通常都是有害的应当尽量避免。 线程安全实现 互斥同步 互斥同步是一种保证多个线程在访问共享资源时的互斥性的机制以防止竞态条件和数据不一致问题。 核心 加锁synchronized 和 ReentrantLock。 互斥同步的理解 互斥Mutual exclusion互斥是指同一时刻只允许一个线程访问共享资源其他线程必须等待。互斥机制可以保证在任何时刻只能有一个线程对共享资源进行操作。临界区Critical section临界区是指一段代码其中访问共享资源的部分。在进入临界区前线程需要获得互斥锁执行完临界区代码后释放互斥锁也就是说只有获得互斥锁的线程才能进入临界区。互斥锁Mutex互斥锁是一种同步机制用于保护临界区的访问。在进入临界区之前线程必须获取互斥锁如果互斥锁已经被其他线程持有则请求线程会被阻塞直到互斥锁被释放为止。一旦线程获得了互斥锁其他线程将无法获得该锁知道它被释放。条件变量Condition variable条件变量是一种同步进制用于在共享资源的状态发生变化时线程的等待和唤醒。条件变量通常与互斥锁一起使用。当某个线程发现共享资源的状态不满足其要求时它会进入等待状态同时释放互斥锁允许其他线程继续执行。当其他线程更改了共享资源的状态并满足该线程的要求时它会被唤醒并重新获取互斥锁。 互斥同步的主要问题就是线程阻塞和唤醒所带来的性能问题因此这种同步也称为阻塞同步。 非阻塞同步  阻塞同步采用的是悲观策略认为一个线程在修改时一定会有其他线程进行访问修改导致数据不一致。 非阻塞同步采用的是乐观策略认为一个线程在修改时不会有其他线程进行访问修改那就修改成功了如果有其他线程修改那就采取补偿措施不断地重试直到成功为止。一个线程在进行某段特定代码临界区操作时其他线程可以进行其他代码的操作不需要进行等待。 CAS CASCompare And Swap适用于解决多线程环境下的并发问题的一个方案非阻塞同步方案保证原子性。 通过比较一个内存位置的值如果相等则将新值写入该内存位置否则不做任何操作。 CAS操作包括三个操作数内存位置内存地址V、预期值A和新值B。具体操作步骤如下 将内存位置的当前值即预期值V与预期值A进行比较。如果相等则将新值B写入内存位置操作成功。如果不相等则表示内存位置的值已经被其他线程修改操作失败。根据需要可以重试或者执行其他处理逻辑。  CAS的特点和优势包括 原子性CAS操作是原子操作保证了操作的完整性。在操作中其他线程不能修改内存位置的值因此可以确保数据的一致性。无锁相比于使用锁进行同步CAS是一种无锁的方式。它避免了线程阻塞和上下文切换带来的开销在高并发的情况下性能较好。忙等待由于CAS是基于自旋的方式进行操作当操作失败时线程会忙等待直到操作成功。这可能会造成一定的CPU开销。无阻塞由于CAS不涉及线程阻塞因此不存在死锁问题。 CAS主要应用于一些需要高并发和原子性操作的场景比如非阻塞算法、无所对列和乐观锁等。在Java中java.util.concurrent.atomic 包提供了一些原子类如Atomicinteger和AtomicLong它们底层使用了CAS来实现线程安全的操作。 ABA 如果一个变量初次读取的时候是A值它的值被改成了B后来又被改回为A那CAS操作就会误认为它从来没有改变过。 在使用CAS时可能存在ABA问题也就是说即使内存位置的值已经变化但其实际含义对当前线程来说是没有变化的。为了解决ABA问题可以使用版本号或引用的方式进行解决。比如AtomicStampedReference和AtomicMarkableReference类。 无同步方案 要保证线程安全并不是一定就要进行同步。如果一个方法本来就不涉及共享数据那它自然就无需任何同步措施去保证正确性。 栈封闭  多个线程访问同一个方法的局部变量时不会出现线程安全问题因为局部变量存储在虚拟机栈中属于线程私有的。 public class StackClosedExample {public void add100() {int cnt 0;for (int i 0; i 100; i) {cnt;}System.out.println(cnt);} }public static void main(String[] args) {StackClosedExample example new StackClosedExample();ExecutorService executorService Executors.newCachedThreadPool();executorService.execute(() - example.add100());executorService.execute(() - example.add100());executorService.shutdown(); }/*** 输出结果* 100* 100*/ 线程本地存储 如果能保证共享变量每次使用时都在同一个线程中执行那么就没有线程安全问题可言。 可重入代码 可重入代码reentrant code是指可以由多个任务并发使用且不会引发数据错误的代码。换言之一个可重入的程序、函数或例程在执行过程中被中断然后在中断返回前再次调用它都将产生可预期的结果。 可重入性是一个重要的概念尤其是在多线程或多任务的并发编程环境中它确保了代码的执行不会被其他线程或任务的干扰。 要编写可重入代码就必须避免使用全局变量、静态变量或其他非局部的状态也需要避免调用非重入的函数并确保对互斥对象的访问如锁是正确的。 线程基础 线程状态 线程的状态包含 新建New、可运行Runnable、阻塞Blocking、无限期等待Waiting、限期等待Timed Waiting、死亡Terminated。 新建New 当新创建一个线程对象时线程即进入新建状态。此时该线程还没有开始运行没有分配到CPU时间片。 可运行Runnable 当调用线程的start() 方法后线程即进入运行状态。此时该线程已经分配到了CPU时间片并且开始执行。 阻塞Blocking 线程阻塞是指线程在执行过程中暂停或等待某个条件满足之后才能继续执行的状态。等待获取一个排他锁如果其线程释放了锁就会结束此状态。 线程阻塞可能发生在以下几种情况 IO阻塞线程在执行IO操作如读写文件、网络通信等时如果IO操作未完成或数据未就绪线程会被阻塞暂停执行直到IO操作完成或数据就绪。等待阻塞线程调用了wait()方法进入等待状态直到其他线程通过notify()或notifyAll()方法唤醒它。睡眠阻塞线程调用了sleep()方法使自身进入了睡眠状态暂停执行指定的时间。锁阻塞线程在执行同步代码块或同步方法时如果获取不到同步锁即锁已被其他线程持有线程会被阻塞直到获得锁。运行阻塞处于运行状态的线程可以被调度器暂停执行转而执行其他线程。 在线程阻塞的状态下线程会暂停执行不会占用CPU资源。一旦解除阻塞条件线程会从阻塞状态恢复重新进入可运行状态等待CPU调度执行。 无限期等待Waiting 等待其他线程显式地唤醒否则不会被分配CPU时间片。 进入方式退出方式没有设置Timeout 参数的Object.wait()方法Object.notify() / Object.notifyAll()没有设置Timeout参数的Thread.join()方法被调用的线程执行完毕LockSupport.part()方法LockSupport.unpart()方法 限期等待Timed Waiting 无需等待其他线程显示地唤醒在一定时间之后会被系统自动唤醒。 调用Thread.sleep() 方法使线程进入限期等待状态时常常用使一个线程睡眠进行描述。调用Object.wait()方法使线程进入限期等待或者无限期等待时常常用挂起一个线程进行描述。睡眠和挂起是用来描述行为而阻塞和等待用来描述状态。 进入方式退出方式Thread.sleep()方法时间结束设置了Timeout参数的Object.wait()方法时间结束/Object.notify()/Object.notifyAll()设置了Timeout参数的Thread.join()方法时间结束/被调用的线程执行结束LockSupport.parkNanos()方法LockSupport.parkUntil()方法 阻塞和等待的区别 阻塞状态Blocked线程阻塞是在某些特定情况下发生比如线程在执行同步代码块或方法时尝试获取一个已经被其他线程持有的锁或者在执行IO操作时等待数据完成。线程阻塞是由外部因素如锁、IO操作等所引起的线程会暂停执行直到满足阻塞条件才会继续执行。 等待状态Waiting线程等待是通过调用对象的wait()方法来实现的线程进入等待状态时它会主动释放所持有的对象锁进入等待对列中直到其他线程调用notify()或notifyAll()方法唤醒等待的线程。线程等待是与其他线程之间的协作关系等待关系的线程需要通过其他线程的通知来唤醒。 阻塞状态是由外部因素被动引起的线程会暂停执行并在满足某个条件后继续执行。而等待状态是线程主动释放对象锁并进入等待状态直到其他线程通知其继续执行。  死亡Terminated)  线程执行完了其任务或者出现了异常即进入死亡状态。 线程创建使用 线程的创建有三种方式实现Runnable接口、实现Callable接口、继承Thread类。其实还有一种线程池创建线程。 实现Runnable 和 Callable接口的类只能当做一个可以在线程中运行的任务不是真正意义上的线程因此最后还需要通过Thread来调用。可以说任务是通过线程驱动从而执行的。也就是实现Runnable 和 Callable 接口的类创建的对象这个对象最终还是作为参数给到Thread 来驱动执行。 实现Runnable接口 实现Runnable接口并重写run()方法然后将其创建为Thread对象调用Thread对象的start()方法来启动。 public class MyRunnable implements Runnable {public void run() {// ...} }public static void main(String[] args) {MyRunnable instance new MyRunnable();Thread thread new Thread(instance);thread.start(); } 实现Callable接口 与Runnable相似但Callable可以有返回值返回值通过FutureTask进行封装。 public class MyCallable implements CallableInteger {public Integer call() {return 123;} }public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable mc new MyCallable();FutureTaskInteger ft new FutureTask(mc);Thread thread new Thread(ft);thread.start();System.out.println(ft.get()); } 继承Thread类 创建MyThread类继承Thread并重写run()方法然后将其创建为Thread对象调用Thread对象的start()方法来启动。 public class MyThread extends Thread {public void run() {// ...} }public static void main(String[] args) {MyThread mt new MyThread();mt.start(); } 当调用start()方法启动一个线程时虚拟机会将该线程放入就绪对列中等待被调度当一个线程被调度时会执行该线程的run()方法。 实现接口、继承Thread类对比 大多数情况下实现Runnable接口会更好一些。 java不支持多重继承如果一个类已经继承了其他类就不能再继承Thread类了只能通过实现接口来创建线程。类可能只要求可执行就行继承整个Thread类开销过大。实现接口的方式更符合面向对象的设计思想。 线程机制 基础的线程机制需要理解Executor、Daemon、sleep()、yield()。  Executor 从线程创建使用我们知道了怎么创建和启动一个线程但是实际的开发过程中如果用到了多线程线程不可能只有一个如果有很多个线程一个线程一个线程的创建启动那样会非常难以管理。于是引入了Executor线程执行器。 Executor框架是Java中用于执行任务的高级框架线程池框架通过使用线程池和任务调度策略简化了线程的使用和管理提供了更灵活、可拓展的任务执行方式。Executor管理多个异步任务的执行而无需程序员显式地管理线程地声明周期。这里的异步是指多个任务的执行互补干扰不需要进行同步操作。 Executor继承关系 Executors类是Java中Executor框架的工具类它提供了一些静态方法来创建不同类型的Executor实例。 Executors中几种常用的方法创建Executor 方法说明newFixedThreadPool(int nThreads)创建一个固定大小的线程池,该线程池的线程数量固定为指定的n Threads个。任务会被顺序执行多于的任务会在任务对列中等待。返回一个ThreadPoolExecutor实例。newCachedThreadPool()创建一个可缓存的线程池线程池的大小会根据需要进行调整没有活动的线程会被回收需要执行任务时会自动创建新的线程。返回一个ThreadPoolExecutor实例。newSingleThreadExecutor()创建一个单线程的线程池该线程池中只有一个线程所有任务按照提交的顺序依次执行。返回一个ThreadPoolExecutor实例。newFixedThreadPool(int nThreads, ThreadFactory threadFactory)创建一个固定大小的线程池并可以设置自定义的线程工厂。线程池中的线程数量固定为指定的n Threads个。任务会被顺序执行多余的任务会在任务对列中等待。返回一个ThreadPoolExecutor实例。newScheduledThreadPool(int corePoolSize)创建一个定时执行任务的线程池线程池的大小固定为指定的corePoolSize个。该线程池可按照设定的时间间隔或特定的执行时间来执行任务。返回一个ScheduledThreadPoolExecutor实例。 public static void main(String[] args) {ExecutorService executorService Executors.newCachedThreadPool();for (int i 0; i 5; i) {executorService.execute(new MyRunnable());}executorService.shutdown(); } Daemon 守护线程Daemon Thread是一种特殊的线程它的特性是进程结束时所有的守护线程也会随之结束。 Java中几种常见的线程分类 用户线程User Threads)用户线程是应用程序中最常见的线程类型由应用程序创建和控制。它们执行应用程序的业务逻辑和任务当所有的用户线程执行完毕后JVM就会退出。后台线程Daemon Threads又称为守护线程后台线程是一种特殊的线程它在程序运行期间在后台提供服务。后台线程的声明周期不会影响程序的退出当所有用户线程结束时JVM会自动终止所有未结束的后台线程。后台线程通常用于执行一些不需要阻止应用程序退出的任务如垃圾回收GC。主线程Main Thread主线程是应用程序启动时第一个被创建的线程它执行的是main()方法。主线程通常负责初始化应用程序的环境、加载类和启动其他线程。一旦主线程执行完毕程序可能继续执行其他用户线程或退出。 守护线程不会阻止JVMJava虚拟机或者其他主程序退出 在JVM中当所有的非守护线程也就是用户线程都退出时那么JVM就认为程序已经结束了因此会结束运行。这时JVM不会关心是否还有守护线程在运行无论守护线程是否完成任务它们都会被强制结束。因此“守护线程不会阻止JVM或者主程序退出”。 将用户线程设置为守护线程 public static void main(String[] args) {Thread thread new Thread(new MyRunnable());thread.setDaemon(true); } sleep() Thread.sleepmillisec方法会休眠当前正在执行的线程millisec单位为毫秒。 sleep() 可能会抛出InterruptedException,因为异常不能跨线程传播回main()中因此必须在本地进行处理。线程中抛出的其他异常也同样需要在本地进行处理。 public void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();} } yield() 对静态方法Thread.yield()的调用声明了当前线程已经完成了声明周期中最重要的部分可以切换给其他线程来执行。该方法只是对线程调度器的一个建议而且也只是建议具有相同优先级的其他线程可以运行。 public void run() {Thread.yield(); } 线程中断 Thread中提供了中断相关方法 方法说明void interrupt()中断线程即设置线程的中断状态为true。boolean isInterrupt()判断线程是否被中断如果线程被中断返回true否则返回false。static boolean interrupted()判断当前线程是否被中断如果线程被中断返回true并清除中断状态否则返回false。 当想中断某个线程时可以调用那个线程的interrrupt()方法此时那个线程的中断标志位会被设置为true。被中断的线程可以通过isInterrupted()来检查自身是否被中断。如果需要清除中断标志位可以使用interrupted()方法它会返回当前的中断状态并立即清除该状态设为false。通过调用一个线程的interrupt()来中断该线程如果该线程处于阻塞、限期等待或者无限期等待状态那么就会抛出InterruptedException从而提前结束该线程。但是不能中断I/O阻塞和synchronized锁阻塞。 InterruptedException案例 在main()中启用一个线程之后再中断它由于线程中调用了Thread.sleep()方法因此会抛出一个InterruptedException,从而提前结束线程不执行之后的语句。 public class InterruptExample {private static class MyThread1 extends Thread {Overridepublic void run() {try {Thread.sleep(2000);System.out.println(Thread run);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {Thread thread1 new MyThread1();thread1.start();thread1.interrupt();System.out.println(Main run);} }// 异常结果java.lang.InterruptedException: sleep interrupted interrupt案例 public class InterruptExample {private static class MyThread2 extends Thread {Overridepublic void run() {while (!interrupted()) {// ..}System.out.println(Thread end);}}public static void main(String[] args) throws InterruptedException {Thread thread2 new MyThread2();thread2.start();thread2.interrupt();} }// 结果Thread end Executor的中断 调用Executor的shutdown()方法会等待线程都执行完毕之后再关闭。但是如果调用的是shutdownNow()方法则相当于调用每个线程的interrupt()方法。 public static void main(String[] args) {ExecutorService executorService Executors.newCachedThreadPool();executorService.execute(() - {try {Thread.sleep(2000);System.out.println(Thread run);} catch (InterruptedException e) {e.printStackTrace();}});executorService.shutdownNow();System.out.println(Main run); } 如果只想中断Executor中的一个线程可以通过使用submit()方法来提交一个线程它会返回一个Future?对象通过调用该对象的cancel(true)方法就可以中断线程 Future? future executorService.submit(() - {// .. }); future.cancel(true); 互斥同步 Java提供了两种锁机制来控制多个线程对共享资源的互斥访问。第一个JVM实现的sychronized,而另一个是JDK实现的ReentrantLock。 synchronized synchronized同步范围和位置有关同步代码块、同步方法、同步类、同步静态方法。 同步代码块 同步代码块只作用于同一个对象如果调用两个对象上的同步代码块就不会进行同步。 使用ExecutorService执行了两个线程由于调用的是同一个对象的同步代码块因此这两个线程会进行同步当一个线程进入同步语句块时另一个线程就必须等待。 public class SynchronizedExample {public void func1() {synchronized (this) {for (int i 0; i 10; i) {System.out.print(i );}}}public static void main(String[] args) {SynchronizedExample e1 new SynchronizedExample();ExecutorService executorService Executors.newCachedThreadPool();executorService.execute(() - e1.func1());executorService.execute(() - e1.func1());} }// 输出结果0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 两个线程调用了不同对象的同步代码块因此这两个线程就不需要同步。从输出结果可以看出两个线程交叉执行。 public static void main(String[] args) {SynchronizedExample e1 new SynchronizedExample();SynchronizedExample e2 new SynchronizedExample();ExecutorService executorService Executors.newCachedThreadPool();executorService.execute(() - e1.func1());executorService.execute(() - e2.func1()); }// 输出结果0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9同步方法 效果和同步代码块一样。 public synchronized void func () {// ... } 同步类 作用于整个类也就是说两个线程调用同一个类的不同对象上的这种同步语句也会进行同步。 public class SynchronizedExample {public void func2() {synchronized (SynchronizedExample.class) {for (int i 0; i 10; i) {System.out.print(i );}}}public static void main(String[] args) {SynchronizedExample e1 new SynchronizedExample();SynchronizedExample e2 new SynchronizedExample();ExecutorService executorService Executors.newCachedThreadPool();executorService.execute(() - e1.func2());executorService.execute(() - e2.func2());} }// 输出结果0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 同步静态方法 效果和同步类一样作用于整个类。 public synchronized static void fun() {// ... } ReentrantLock ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。 public class LockExample {private Lock lock new ReentrantLock();public void func() {lock.lock();try {for (int i 0; i 10; i) {System.out.print(i );}} finally {lock.unlock(); // 确保释放锁从而避免发生死锁。}}public static void main(String[] args) {LockExample lockExample new LockExample();ExecutorService executorService Executors.newCachedThreadPool();executorService.execute(() - lockExample.func());executorService.execute(() - lockExample.func());} }// 输出结果0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 synchronzied、ReentrantLock对比 锁的实现sychronized 是JVM实现的而ReentrantLock是JDK实现的。性能新版本Java对sychronized进行了很多优化例如自旋锁等sychronized与ReentrantLock大致相同。等待可中断当持有锁的线程长期不释放锁的时候正在等待的线程选择放弃等待改为处理其他事情。ReentrantLock可中断而sychronized不行。公平锁公平锁是指多个线程在等待同一个锁时必须按照申请锁的时间顺序来依次获得锁。sychronized中的锁是非公平的ReentrantLock默认情况下也是非公平的但是也可以是公平的。锁绑定多个条件一个ReentrantLock可以同时绑定多个Condition对象。 互斥锁选择 使用synchronized是首选方法除非在需要ReentrantLock的高级功能。 synhronized是由JVM直接支持并实现的锁机制而不是所有的JDK版本都支持ReentrantLock。此外使用synchronized没必要担心锁未被释放而引起的死锁因为JVM会自动确保锁被正确释放。 ReentrantLock需要手动释放锁所以需要确保在finally块中释放锁否则容易造成线程死锁。 线程间协作  join() join()方法是Thread类中的一个方法用于等待一个线程的完成。当一个线程在另一个线程上调用join()方法时调用线程会被阻塞直到被调用线程执行完毕。 举例在thread1和thread2执行完毕之后再执行下面打印的代码 Thread thread1 new Thread(new MyRunnable()); Thread thread2 new Thread(new MyRunnable());thread1.start(); thread2.start();// 在thread1和thread2执行完毕之后再执行下面的代码 thread1.join(); thread2.join();System.out.println(线程执行完毕); 举例等待thread1和thread2执行完毕最多等待5秒 Thread thread1 new Thread(new MyRunnable()); Thread thread2 new Thread(new MyRunnable());thread1.start(); thread2.start();// 等待thread1和thread2执行完毕最多等待5秒 thread1.join(5000); thread2.join(5000);System.out.println(线程执行完毕); 举例虽然b线程先启动但是因为在b线程中调用了a线程的join()方法b线程会等待a线程结束才继续执行因此最后能够保证a线程的输出先于b线程的输出。 public class JoinExample {private class A extends Thread {Overridepublic void run() {System.out.println(A);}}private class B extends Thread {private A a;B(A a) {this.a a;}Overridepublic void run() {try {a.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(B);}}public void test() {A a new A();B b new B(a);b.start();a.start();}public static void main(String[] args) {JoinExample example new JoinExample();example.test();} }// 输出结果A B wait()notify()notifyAll() 调用wait()使得线程等待某个条件满足线程在等待时会被挂起当其他线程的运行使得这个条件满足时其他线程会调用notify()或者notifyAll()来唤醒挂起的线程。 wait()notify()notifyAll()都属于Object的一部分而不属于Thread。只能在同步方法或者同步控制块中使用否则会在运行时抛出IllegalMonitorStateExecption。使用wait()挂起期间线程会释放锁。这是因为如果没有释放锁那么其他线程就无法进入对象的同步方法或者同步控制块中那么就无法执行notify()或者notifyAll()来唤醒挂起的线程造成死锁。 public class WaitNotifyExample {public synchronized void before() {System.out.println(before);notifyAll();}public synchronized void after() {try {wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(after);}public static void main(String[] args) {ExecutorService executorService Executors.newCachedThreadPool();WaitNotifyExample example new WaitNotifyExample();executorService.execute(() - example.after());executorService.execute(() - example.before());} }// 输出结果before after wait()和sleep()的区别 wait()是Object的方法而sleep()是Thread的静态方法wait()会释放锁sleep()不会。 await()signal()signAll() java.util.concurrent类库中提供了Condition类来实现线程之间的协调可以在Condition上调用await()方法使线程等待其他线程调用signal()或signalAll()方法唤醒等待的线程。相比于wait()这种等待方式。await()可以指定等待的条件因此更加灵活。 public class AwaitSignalExample {private Lock lock new ReentrantLock();private Condition condition lock.newCondition();public void before() {lock.lock();try {System.out.println(before);condition.signalAll();} finally {lock.unlock();}}public void after() {lock.lock();try {condition.await();System.out.println(after);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {ExecutorService executorService Executors.newCachedThreadPool();AwaitSignalExample example new AwaitSignalExample();executorService.execute(() - example.after());executorService.execute(() - example.before());} }// 输出结果before after Thread方法概述 方法说明start()启动线程并使其处于可执行状态。run()线程的执行体,定义了线程要执行的操作。sleep(long millis)让当前线程暂停执行指定的毫秒数。yield()暂停当前正在执行的线程。让其他线程有机会继续执行。join()等待其他线程终止。interrupt()中断线程。isInterrupted()判断线程是否被中断。isAlive()判断线程是否还存活。setName(String name)设置线程的名称。getName()获取线程的名称。
http://www.hkea.cn/news/14256515/

相关文章:

  • 石家庄网站建设开发专业做汽车网站优化排名
  • iis 子网站关于企业网站建设的请示
  • 商城网站多少钱做php网站如何做多语言
  • 青县做网站价格用户体验较好的网站
  • 巨鹿网站建设多少钱国精产品w灬源码1688说明
  • 南宁微网站制作系部网站建设研究方案
  • 求个网站你们知道的广东嵘通建设
  • 怎么把自己做的网站发布到网上建个静态网站
  • 移动网站建设服务商榆林电商网站建设
  • 苏州专业建站厦门大型服装商城网站建设
  • 如何选择丹阳网站建设网站怎么加内容
  • 大淘客怎样做网站微信公众上传wordpress
  • 作文素材网站郑州seo排名扣费
  • 嘉纪商正网站建设公司2022年新闻大事
  • 公司建网站一般多少钱企业商务网站建设论文
  • 网站建设基本技术app设计界面
  • 公章在线制作网站做不了wordpress 视频页面
  • 自主建站是什么意思济南房产网安居客
  • 河南城市建设招标类网站搜索引擎中 哪些网站可以获得更好的排名
  • 山东做网站公司有哪些室内设计效果图招聘
  • 成都制作网站的公司简介两学一做考学网站
  • 做网站要多大空间做网站的找哪个
  • wordpress国内网站吗正规的手机网站建设
  • 贵阳网站建设搜王道下拉如何提高网页设计
  • 网站开发PRD做模板网站价格
  • 做网站推广的需要了解哪些知识南宁在那里推广网站
  • 怎么做一个门户网站温州网站建设seo
  • 网站制作维护费 归属泉州网络公司都
  • 男装网站模板演示如何提取网页中的视频
  • 织梦教育网站开发电子商务的建站流程