线程 订阅
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。一个进程可以有很多线程,每条线程并行执行不同的任务。在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。 展开全文
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。一个进程可以有很多线程,每条线程并行执行不同的任务。在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
信息
提出者
dozer
定    义
程序执行流的最小单元
多线程中
CPU的基本单位
应用学科
计算机
中文名
线程
外文名
thread
别    称
执行绪;轻量进程
线程发展简史
线程的引入:60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。因此在80年代,出现了能独立运行的基本单位——线程(Threads)。
收起全文
精华内容
参与话题
问答
  • java 线程 多线程 thread 设置线程名称 获取线程名称 public class Name extends Thread { @Override public void run() { //setName()方法设置线程名称 this.setName("线程A"); System.out.println(...

    java 线程 多线程 thread 设置线程名称 获取线程名称

     

    public class Name extends Thread {
    	
    	@Override
    	public void run() {
    		//setName()方法设置线程名称
    		this.setName("线程A");
    		System.out.println("线程名称:" + this.getName());
    	}
    
    	public static void main(String[] args) {
    		new Name().start();
    		
    		//构造方法设置线程名称
    		new Thread("线程B") {
    			public void run() {
    				System.out.println("线程名称:" + this.getName());
    			}
    		}.start();
    	}
    
    }
    线程名称:线程A
    线程名称:线程B

     

    展开全文
  • C++11多线程编程

    千人学习 2018-09-06 14:34:58
    本课程,讲解的重点定位在c++11新标准中的多线程开发部分,同时,老师还会结合自己的经验把多线程的讲解进一步拓展到一个比较大的范畴,因为无论是c++11多线程开发还是各种其他的多线程开发实现方法,都有很多类似的...
  • 多线程面试题(值得收藏)

    万次阅读 多人点赞 2019-08-16 09:41:18
    史上最强多线程面试47题(含答案),建议收藏 金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~ 1、并发编程三要素?...

    史上最强多线程面试47题(含答案),建议收藏

    金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~

    1、并发编程三要素?

    1)原子性

    原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。

    2)可见性

    可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。

    3)有序性

    有序性,即程序的执行顺序按照代码的先后顺序来执行。

    2、实现可见性的方法有哪些?

    synchronized或者Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。

    3、多线程的价值?

    1)发挥多核CPU的优势

    多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。

    2)防止阻塞

    从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

    3)便于建模

    这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

    4、创建线程的有哪些方式?

    1)继承Thread类创建线程类

    2)通过Runnable接口创建线程类

    3)通过Callable和Future创建线程

    4)通过线程池创建

    5、创建线程的三种方式的对比?

    1)采用实现Runnable、Callable接口的方式创建多线程。

    优势是:

    线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

    在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

    劣势是:

    编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

    2)使用继承Thread类的方式创建多线程

    优势是:

    编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

    劣势是:

    线程类已经继承了Thread类,所以不能再继承其他父类。

    3)Runnable和Callable的区别

    Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
    Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
    Call方法可以抛出异常,run方法不可以。
    运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

    6、线程的状态流转图

    线程的生命周期及五种基本状态:

    7、Java线程具有五中基本状态

    1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

    2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

    3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就
    绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

    4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

    根据阻塞产生的原因不同,阻塞状态又可以分为三种:

    a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

    b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

    c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

    5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    8、什么是线程池?有哪几种创建方式?

    线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。

    java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。

    9、四种线程池的创建:

    1)newCachedThreadPool创建一个可缓存线程池

    2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。

    3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

    4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。

    10、线程池的优点?

    1)重用存在的线程,减少对象创建销毁的开销。

    2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。

    3)提供定时执行、定期执行、单线程、并发数控制等功能。

    11、常用的并发工具类有哪些?

    CountDownLatch
    CyclicBarrier
    Semaphore
    Exchanger

    12、CyclicBarrier和CountDownLatch的区别

    1)CountDownLatch简单的说就是一个线程等待,直到他所等待的其他线程都执行完成并且调用countDown()方法发出通知后,当前线程才可以继续执行。

    2)cyclicBarrier是所有线程都进行等待,直到所有线程都准备好进入await()方法之后,所有线程同时开始执行!

    3)CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。

    4)CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。如果被中断返回true,否则返回false。

    13、synchronized的作用?

    在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。

    synchronized既可以加在一段代码上,也可以加在方法上。

    14、volatile关键字的作用

    对于可见性,Java提供了volatile关键字来保证可见性。

    当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

    从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。

    15、什么是CAS

    CAS是compare and swap的缩写,即我们所说的比较交换。

    cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。

    CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

    java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的( AtomicInteger,AtomicBoolean,AtomicLong)。

    16、CAS的问题

    1)CAS容易造成ABA问题

    一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。

    2) 不能保证代码块的原子性

    CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

    3)CAS造成CPU利用率增加

    之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。

    17、什么是Future?

    在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承thread类还是实现runnable接口,都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果。

    Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。

    18、什么是AQS

    AQS是AbustactQueuedSynchronizer的简称,它是一个Java提高的底层同步工具类,用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态。

    AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。

    19、AQS支持两种同步方式:

    1)独占式

    2)共享式

    这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock。总之,AQS为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。

    20、ReadWriteLock是什么

    首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

    因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

    21、FutureTask是什么

    这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

    22、synchronized和ReentrantLock的区别

    synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:

    1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁

    2)ReentrantLock可以获取各种锁的信息

    3)ReentrantLock可以灵活地实现多路通知

    另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。

    23、什么是乐观锁和悲观锁

    1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

    2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

    24、线程B怎么知道线程A修改了变量

    volatile修饰变量
    synchronized修饰修改变量的方法
    wait/notify
    while轮询

    25、synchronized、volatile、CAS比较

    synchronized是悲观锁,属于抢占式,会引起其他线程阻塞。
    volatile提供多线程共享变量可见性和禁止指令重排序优化。
    CAS是基于冲突检测的乐观锁(非阻塞)

    26、sleep方法和wait方法有什么区别?

    这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器

    27、ThreadLocal是什么?有什么用?

    ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

    简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。

    28、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

    这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁

    29、多线程同步有哪几种方法?

    Synchronized关键字,Lock锁实现,分布式锁等。

    30、线程的调度策略

    线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:

    1)线程体中调用了yield方法让出了对cpu的占用权利

    2)线程体中调用了sleep方法使线程进入睡眠状态

    3)线程由于IO操作受到阻塞

    4)另外一个更高优先级线程出现

    5)在支持时间片的系统中,该线程的时间片用完

    31、ConcurrentHashMap的并发度是什么

    ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

    32、Linux环境下如何查找哪个线程使用CPU最长

    1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过

    2)top -H -p pid,顺序不能改变

    33、Java死锁以及如何避免?

    Java中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java死锁情况出现至少两个线程和两个或更多资源。

    Java发生死锁的根本原因是:在申请锁时发生了交叉闭环申请。

    34、死锁的原因

    1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。

    例如:线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入死锁循环。

    2)默认的锁申请操作是阻塞的。

    所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。

    35、怎么唤醒一个阻塞的线程

    如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

    36、不可变对象对多线程有什么帮助

    前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。

    37、什么是多线程的上下文切换

    多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

    38、如果你提交任务时,线程池队列已满,这时会发生什么

    这里区分一下:

    1)如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务

    2)如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy

    39、Java中用到的线程调度算法是什么

    抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

    40、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

    线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

    41、什么是自旋

    很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

    42、Java
    Concurrency API中的Lock接口(Lock
    interface)是什么?对比同步它有什么优势?

    Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

    它的优势有:

    可以使锁更公平
    可以使线程在等待锁的时候响应中断
    可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
    可以在不同的范围,以不同的顺序获取和释放锁

    43、单例模式的线程安全性

    老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:

    1)饿汉式单例模式的写法:线程安全

    2)懒汉式单例模式的写法:非线程安全

    3)双检锁单例模式的写法:线程安全

    44、Semaphore有什么作用

    Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

    45、Executors类是什么?

    Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。

    Executors可以用于方便的创建线程池

    46、线程类的构造方法、静态块是被哪个线程调用的

    这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

    如果说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:

    1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的

    2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的

    47、同步方法和同步块,哪个是更好的选择?

    同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。

    48、Java线程数过多会造成什么异常?

    1)线程的生命周期开销非常高

    2)消耗过多的CPU资源

    如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争CPU资源时还将产生其他性能的开销。

    3)降低稳定性

    JVM在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括JVM的启动参数、Thread构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError异常。

    展开全文
  • 同步还是异步,单线程还是多线程是我们软件开发中经常面临的设计选择,只有准确的区分它们之间的优缺点,才能根据我们的实际应用场景作出正确选择。它们两两组合一共有四种模式,下面就我自己的理解做一些总结分析。...

    同步还是异步,单线程还是多线程是我们软件开发中经常面临的设计选择,只有准确的区分它们之间的优缺点,才能根据我们的实际应用场景作出正确选择。它们两两组合一共有四种模式,下面就我自己的理解做一些总结分析。

     1、同步单线程模式

    优点:a)实现简单。b)不用考虑线程间同步互斥问题。

    缺点:a)对CPU的使用率不高(容易在进行IO操作或自身等待操作时阻塞),在多CPU时劣势更明显。b)并发性不好,在有的事件需要长时间占用CPU处理的情况下,其他事件会长时间等待得不到处理。

     2、同步多线程模式

    优点:a)对CPU的使用率较高,在多CPU时优势更明显。b)并发性好,各线程都能根据优先级得到执行。

    缺点:a)需要考虑线程间同步互斥问题。b)实现较复杂,不同线程的业务步骤有相互依赖时,需要分解实现成状态机及事件通知驱动模式(或者轮询模式)。

     3、异步多线程模式

    优点:a)对CPU的使用率高,在多CPU时优势更明显。b)并发性好,各线程都能根据优先级得到执行。

    缺点:a)需要考虑线程间同步互斥问题。b)实现复杂,要把所有会导致阻塞的操作转化为异步操作,另外不同线程的业务步骤有相互依赖时,需要分解实现成状态机及事件通知驱动模式(或者轮询模式)。

     4、异步单线程模式

    优点:a)对CPU的使用率高。b)不用考虑线程间同步互斥问题。

    缺点:a)实现较复杂,要把所有会导致阻塞的操作转化为异步操作。b)并发性不好,在有的事件需要长时间占用CPU处理的情况下,其他事件会长时间等待得不到处理。c)在多CPU时不如多线程高效。

    简单的说:同步实现简单但是CPU利用率低,异步实现复杂但是CPU利用率高。

    单线程不用考虑互斥但是并发性、多CPU利用率低,多线程需要考虑互斥但是并发性、多CPU利用率高。

    (完)

    展开全文
  • 多线程(一):创建线程和线程的常用方法

    万次阅读 多人点赞 2018-09-01 19:14:23
    一:为什么要学多线程 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也...
    分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。

    一:为什么要学多线程

    1. 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。
    2. 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),虽然代码中很少会自己去创建线程,但是实际环境中每行代码却都是并行执行的,同一时刻大量请求同一个接口,并发可能会产生一些问题,所以也需要掌握一定的并发知识

    二:进程与线程

    1. 进程

    进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

    2. 线程

    线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

    一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。

    线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行。

    3. 进程与线程的关系

    一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:

    • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。

    • 处理机分给线程,即真正在处理机上运行的是线程。

    • 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

    如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答,此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)

    4. 为什么要使用多线程

    多线程可以提高程序的效率。

    实际生活案例:村长要求喜洋洋在一个小时内打100桶水,可以喜洋洋一个小时只能打25桶水,如果这样就需要4个小时才能完成任务,为了在一个小时能够完成,喜洋洋就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样4只羊同时干活,在一小时内完成了任务。原本用4个小时完成的任务现在只需要1个小时就完成了,如果把每只羊看做一个线程,多只羊即多线程可以提高程序的效率。

    5. 多线程应用场景

    • 一般线程之间比较独立,互不影响
    • 一个线程发生问题,一般不影响其它线程

    三:多线程的实现方式

    1. 顺序编程

    顺序编程:程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。

    public class Main {
        // 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚才能喝酒
        public static void main(String[] args) throws Exception {
    		// 先吃饭再喝酒
            eat();
            drink();
        }
    
        private static void eat() throws Exception {
            System.out.println("开始吃饭?...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束吃饭?...\t" + new Date());
        }
    
        private static void drink() throws Exception {
            System.out.println("开始喝酒?️...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    这里写图片描述

    2. 并发编程

    并发编程:多个任务可以同时做,常用与任务之间比较独立,互不影响。
    线程上下文切换:

    同一个时刻一个CPU只能做一件事情,即同一时刻只能一个线程中的部分代码,假如有两个线程,Thread-0和Thread-1,刚开始CPU说Thread-0你先执行,给你3毫秒时间,Thread-0执行了3毫秒时间,但是没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,当时的变量的值是多少,然后CPU说Thread-1你可以执行了,给你2毫秒的时间,Thread-1执行了2毫秒也没执行完,此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,当时的变量的值是多少,此时CPU又说Thread-0又该你,这次我给你5毫秒时间,去执行吧,此时CPU就找出上次Thread-0线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续执行,结果用了2毫秒就Thread-0就执行完了,就终止了,然后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续开始执行,结果Thread-1在4毫秒内也执行结束了,Thread-1也结束了终止了。CPU在来回改变线程的执行机会称之为线程上下文切换。

    public class Main {
        public static void main(String[] args) {
    	    // 一边吃饭一边喝酒
            new EatThread().start();
            new DrinkThread().start();
        }
    }
    
    class EatThread extends Thread{
        @Override
        public void run() {
            System.out.println("开始吃饭?...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束吃饭?...\t" + new Date());
        }
    }
    
    class DrinkThread extends Thread {
        @Override
        public void run() {
            System.out.println("开始喝酒?️...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    并发编程,一边吃饭一边喝酒总共用时5秒,比顺序编程更快,因为并发编程可以同时运行,而不必等前面的代码运行完之后才允许后面的代码

    这里写图片描述

    本示例主要启动3个线程,一个主线程main thread、一个吃饭线程(Thread-0)和一个喝酒线程(Thread-1),共三个线程, 三个线程并发切换着执行。main线程很快执行完,吃饭线程和喝酒线程会继续执行,直到所有线程(非守护线程)执行完毕,整个程序才会结束,main线程结束并不意味着整个程序结束。
    这里写图片描述

    • 顺序:代码从上而下按照固定的顺序执行,只有上一件事情执行完毕,才能执行下一件事。就像物理电路中的串行,假如有十件事情,一个人来完成,这个人必须先做第一件事情,然后再做第二件事情,最后做第十件事情,按照顺序做。

    • 并行:多个操作同时处理,他们之间是并行的。假如十件事情,两个人来完成,每个人在某个时间点各自做各自的事情,互不影响

    • 并发:将一个操作分割成多个部分执行并且允许无序处理,假如有十件事情,如果有一个人在做,这个人可能做一会这个不想做了,再去做别的,做着做着可能也不想做了,又去干其它事情了,看他心情想干哪个就干哪个,最终把十件事情都做完。如果有两个人在做,他们俩先分一下,比如张三做4件,李四做6件,他们各做自己的,在做自己的事情过程中可以随意的切换到别的事情,不一定要把某件事情干完再去干其它事情,有可能一件事做了N次才做完。

    通常一台电脑只有一个cpu,多个线程属于并发执行,如果有多个cpu,多线程并发执行有可能变成并行执行。
    这里写图片描述

    3. 多线程创建方式

    • 继承 Thread
    • 实现 Runable
    • 实现 Callable
    ①:继成java.lang.Thread, 重写run()方法
    public class Main {
        public static void main(String[] args) {
            new MyThread().start();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    Thread 类

    package java.lang;
    public class Thread implements Runnable {
    	// 构造方法
    	public Thread(Runnable target);
    	public Thread(Runnable target, String name);
    	
    	public synchronized void start();
    }
    

    Runnable 接口

    package java.lang;
    
    @FunctionalInterface
    public interface Runnable {
        pubic abstract void run();
    }
    

    ②:实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装

    public class Main {
        public static void main(String[] args) {
        	 // 将Runnable实现类作为Thread的构造参数传递到Thread类中,然后启动Thread类
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable).start();
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    可以看到两种方式都是围绕着Thread和Runnable,继承Thread类把run()写到类中,实现Runnable接口是把run()方法写到接口中然后再用Thread类来包装, 两种方式最终都是调用Thread类的start()方法来启动线程的。
    两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式

    其它变体写法:

    public class Main {
        public static void main(String[] args) {
            // 匿名内部类
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }).start();
    
            // 尾部代码块, 是对匿名内部类形式的语法糖
            new Thread() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }.start();
    
            // Runnable是函数式接口,所以可以使用Lamda表达式形式
            Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());};
            new Thread(runnable).start();
        }
    }
    

    ③:实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread

    Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

    public class Main {
        public static void main(String[] args) throws Exception {
        	 // 将Callable包装成FutureTask,FutureTask也是一种Runnable
            MyCallable callable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
    
            // get方法会阻塞调用的线程
            Integer sum = futureTask.get();
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
        }
    }
    
    
    class MyCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
    
            int sum = 0;
            for (int i = 0; i <= 100000; i++) {
                sum += i;
            }
            Thread.sleep(5000);
    
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
            return sum;
        }
    }
    

    Callable 也是一种函数式接口

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    FutureTask

    public class FutureTask<V> implements RunnableFuture<V> {
    	// 构造函数
    	public FutureTask(Callable<V> callable);
    	
    	// 取消线程
    	public boolean cancel(boolean mayInterruptIfRunning);
    	// 判断线程
    	public boolean isDone();
    	// 获取线程执行结果
    	public V get() throws InterruptedException, ExecutionException;
    }
    

    RunnableFuture

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

    三种方式比较:

    • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
    • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
    • Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
    • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
    • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

    四:线程的状态

    1. 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
    2. 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
    3. 运行(running)状态: 执行run()方法
    4. 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
    5. 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

    这里写图片描述

    这里写图片描述

    这里写图片描述

    五:Thread常用方法

    Thread

    public class Thread implements Runnable {
        // 线程名字
        private volatile String name;
        // 线程优先级(1~10)
        private int priority;
        // 守护线程
        private boolean daemon = false;
        // 线程id
        private long tid;
        // 线程组
        private ThreadGroup group;
        
        // 预定义3个优先级
        public final static int MIN_PRIORITY = 1;
        public final static int NORM_PRIORITY = 5;
        public final static int MAX_PRIORITY = 10;
        
        
        // 构造函数
        public Thread();
        public Thread(String name);
        public Thread(Runnable target);
        public Thread(Runnable target, String name);
        // 线程组
        public Thread(ThreadGroup group, Runnable target);
        
        
        // 返回当前正在执行线程对象的引用
        public static native Thread currentThread();
        
        // 启动一个新线程
        public synchronized void start();
        // 线程的方法体,和启动线程没毛关系
        public void run();
        
        // 让线程睡眠一会,由活跃状态改为挂起状态
        public static native void sleep(long millis) throws InterruptedException;
        public static void sleep(long millis, int nanos) throws InterruptedException;
        
        // 打断线程 中断线程 用于停止线程
        // 调用该方法时并不需要获取Thread实例的锁。无论何时,任何线程都可以调用其它线程的interruptf方法
        public void interrupt();
        public boolean isInterrupted()
        
        // 线程是否处于活动状态
        public final native boolean isAlive();
        
        // 交出CPU的使用权,从运行状态改为挂起状态
        public static native void yield();
        
        public final void join() throws InterruptedException
        public final synchronized void join(long millis)
        public final synchronized void join(long millis, int nanos) throws InterruptedException
        
        
        // 设置线程优先级
        public final void setPriority(int newPriority);
        // 设置是否守护线程
        public final void setDaemon(boolean on);
        // 线程id
        public long getId() { return this.tid; }
        
        
        // 线程状态
        public enum State {
            // new 创建
            NEW,
    
            // runnable 就绪
            RUNNABLE,
    
            // blocked 阻塞
            BLOCKED,
    
            // waiting 等待
            WAITING,
    
            // timed_waiting
            TIMED_WAITING,
    
            // terminated 结束
            TERMINATED;
        }
    }
    
    public static void main(String[] args) {
        // main方法就是一个主线程
    
        // 获取当前正在运行的线程
        Thread thread = Thread.currentThread();
        // 线程名字
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否存活
        boolean alive = thread.isAlive();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
    
        // Thread[name=main, id=1 ,priority=5 ,alive=true ,daemon=false]
        System.out.println("Thread[name=" + name + ", id=" + id + " ,priority=" + priority + " ,alive=" + alive + " ,daemon=" + daemon + "]");
    }
    
    0. Thread.currentThread()
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        // 线程名称
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程已经启动且尚未终止
        // 线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的
        boolean alive = thread.isAlive();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
        
        // Thread[name=main,id=1,alive=true,priority=5,daemon=false]
        System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");
    }
    
    1. start() 与 run()
    public static void main(String[] args) throws Exception {
       new Thread(()-> {
           for (int i = 0; i < 5; i++) {
               System.out.println(Thread.currentThread().getName() + " " + i);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-A").start();
    
       new Thread(()-> {
           for (int j = 0; j < 5; j++) {
               System.out.println(Thread.currentThread().getName() + " " + j);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-B").start();
    }
    

    start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
    这里写图片描述

    public static void main(String[] args) throws Exception {
        new Thread(()-> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-A").run();
    
        new Thread(()-> {
            for (int j = 0; j < 5; j++) {
                System.out.println(Thread.currentThread().getName() + " " + j);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-B").run();
    }
    

    注意:执行结果都是main主线程
    这里写图片描述

    run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。

    有些面试官经常问一些启动一个线程是用start()方法还是run()方法,为了面试而面试。

    2. sleep() 与 interrupt()
    public static native void sleep(long millis) throws InterruptedException;
    public void interrupt();
    

    sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。

    • sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
    • interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
    public static void main(String[] args) throws Exception {
        Thread thread0 = new Thread(()-> {
            try {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,让我睡10秒,中间有事叫我,zZZ。。。");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要继续干活了");
            }
        });
        thread0.start();
    
        // 这里睡眠只是为了保证先让上面的那个线程先执行
        Thread.sleep(2000);
    
        new Thread(()-> {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,别睡了,起来干活了!!!");
            // 无需获取锁就可以调用interrupt
            thread0.interrupt();
        }).start();
    }
    

    这里写图片描述

    3. wait() 与 notify()

    wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()

    • wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
    • notify(): 该方法只能在同步方法或同步块内部调用, 随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
    • notifyAll(): 唤醒所有的wait对象

    注意:

    • Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部
    • 让哪个对象等待wait就去通知notify哪个对象,不要让A对象等待,结果却去通知B对象,要操作同一个对象

    Object

    public class Object {
    	public final void wait() throws InterruptedException;
    	public final native void wait(long timeout) throws InterruptedException;
    	public final void wait(long timeout, int nanos) throws InterruptedException;
    	
    	
    	public final native void notify();
    	public final native void notifyAll();
    }
    

    WaitNotifyTest

    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡觉1秒中,目的是让上面的线程先执行,即先执行wait()");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                waitNotifyTest.notifyPrint();
            }).start();
        }
    
        private synchronized void printFile() throws InterruptedException {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
            this.wait();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
        }
    
        private synchronized void notifyPrint() {
            this.notify();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
        }
    }
    
    

    这里写图片描述

    wait():让程序暂停执行,相当于让当前,线程进入当前实例的等待队列,这个队列属于该实例对象,所以调用notify也必须使用该对象来调用,不能使用别的对象来调用。调用wait和notify必须使用同一个对象来调用。
    这里写图片描述

    this.notifyAll();
    这里写图片描述

    4. sleep() 与 wait()
    ① Thread.sleep(long millis): 睡眠时不会释放锁
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    try { Thread.sleep(1000); } catch (InterruptedException e) { }
                }
            }
        }).start();
    
        Thread.sleep(1000);
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                }
            }
        }).start();
    }
    

    因main方法中Thread.sleep(1000)所以上面的线程Thread-0先被执行,当循环第一次时就会Thread.sleep(1000)睡眠,因为sleep并不会释放锁,所以Thread-1得不到执行的机会,所以直到Thread-0执行完毕释放锁对象lock,Thread-1才能拿到锁,然后执行Thread-1;
    这里写图片描述

    5. wait() 与 interrupt()

    wait(): 方法的作用是释放锁,加入到等待队列,当调用interrupt()方法后,线程必须先获取到锁后,然后才抛出异常InterruptedException 。注意: 在获取锁之前是不会抛出异常的,只有在获取锁之后才会抛异常

    所有能抛出InterruptedException的方法都可以通过interrupt()来取消的

    public static native void sleep(long millis) throws InterruptedException;
    public final void wait() throws InterruptedException;
    public final void join() throws InterruptedException;
    public void interrupt();
    

    notify()和interrupt()
    从让正在wait的线程重新运行这一点来说,notify方法和intterrupt方法的作用有些类似,但仍有以下不同之处:

    • notify/notifyAll是java.lang.Object类的方法,唤醒的是该实例的等待队列中的线程,而不能直接指定某个具体的线程。notify/notifyAll唤醒的线程会继续执行wait的下一条语句,另外执行notify/notifyAll时线程必须要获取实例的锁

    • interrupte方法是java.lang.Thread类的方法,可以直接指定线程并唤醒,当被interrupt的线程处于sleep或者wait中时会抛出InterruptedException异常。执行interrupt()并不需要获取取消线程的锁。

    • 总之notify/notifyAll和interrupt的区别在于是否能直接让某个指定的线程唤醒、执行唤醒是否需要锁、方法属于的类不同

    6. interrupt()

    有人也许认为“当调用interrupt方法时,调用对象的线程就会InterruptedException异常”, 其实这是一种误解,实际上interrupt方法只是改变了线程的“中断状态”而已,所谓中断状态是一个boolean值,表示线程是否被中断的状态。

    public class Thread implements Runnable {
    	public void interrupt() {
    		中断状态 = true;
    	}
    	
    	// 检查中断状态
    	public boolean isInterrupted();
    	
    	// 检查中断状态并清除当前线程的中断状态
    	public static boolean interrupted() {
    		// 伪代码
    		boolean isInterrupted = isInterrupted();
    		中断状态 = false;
    	}
    }	
    

    假设Thread-0执行了sleep、wait、join中的一个方法而停止运行,在Thread-1中调用了interrupt方法,此时线程Thread-0的确会抛出InterruptedException异常,但这其实是sleep、wait、join中的方法内部会对线程的“中断状态”进行检查,如果中断状态为true,就会抛出InterruptedException异常。假如某个线程的中断状态为true,但线程体中却没有调用或者没有判断线程中断状态的值,那么线程则不会抛出InterruptedException异常。

    isInterrupted() 检查中断状态
    若指定线程处于中断状态则返回true,若指定线程为非中断状态,则反回false, isInterrupted() 只是获取中断状态的值,并不会改变中断状态的值。

    interrupted()
    检查中断状态并清除当前线程的中断状态。如当前线程处于中断状态返回true,若当前线程处于非中断状态则返回false, 并清除中断状态(将中断状态设置为false), 只有这个方法才可以清除中断状态,Thread.interrupted的操作对象是当前线程,所以该方法并不能用于清除其它线程的中断状态。

    interrupt()与interrupted()

    • interrupt():打断线程,将中断状态修改为true
    • interrupted(): 不打断线程,获取线程的中断状态,并将中断状态设置为false

    这里写图片描述

    public class InterrupptTest {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
            boolean interrupted = thread.isInterrupted();
            // interrupted=false
            System.out.println("interrupted=" + interrupted);
    
            thread.interrupt();
    
            boolean interrupted2 = thread.isInterrupted();
            // interrupted2=true
            System.out.println("interrupted2=" + interrupted2);
    
            boolean interrupted3 = Thread.interrupted();
            // interrupted3=false
            System.out.println("interrupted3=" + interrupted3);
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // InterruptedException	false
                    System.out.println("InterruptedException\t" + Thread.currentThread().isInterrupted());
                }
            }
        }
    }
    
    

    这里写图片描述

    ② object.wait(long timeout): 会释放锁
    public class SleepWaitTest {
        public static void main(String[] args) throws InterruptedException {
            SleepWaitTest object = new SleepWaitTest();
    
            new Thread(() -> {
                synchronized (object) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
                    try {
                        object.wait(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
                }
            }).start();
    
    		 // 先上面的线程先执行
            Thread.sleep(1000);
    
            new Thread(() -> {
                synchronized (object) {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    }
                }
            }).start();
        }
    }
    

    因main方法中有Thread.sleep(1000)所以上面的线程Thread-0肯定会被先执行,当Thread-0被执行时就拿到了object对象锁,然后进入wait(5000)5秒钟等待,此时wait释放了锁,然后Thread-1就拿到了锁就执行线程体,Thread-1执行完后就释放了锁,当等待5秒后Thread-0就能再次获取object锁,这样就继续执行后面的代码。wait方法是释放锁的,如果wait方法不释放锁那么Thread-1是拿不到锁也就没有执行的机会的,事实是Thread-1得到了执行,所以说wait方法会释放锁

    这里写图片描述

    ③ sleep与wait的区别
    • sleep在Thread类中,wait在Object类中
    • sleep不会释放锁,wait会释放锁
    • sleep使用interrupt()来唤醒,wait需要notify或者notifyAll来通知
    5.join()

    让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

    将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用

    join() 方法:

    public final void join() throws InterruptedException {
            join(0);
    }
    
    
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
        	 // 循环检查线程的状态是否还活着,如果死了就结束了,如果活着继续等到死
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    
    public final synchronized void join(long millis, int nanos) throws InterruptedException {
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
    
        join(millis);
    }
    
    

    JoinTest

    public class JoinTest {
        public static void main(String[] args) {
            new Thread(new ParentRunnable()).start();
        }
    }
    
    class ParentRunnable implements Runnable {
        @Override
        public void run() {
            // 线程处于new状态
            Thread childThread = new Thread(new ChildRunable());
            // 线程处于runnable就绪状态
            childThread.start();
            try {
                // 当调用join时,parent会等待child执行完毕后再继续运行
                // 将某个线程加入到当前线程
                childThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程 running");
            }
        }
    }
    
    class ChildRunable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程 running");
            }
        }
    }
    
    

    程序进入主线程,运行Parent对应的线程,Parent的线程代码分两段,一段是启动一个子线程,一段是Parent线程的线程体代码,首先会将Child线程加入到Parent线程,join()方法会调用join(0)方法(join()方法是普通方法并没有加锁,join(0)会加锁),join(0)会执行while(isAlive()) { wait(0);} 循环判断线程是否处于活动状态,如果是继续wait(0)知道isAlive=false结束掉join(0), 从而结束掉join(), 最后回到Parent线程体中继续执行其它代码。

    在Parent调用child.join()后,child子线程正常运行,Parent父线程会等待child子线程结束后再继续运行。
    这里写图片描述

    • join() 和 join(long millis, int nanos) 最后都调用了 join(long millis)。

    • join(long millis, int nanos)和join(long millis)方法 都是synchronized。

    • join() 调用了join(0),从源码可以看到join(0)不断检查当前线程是否处于Active状态。

    • join() 和 sleep() 一样,都可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了wait(),会出让锁,而 sleep() 会一直保持锁。

    6. yield()

    交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间,yield就像一个好人似的,当CPU轮到它了,它却说我先不急,先给其他线程执行吧, 此方法很少被使用到,

    /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();
    

    这里写图片描述

    public static void main(String[] args) {
        new Thread(new Runnable() {
            int sum = 0;
            @Override
            public void run() {
                long beginTime=System.currentTimeMillis();
                for (int i = 0; i < 99999; i++) {
                    sum += 1;
                    // 去掉该行执行用2毫秒,加上271毫秒
                    Thread.yield();
                }
                long endTime=System.currentTimeMillis();
                System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");
            }
        }).start();
    }
    

    sleep(long millis) 与 yeid()

    • sleep(long millis): 需要指定具体睡眠的时间,不会释放锁,睡眠期间CPU会执行其它线程,睡眠时间到会立刻执行
    • yeid(): 交出CPU的执行权,不会释放锁,和sleep不同的时当再次获取到CPU的执行,不能确定是什么时候,而sleep是能确定什么时候再次执行。两者的区别就是sleep后再次执行的时间能确定,而yeid是不能确定的
    • yield会把CPU的执行权交出去,所以可以用yield来控制线程的执行速度,当一个线程执行的比较快,此时想让它执行的稍微慢一些可以使用该方法,想让线程变慢可以使用sleep和wait,但是这两个方法都需要指定具体时间,而yield不需要指定具体时间,让CPU决定什么时候能再次被执行,当放弃到下次再次被执行的中间时间就是间歇等待的时间
    7. setDaemon(boolean on)

    线程分两种:

    • 用户线程:如果主线程main停止掉,不会影响用户线程,用户线程可以继续运行。
    • 守护线程:如果主线程死亡,守护线程如果没有执行完毕也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程执行结束,子线程仍然继续执行,主线程和用户线程的生命周期各自独立。");
    }
    

    这里写图片描述

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.setDaemon(true);
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程死亡,子线程也要陪着一块死!");
    }
    

    这里写图片描述

    六 线程组

    可以对线程分组,分组后可以统一管理某个组下的所有线程,例如统一中断所有线程

    public class ThreadGroup implements Thread.UncaughtExceptionHandler {
        private final ThreadGroup parent;
        String name;
        int maxPriority;
        
        Thread threads[];
        
        private ThreadGroup() {
            this.name = "system";
            this.maxPriority = Thread.MAX_PRIORITY;
            this.parent = null;
        }
        
        public ThreadGroup(String name) {
            this(Thread.currentThread().getThreadGroup(), name);
        }
        
        public ThreadGroup(ThreadGroup parent, String name) {
            this(checkParentAccess(parent), parent, name);
        }
        
        // 返回此线程组中活动线程的估计数。 
        public int activeGroupCount();
        
        // 中断此线程组中的所有线程。
        public final void interrupt();
    }
    
    public static void main(String[] args) {
        String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
        System.out.println(mainThreadGroupName);
        // 如果一个线程没有指定线程组,默认为当前线程所在的线程组
        new Thread(() -> { }, "my thread1").start();
    
        ThreadGroup myGroup = new ThreadGroup("MyGroup");
        myGroup.setMaxPriority(5);
    
        Runnable runnable = () -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            String groupName = threadGroup.getName();
            ThreadGroup parentGroup = threadGroup.getParent();
            String parentGroupName = parentGroup.getName();
            ThreadGroup grandpaThreadGroup = parentGroup.getParent();
            String grandpaThreadGroupName = grandpaThreadGroup.getName();
            int maxPriority = threadGroup.getMaxPriority();
            int activeCount = myGroup.activeCount();
    
            // system <- main <- MyGroup(1) <- my thread2
            System.out.println(MessageFormat.format("{0} <- {1} <- {2}({3}) <- {4}",
                    grandpaThreadGroupName,
                    parentGroupName,
                    groupName,
                    activeCount,
                    Thread.currentThread().getName()));
        };
    
        new Thread(myGroup, runnable, "my thread2").start();
    }
    

    线程组与线程组之间是有父子关系的,自定义线程组的父线程组是main线程组,main线程组的父线程组是system线程组。
    这里写图片描述

    分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。
    展开全文
  • 多进程与多线程区别

    万次阅读 多人点赞 2016-04-21 10:01:16
    在Unix上编程采用多线程还是多进程的争执由来已久,这种争执最常见到在C/S通讯中服务端并发技术 的选型上,比如WEB服务器技术中,Apache是采用多进程的(perfork模式,每客户连接对应一个进程,每进程中只存在唯一一...
  • 本文将带领你与多线程作第一次亲密接触,并深入分析CreateThread与_beginthreadex的本质区别,相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beginthreadex到底有什么区别,在实际的编程中...
  • 多线程的作用以及什么地方用到多线程?

    万次阅读 多人点赞 2016-07-31 13:09:12
    多线程的作用以及什么地方用到多线程?
  • 进程和线程的区别(超详细)

    万次阅读 多人点赞 2019-10-03 21:57:46
    进程和线程 进程 一个在内存中运行的应用...一个进程至少有一个线程,一个进程可以运行线程线程可共享数据。 与进程不同的是同类的线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟...
  • 秒杀多线程第一篇 多线程笔试面试题汇总

    万次阅读 多人点赞 2012-04-05 09:35:49
    系列中不但会详细讲解多线程同步互斥的各种“招式”,而且会进一步的讲解多线程同步互斥的“内功心法”。有了“招式”和“内功心法”,相信你也能对多线程挥洒自如,在笔试面试中顺利的秒杀多线程试题。 ----------...
  • 秒杀多线程第六篇 经典线程同步 事件Event

    万次阅读 多人点赞 2012-04-11 09:06:57
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇 一个经典的多线程同步问题》《秒杀多线程第五篇 经典线程同步关键段CS》 上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性...
  • 我说我懂多线程,面试官立马给我发了offer

    万次阅读 多人点赞 2020-04-07 09:24:52
    不小心拿了几个offer,有点烦
  • 秒杀多线程第五篇 经典线程同步 关键段CS

    万次阅读 多人点赞 2012-04-11 09:06:40
    上一篇《秒杀多线程第四篇 一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题。本文首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现...
  • 什么是多线程?如何实现多线程

    万次阅读 多人点赞 2019-04-09 09:53:36
    【转】什么是线程安全?怎么实现线程安全?什么是进程?什么是线程?...电脑中时会有很单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑...
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • 多线程(一):初识多线程

    万次阅读 多人点赞 2016-03-06 16:24:11
    在这次研究过程中自己也对多线程以及相关的知识进行了深入的探索,总的来说还是蛮不错的一次探索之旅吧! 【前世今缘】 说起我和多线程的渊源,还得追溯到两年前的考试维护,记得当时自己还是一个小兵,看...
  • 多线程之线程通信

    万次阅读 2007-05-16 15:37:00
     线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。 线程互斥是指对于共享的操作系统资源(指的是广义的"资源",...
  • Java多线程之初识线程

    万次阅读 2020-09-10 23:49:26
    文章目录实现多线程的两种方式区别继承Thread示例实现Runnable接口示例start()的执行步骤 实现多线程的两种方式 1、继承Thread类; 2、实现Runnable接口。 区别 Java语言是单继承的,使用实现Runnable方式创建线程,...
  • 【2】多线程线程安全

    万次阅读 2019-11-22 20:58:01
    目录 知识点1:什么是线程安全? 1、为什么有线程安全问题? 知识点2:线程安全解决办法 ...知识点3:多线程死锁 1、什么是多线程死锁? 知识点4:Threadlocal 1、什么是Threadlocal 2、ThreadL...
  • 多线程

    万次阅读 2018-02-28 16:55:11
    Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。 其中前两种方式线程...
  • 秒杀多线程第八篇 经典线程同步 信号量Semaphore

    万次阅读 多人点赞 2012-05-03 09:30:00
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇一个经典的多线程同步问题》《秒杀多线程第五篇经典线程同步关键段CS》《秒杀多线程第六篇经典线程同步事件Event》《秒杀多线程第七篇经典线程同步互斥量Mutex》...
  • Linux系统编程——多线程实现多任务

    万次阅读 2015-06-10 18:00:27
    每个进程都拥有自己的数据段、代码段和堆栈段,这就造成进程在进行创建、切换、撤销操作时,需要较大的系统开销。为了减少系统开销,从进程中演化出了线程。...更详情,请看《进程和线程的区别与联系》。
  • C++ 多线程 API 简介

    万次阅读 2020-10-04 20:31:59
    本文主要介绍了一些C++中的多线程 API 的使用和原理,以及多线程类的封装,包括 线程的创建、挂起、恢复、销毁、使用等等
  • 秒杀多线程第七篇 经典线程同步 互斥量Mutex

    万次阅读 多人点赞 2012-04-18 09:26:51
    阅读本篇之前推荐阅读以下姊妹篇:《秒杀多线程第四篇一个经典的多线程同步问题》《秒杀多线程第五篇经典线程同步关键段CS》《秒杀多线程第六篇经典线程同步事件Event》 前面介绍了关键段CS、事件Event在经典线程...
  • 单线程与多线程的区别

    万次阅读 2017-11-27 11:03:18
    什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括...而一个进程又是由线程所组成的。 什么是线程线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区
  • 秒杀多线程第四篇 一个经典的多线程同步问题

    万次阅读 多人点赞 2012-04-10 09:57:02
    上一篇《秒杀多线程第三篇原子操作 Interlocked系列函数》中介绍了原子操作在多进程中的作用,现在来个复杂点的。这个问题涉及到线程的同步和互斥,是一道非常有代表性的多线程同步问题,如果能将这个问题搞清楚,...
  • 线程安全:就是当多线程访问时,采用了加锁的机制;即当一个线程访问该类的某个数据时,会对这个数据进行保护,其他线程不能对其访问,直到该线程读取完之后,其他线程才可以使用。防止出现数据不一致或者数据被污染...
  • 优先级调度决定了线程按照什么顺序轮流执行,在具有优先级调度的系统中,线程拥有各自的线程优先级(Thread Priority)。具有高优先级的线程会更早地执行,而低优先级的线程通常要等没有更高优先级的可执行线程时才会...
  • 线程是什么?要理解这个概念,须要先了解一下操作系统的一些相关概念。大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,...
  • Java多线程——基本概念

    万次阅读 多人点赞 2019-10-23 10:36:25
    线程和多线程 程序:是一段静态的代码,是应用软件执行的蓝本 进程:是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程 线程:是比...

空空如也

1 2 3 4 5 ... 20
收藏数 825,056
精华内容 330,022
关键字:

线程