精华内容
下载资源
问答
  • 多线程面试题(值得收藏)

    万次阅读 多人点赞 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异常。

    展开全文
  • Java多线程详解(一)Java多线程入门

    千次阅读 多人点赞 2019-11-27 20:18:20
    最近听很多面试的小伙伴说,网上往往是一篇一篇的Java多线程的文章,除了书籍没有什么学习多线程的一系列文章。但是仅仅凭借一两篇文章很难对多线程有系统的学习,而且面试的时候多线程这方面的知识往往也是考察的...

    最近听很多面试的小伙伴说,网上往往是一篇一篇的Java多线程的文章,除了书籍没有什么学习多线程的一系列文章。但是仅仅凭借一两篇文章很难对多线程有系统的学习,而且面试的时候多线程这方面的知识往往也是考察的重点,所以考虑之下决定写一系列关于Java多线程的文章。文章参考了高老师的《Java多线程编程核心技术》。力争使用最短的篇幅把Java多线程的知识作以系统的讲述。

    本节思维导图:

    思维导图源文件+思维导图软件关注微信公众号:“Java团长”回复关键字:“Java多线程”即可免费领取。

     

    一 进程和多线程简介

    1.1 相关概念

    何为线程?

    线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程

    何为进程?

    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。

    线程和进程有何不同?

    线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

    1.2 多线程

    何为多线程?

    多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。

    为什么多线程是必要的?

    1. 使用线程可以把占据长时间的程序中的任务放到后台去处理
    2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
    3. 程序的运行速度可能加快

    二 使用多线程

    2.1继承Thread类

    MyThread.java

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		System.out.println("MyThread");
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		MyThread mythread = new MyThread();
    		mythread.start();
    		System.out.println("运行结束");
    	}
    
    }

    运行结果:

    结果

    从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法

     

    2.2实现Runnable接口

    推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口

    MyRunnable.java

    public class MyRunnable implements Runnable {
    	@Override
    	public void run() {
    		System.out.println("MyRunnable");
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		Runnable runnable=new MyRunnable();
    		Thread thread=new Thread(runnable);
    		thread.start();
    		System.out.println("运行结束!");
    	}
    
    }

    运行结果:

    运行结果

     

    三 实例变量和线程安全

    定义线程类中的实例变量针对其他线程可以有共享和不共享之分

    3.1 不共享数据的情况

    MyThread.java

    public class MyThread extends Thread {
    
    	private int count = 5;
    
    	public MyThread(String name) {
    		super();
    		this.setName(name);
    	}
    
    	@Override
    	public void run() {
    		super.run();
    		while (count > 0) {
    			count--;
    			System.out.println("由 " + MyThread.currentThread().getName()
    					+ " 计算,count=" + count);
    		}
    	}
    }

    Run.java

    public class Run {
    	public static void main(String[] args) {
    		MyThread a = new MyThread("A");
    		MyThread b = new MyThread("B");
    		MyThread c = new MyThread("C");
    		a.start();
    		b.start();
    		c.start();
    	}
    }

    运行结果:

    运行结果

    可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另一种情况。

     

    3.2 共享数据的情况

    MyThread.java

    public class MyThread extends Thread {
    
    	private int count = 5;
    
    	@Override
    	 public void run() {
    		super.run();
    		count--;
    		System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count);
    	}
    }

    Run.java

    public class Run {
    	public static void main(String[] args) {
    		
    		MyThread mythread=new MyThread();
            //下列线程都是通过mythread对象创建的
    		Thread a=new Thread(mythread,"A");
    		Thread b=new Thread(mythread,"B");
    		Thread c=new Thread(mythread,"C");
    		Thread d=new Thread(mythread,"D");
    		Thread e=new Thread(mythread,"E");
    		a.start();
    		b.start();
    		c.start();
    		d.start();
    		e.start();
    	}
    }

    运行结果:

    运行结果

    可以看出这里已经出现了错误,我们想要的是依次递减的结果。为什么呢??

    因为在大多数jvm中,count--的操作分为如下下三步:

    1. 取得原有count值
    2. 计算i -1
    3. 对i进行赋值

    所以多个线程同时访问时出现问题就是难以避免的了。

    那么有没有什么解决办法呢?

    答案是:当然有,而且很简单。

    在run方法前加上synchronized关键字即可得到正确答案

    加上关键字后的运行结果:

    加上关键字后的运行结果

    四 一些常用方法

    4.1 currentThread()

    返回对当前正在执行的线程对象的引用。

    4.2 getId()

    返回此线程的标识符

    4.3 getName()

    返回此线程的名称

    4.4 getPriority()

    返回此线程的优先级

    4.5 isAlive()

    测试这个线程是否还处于活动状态。

    什么是活动状态呢?

    活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。

    4.6 sleep(long millis)

    使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

    4.7 interrupt()

    中断这个线程。

    4.8 interrupted() 和isInterrupted()

    interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能

    isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志

    4.9 setName(String name)

    将此线程的名称更改为等于参数 name 。

    4.10 isDaemon()

    测试这个线程是否是守护线程。

    4.11 setDaemon(boolean on)

    将此线程标记为 daemon线程或用户线程。

    4.12 join()

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了

    join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行

    4.13 yield()

    yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。

    4.14 setPriority(int newPriority)

    更改此线程的优先级

    五 如何停止一个线程呢?

    stop(),suspend(),resume()(仅用于与suspend()一起使用)这些方法已被弃用,所以我这里不予讲解。

     

    5.1 使用interrupt()方法

    我们上面提到了interrupt()方法,先来试一下interrupt()方法能不能停止线程 MyThread.java

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 5000000; i++) {
    			System.out.println("i=" + (i + 1));
    		}
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    	}
    
    }

    运行上诉代码你会发现,线程并不会终止

    针对上面代码的一个改进:

    interrupted()方法判断线程是否停止,如果是停止状态则break

    MyThread.java

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 500000; i++) {
    			if (this.interrupted()) {
    				System.out.println("已经是停止状态了!我要退出了!");
    				break;
    			}
    			System.out.println("i=" + (i + 1));
    		}
    		System.out.println("看到这句话说明线程并未终止------");
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    		System.out.println("end!");
    	}
    
    }

    运行结果:

    运行结果

    for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。

     

    5.2 使用return停止线程

    MyThread.java

    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    			while (true) {
    				if (this.isInterrupted()) {
    					System.out.println("ֹͣ停止了!");
    					return;
    				}
    				System.out.println("timer=" + System.currentTimeMillis());
    			}
    	}
    
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) throws InterruptedException {
    		MyThread t=new MyThread();
    		t.start();
    		Thread.sleep(2000);
    		t.interrupt();
    	}
    
    }

    运行结果:

    运行结果

    当然还有其他停止线程的方法,后面再做介绍。

    六 线程的优先级

    每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。

    线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。

    线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。

    Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1)Thread.NORM_PRIORITY(常数5), Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)

    学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。

    线程优先级具有继承特性测试代码:

    MyThread1.java:

    public class MyThread1 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread1 run priority=" + this.getPriority());
    		MyThread2 thread2 = new MyThread2();
    		thread2.start();
    	}
    }

    MyThread2.java:

    public class MyThread2 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread2 run priority=" + this.getPriority());
    	}
    }

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		System.out.println("main thread begin priority="
    				+ Thread.currentThread().getPriority());
    		Thread.currentThread().setPriority(6);
    		System.out.println("main thread end   priority="
    				+ Thread.currentThread().getPriority());
    		MyThread1 thread1 = new MyThread1();
    		thread1.start();
    	}
    }

    运行结果:

    运行结果

     

    七 Java多线程分类

    7.1 多线程分类

    用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

    守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”

    特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

    应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程

    最常见的守护线程:垃圾回收线程

    7.2 如何设置守护线程?

    可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程

    注意事项:

    1.  setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
    2. 在守护线程中产生的新线程也是守护线程
    3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑

    MyThread.java:

    public class MyThread extends Thread {
    	private int i = 0;
    
    	@Override
    	public void run() {
    		try {
    			while (true) {
    				i++;
    				System.out.println("i=" + (i));
    				Thread.sleep(100);
    			}
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.setDaemon(true);
    			thread.start();
    			Thread.sleep(5000);
    			System.out.println("我离开thread对象也不再打印了,也就是停止了!");
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }

    运行结果:

    守护线程

     

    如果你觉得博主的文章不错,欢迎转发点赞。

    参考:

    展开全文
  • JAVA线程异常终止

    千次阅读 2017-07-06 09:06:28
    设置当线程由于未捕获的异常突然终止而调用的默认处理程序,并且没有为该线程定义其他处理程序。 我们开发工程中经常使用到线程,在线程使用上,我们可能会有这样的场景: 伴随这一个业务产生一个比较耗时的任务,...
    static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 
    设置当线程由于未捕获的异常突然终止而调用的默认处理程序,并且没有为该线程定义其他处理程序。  
    

    我们开发工程中经常使用到线程,在线程使用上,我们可能会有这样的场景:

    1. 伴随这一个业务产生一个比较耗时的任务,而这个业务返回并不需要等待该任务。那我们往往会启动一个线程去完成这个异步任务。

    2. 我们需要一个定时任务比如:定时清除数据,我们会起一个定时执行线程去做该任务。
        

    上述问题比较简单,new一个线程然后去做这件事。但是我们常常忽略一个问题,线程异常了怎么办?
    比如耗时任务我们只完成了一半,我们就异常结束了(这里不考虑事务一致性,我们只考虑一定要将任务完成)。又比如在清数据的时候,数据库发生断连。这时候我们会发现线程死掉了,任务终止了,我们需要重启整个项目把该定时任务起起来。

    场景一解决思路:

    package cn.merryyou.thread;
    
    /**
     * Created by 11 on 2017/7/5.
     */
    public class Plan {
    
        private SimpleTask task = new SimpleTask();
        public static void main(String[] args) {
            Plan plan = new Plan();
            plan.start();
        }
    
        public void start(){
            Thread thread = new Thread(task);
            thread.setUncaughtExceptionHandler((t, e) -> {
                System.out.println(e.getMessage());
                start();
            });
            thread.start();
        }
    
        static class SimpleTask implements Runnable{
    
            private int task = 10;
    
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName+"--"+"启动");
                while(task>0){
                    try {
                        Thread.sleep(100);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    if(System.currentTimeMillis()%3==0){
                        throw new RuntimeException("模拟异常");
                    }
                    System.out.println(threadName+"--"+"执行task"+task);
                    task--;
                }
                System.out.println(threadName+"--"+"正常终止");
            }
        }
    }
    

    输出:

    Thread-0--启动
    Thread-0--执行task10
    Thread-0--执行task9
    Thread-0--执行task8
    模拟异常
    Thread-1--启动
    Thread-1--执行task7
    模拟异常
    Thread-2--启动
    模拟异常
    Thread-3--启动
    Thread-3--执行task6
    模拟异常
    Thread-4--启动
    Thread-4--执行task5
    模拟异常
    Thread-5--启动
    Thread-5--执行task4
    模拟异常
    Thread-6--启动
    Thread-6--执行task3
    模拟异常
    Thread-7--启动
    Thread-7--执行task2
    Thread-7--执行task1
    Thread-7--正常终止
    

    线程池实现的方式:

    package cn.merryyou.thread;
    
    import sun.java2d.pipe.SpanShapeRenderer;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    /**
     * Created by 11 on 2017/7/5.
     */
    public class Plan2 {
        private Plan.SimpleTask task = new Plan.SimpleTask();
        private MyFactory factory = new MyFactory(task);
        public static void main(String[] args) {
            Plan2 plan2 = new Plan2();
            ExecutorService pool = Executors.newSingleThreadExecutor(plan2.factory);
            pool.execute(plan2.task);
            pool.shutdown();
        }
    
        class MyFactory implements ThreadFactory {
    
            private Plan.SimpleTask task;
    
            public MyFactory(Plan.SimpleTask task) {
                super();
                this.task = task;
            }
    
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setUncaughtExceptionHandler((t, e) -> {
                    ExecutorService pool = Executors.newSingleThreadExecutor(new MyFactory(task));
                    pool.execute(task);
                    pool.shutdown();
                });
                return thread;
            }
        }
    }
    

    输出:

    Thread-0--启动
    Thread-0--执行task10
    Thread-1--启动
    Thread-1--执行task9
    Thread-1--执行task8
    Thread-1--执行task7
    Thread-2--启动
    Thread-2--执行task6
    Thread-3--启动
    Thread-3--执行task5
    Thread-3--执行task4
    Thread-3--执行task3
    Thread-3--执行task2
    Thread-3--执行task1
    Thread-3--正常终止
    

    定时任务ScheduledExecutorService 实现方式:

    package cn.merryyou.thread;
    
    import java.util.concurrent.*;
    
    /**
     * Created by 11 on 2017/7/5.
     */
    public class Plan3 {
        private SimpleTask task = new SimpleTask();
        public static void main(String[] args) {
            Plan3 plan = new Plan3();
            start(plan.task);
        }
    
        public static void start(SimpleTask task){
            ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
            ScheduledFuture<?> future = pool.scheduleAtFixedRate(task, 0, 1000, TimeUnit.MILLISECONDS);
            try {
                future.get();
            }catch (InterruptedException | ExecutionException e){
                System.out.println(e.getMessage());
                start(task);
            }finally {
                pool.shutdown();
            }
    
        }
    
        class SimpleTask implements Runnable{
            private volatile int count = 0;
    
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName+"--启动");
                try{
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                if(System.currentTimeMillis()%3==0){
                    throw new RuntimeException("模拟异常");
                }
                System.out.println(threadName+"--执行task"+count);
                count++;
                System.out.println(threadName+"--正常终止");
            }
        }
    }
    

    输出:

    pool-1-thread-1--启动
    pool-1-thread-1--执行task0
    pool-1-thread-1--正常终止
    pool-1-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-2-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-3-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-4-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-5-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-6-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-7-thread-1--启动
    java.lang.RuntimeException: 模拟异常
    pool-8-thread-1--启动
    pool-8-thread-1--执行task1
    pool-8-thread-1--正常终止
    pool-8-thread-1--启动
    pool-8-thread-1--执行task2
    pool-8-thread-1--正常终止
    pool-8-thread-1--启动
    ......
    

    参考链接:http://www.cnblogs.com/yuhuihong19941210/p/5547501.html

    展开全文
  • Java多线程

    千次阅读 多人点赞 2019-05-04 22:28:49
    一、多线程的概念 ...多线程:一个进程运行产生了多个线程。 1.1 进程与线程的区别 进程是资源分配的最小单位,线程是程序执行的最小单位。没有进程就没有线程,进程一旦终止,其内的线程也将...

    一、多线程的概念

    想要知道什么是多线程?就会引出线程的概念,而线程和进程之间又是息息相关的。

    进程:操作系统中一个程序的执行周期称为一个进程。

    线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。

    多线程:一个进程运行时产生了多个线程。

     1.1 进程与线程的区别

    • 进程是资源分配的最小单位,线程是程序执行的最小单位。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。

    • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段。
      线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

    • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。

    • 多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

    1.2 为什么要使用多线程?

    1)更好的利用cpu资源

    2)线程之间可以共享数据

    3)创建线程代价比较小

    二、线程的生命周期

    在线程的生命周期中,共有五种状态 :  新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

    当一个线程启动以后,它不能一直霸占着cpu独自运行,CPU需要在多个线程之间切换,于是每个线程就会在各种状态之间转换


    1. 新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

    2. 就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机为其创建方法调用栈和程序计数器,等待调度运行

    3. 运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

    4. 阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态

    5.死亡状态:当线程执行完了或者因异常退出了run()的执行,该线程的生命周期就结束了

    线程状态转换图:

    三、实现多线程的方法 

    Java实现多线程一共有三种方法,分别为:继承Thread类、实现Runnable接口、实现Callable接口

    注:想要实现多线程,必须实现Runnable接口,Thread类和Collable接口都间接实现了Runnable接口

    3.1 继承Thread类实现多线程

    新建一个线程最简单的方法就是直接继承Thread类,覆写该类中的run()方法,然后调用线程的start()方法

    //1.继承Thread类
     class MyThread extends Thread{
        private String title;
        public MyThread(String title){
            this.title=title;
        }
    //2.覆写run()方法
        @Override
        public void run(){  //所有线程从此处开始执行
      for(int i=0;i<10;i++){
          System.out.println(this.title+" i="+i);
      }
        }
    }
    public class Test{
        public static void main(String[] args) {
            MyThread myThread1=new MyThread("thread1");
            MyThread myThread2=new MyThread("thread2");
            MyThread myThread3=new MyThread("thread3");
            //3.调用线程的start()方法
            myThread1.start();
            myThread2.start();
            myThread3.start();
        }
         }

    注意:任务中的run()方法指明如何完成这个任务。Java虚拟机会自动调用该方法,无需特意调用它,直接调用run()方法只是在同一个线程中执行该方法,而没有新线程被启动。因此必须通过start()方法来启动线程。

    3.2 Runnable接口实现多线程

    实现多线程最简单的方法就是继承Thread类,但是该方法有单继承局限;使用Runable接口可以解决这一问题。

    范例:利用Runable接口实现线程主体类

     //1.定义类实现Runnable接口
     class MyThread implements Runnable{
        private String title;
        public MyThread(String title){
            this.title=title;
        }
        @Override
         //2.覆写Runnable接口中的run()方法
         //将线程要运行的代码放在该run方法中
        public void run(){  //所有线程从此处开始执行
      for(int i=0;i<10;i++){
          System.out.println(this.title+" i="+i);
      }
        }
    }
    public class Test{
        public static void main(String[] args) {
            MyThread myThread1=new MyThread("thread1");
            MyThread myThread2=new MyThread("thread2");
            MyThread myThread3=new MyThread("thread3");
            //3.通过Thread类建立线程对象
            //4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
            //5.调用Thread类的start方法开启线程
            new Thread(myThread1).start();
            new Thread(myThread2).start();
            new Thread(myThread3).start();
        }
         }

    注意:使用Runnable接口实现多线程时,由于没有继承Thread类,不能直接调用start()方法,此时需要将Runnable接口的子类对象作为参数传递给Thread类的构造函数(该构造函数的参数就是Runnable对象),最后调用Thread类的start()方法,开启线程。


    另外,对于此时的Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义

    范例1:使用匿名内部类进行Runnable对象创建

    public class Test{
        public static void main(String[] args) {
            //新建Thread类、Runnable类的匿名对象
          new Thread(      //匿名内部类可以实例化接口
                  new Runnable(){  //Runnable类的匿名对象作为Thread类构造方法的参数
                      @Override
              public void run(){   //Runnable类是一个接口,该类有一个抽象方法run(),
                          System.out.println("hello word");
                      }
                  }
          ).start();
        }
         }

    范例2:使用Lamdba表达式进行Runnable对象创建

    public class Test{
        public static void main(String[] args) {
            Runnable runnable =()->System.out.println("hello word");//lamdba表达式
            new Thread(runnable).start();
        }
         }

    3.3 Callable接口实现多线程

    Thread类和Runnable接口中的run()方法都没有返回值,但是在某些情况下,线程执行完成后需要带回一些返回值,这时,就需要利用Callable接口来实现多线程。

    范例:使用Callable接口实现多线程

    //1.定义类实现Callable接口
    class MyThread implements Callable<String> {
        private int ticket=10;//10张票
        @Override
        public String call()throws Exception {  //2.实现Callable接口的call()方法,该方法有返回值
            while (this.ticket > 0) {
                System.out.println("剩余票数:" + this.ticket--);
            }
            return("票卖完啦~~");
        }
    }
    public class Test {
        public static void main(String[] args) throws InterruptedException,ExecutionException {
       
    //3.使用FutureTask类来包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
          
      FutureTask<String> task=new FutureTask<>(new MyThread());
           
     //4.使用FutureTask对象作为Thread对象启动线程
         
     new Thread(task).start();    
     new Thread(task).start();
         
     //5.调用FutureTask对象的get()方法获取子线程执行后的返回值
        
      System.out.println(task.get());
        }
    }

    3.4 线程类继承结构

    线程类继承结构图:

    (1)根据以上继承结构图可知:Thread类是Runnable接口的子类,覆写了Runnable接口的run()方法

    (2) 在多线程的处理上,使用的是代理设计模式:Runnable接口定义了相关协议,Thread类是代理类,MyThread类则是目标实现类。

    3.5 三种实现多线程方法对比 

    区别局限性共享性返回值
    Thread类单继承/无返回值
    Runnable接口/多个线程共享一个target对象无返回值
    Callable接口/多个线程共享一个target对象有返回值

    范例1:使用Thread类实现数据共享

    class MyThread extends Thread{
        private int ticket=10;
        @Override
        public void run(){
            while (this.ticket>0){
                System.out.println("剩余票数"+this.ticket--);
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            new MyThread().start();
            new MyThread().start();
           
        }
    }
    
    结果:
    剩余票数10
    剩余票数10
    剩余票数9
    剩余票数8
    剩余票数7
    剩余票数6
    剩余票数5
    剩余票数9
    剩余票数4
    剩余票数8
    剩余票数3
    剩余票数2
    剩余票数1
    剩余票数7
    剩余票数6
    剩余票数5
    剩余票数4
    剩余票数3
    剩余票数2
    剩余票数1
    

    此时,启动三个线程实现买票处理,结果为三个线程各自卖自己的票,说明,Thread类实现的多线程不能共享同一个target对象

    范例2:使用Runnable接口实现数据共享

    class MyThread implements Runnable{
        private int ticket=10;
        @Override
        public void run(){
            while (this.ticket>0){
                System.out.println("剩余票数"+this.ticket--);
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
           MyThread mythread=new MyThread();
           new Thread(mythread).start();
           new Thread(mythread).start();
           new Thread(mythread).start();
        }
    }
    
    结果:
    剩余票数10
    剩余票数8
    剩余票数7
    剩余票数6
    剩余票数5
    剩余票数4
    剩余票数3
    剩余票数1
    剩余票数9
    剩余票数2

    Runnable实现的多线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    那么,为什么Runnable可以共享同一个target对象呢?

    四、多线程的常用操作方法

     4.1 线程命名与取得

    多线程的运行状态是不确定的,所以对于多线程操作必须有一个明确标识出线程对象的信息,这个信息往往通过名称来描述。

    在Thread类中有以下几种关于线程名称的方法:

    方法名称类型描述
    public Thread(Runnable target, String name)构造创建线程的时候设置名称
    public final synchronized void setName(String name)普通设置线程名称
    public final String getName()普通取得线程名称

    取得当前线程对象的方法:

    public static native Thread currentThread();

    范例:线程命名与取得

    class MyThread implements Runnable{
        private int ticket=10;
        @Override
        public void run(){
           for(int i=0;i<10;i++){
                System.out.println("当前线程"+Thread.currentThread().getName()+" i="+i);
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            MyThread mythread=new MyThread();
            new Thread(mythread).start();
            new Thread(mythread,"rachel").start();
        }
    }

     注意:如果线程没有命名,则会自动分配一个名称,另外,设置名称不要重复,中间不要修改

    范例2:线程执行结果

    class MyThread implements Runnable{
        @Override
        public void run(){
                System.out.println("当前线程"+Thread.currentThread().getName());
        }
    }
    public class Test {
        public static void main(String[] args) {
            MyThread mythread=new MyThread();
            mythread.run();//直接通过对象调用run()方法
            new Thread(mythread).start();//通过线程调用
        }
    }
    
    结果:
    当前线程main
    当前线程Thread-0

     通过以上程序可以发现:主方法本来就是一个线程,所有线程都是通过主方法来创建并启动的

    4.2 线程休眠——sleep()方法

    线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
    线程休眠会交出CPU,让CPU去执行其他的任务。但是sleep()方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

    方法定义: 

    public static native void sleep(long millis) throws InterruptedException;

    4.3 线程让步——yield()方法

    线程让步:暂停当前正在执行的线程对象,并执行其他线程。
    1)调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
    2)调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的

    方法定义:

    public static native void yield();

    4.4 等待线程终止——join()方法

    等待该线程终止:如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程

    class MyThread implements Runnable{
        @Override
        public void run() {
            try {
                System.out.println("主线程睡眠前的时间");
                Test.printTime();//线程睡眠开始时间
                Thread.sleep(2000);//线程休眠2秒钟
                System.out.println(Thread.currentThread().getName());//此时获取到的是子线程A的名称
                System.out.println("睡眠结束的时间");
                Test.printTime();//线程睡眠结束时间
            } catch (InterruptedException e) {
    // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public class Test {
        public static void main(String[] args) throws InterruptedException{
            MyThread mt = new MyThread();
            Thread thread = new Thread(mt,"子线程A");
            thread.start();//子线程A启动,执行run方法内的操作
            System.out.println(Thread.currentThread().getName());//此时获得的是主方法线程的名称
            thread.join();//主线程休眠,等待子线程A执行完run方法之后继续运行
            System.out.println("代码结束");
        }
        //打印当前时间
        public static void printTime() {
            Date date=new Date();
            DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time=format.format(date);
            System.out.println(time);
        }
    }
    
    结果:
    main
    主线程睡眠前的时间
    2019-05-06 13:10:56
    子线程A
    睡眠结束的时间
    2019-05-06 13:10:58
    代码结束
    

    4.5 停止线程——stop()方法

    多线程中有三种方式可以停止线程。
    1. 设置标记位,可以使线程正常退出。
    2. 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
    3. 使用Thread类中的一个 interrupt() 可以中断线程。

    范例1:设置标记位使线程停止

    class MyThread implements Runnable {
        private boolean flag = true; //设置标志位为true
        @Override
        public void run() {
            int i = 1;
            while (flag) {
                try {
                    Thread.sleep(1000);
                    System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                    i++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();
            Thread thread1 = new Thread(myThread, "子线程A");
            thread1.start();
            Thread.sleep(2000);  //主方法休眠
            myThread.setFlag(false);//设置标志位为false
            System.out.println("代码结束");
        }
    }

    范例2:使用stop()方法使线程停止

    class MyThread implements Runnable {
        @Override
        public void run() {
            int i = 1;
                try {
                    Thread.sleep(1000);
                    System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                    i++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();
            Thread thread1 = new Thread(myThread,"子线程A");
            thread1.start();
            Thread.sleep(3000);
            thread1.stop();
            System.out.println("代码结束");
        }
    }

    注:使用stop()方法会使线程立即停止,它是一种不安全的操作,某些情况下,会造成数据残留,不同步。

    范例3:使用interrupt()方法使线程停止

    class MyThread implements Runnable {
        private boolean flag = true;
        @Override
        public void run() {
            int i = 1;
            while (flag) {
                try {
    /**
     * 这里阻塞之后,线程被调用了interrupte()方法,
     * 清除中断标志,就会抛出一个异常
     * java.lang.InterruptedException
     */
                    Thread.sleep(1000);
                    boolean bool = Thread.currentThread().isInterrupted();
                    if (bool) {
                        System.out.println("非阻塞情况下执行该操作。。。线程状态" + bool);
                        break;
                    }
                    System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                    i++;
                } catch (InterruptedException e) {
                    System.out.println("退出了");
    /**
     * 这里退出阻塞状态,且中断标志被系统会自动清除,
     * 并且重新设置为false,所以此处bool为false
     */
                    boolean bool = Thread.currentThread().isInterrupted();
                    System.out.println(bool);
    //退出run方法,中断进程
                    return;
                }
            }
        }
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    }
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            MyThread myThread = new MyThread();
            Thread thread1 = new Thread(myThread,"子线程A");
            thread1.start();
            Thread.sleep(3000);
            thread1.interrupt();
            System.out.println("代码结束");
        }

    调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。

    4.6 线程优先级

    线程的优先级指的是,线程的优先级越高越有可能先执行,但仅仅是有可能而已

    Thread类中关于线程优先级的相关方法:

    方法描述
    public final void setPriority(int newPriority)设置优先级
    public final int getPriority()取得优先级

    对于优先级设置的内容可以通过Thread类的几个常量来决定

    1. 最高优先级:public final static int MAX_PRIORITY = 10;
    2. 中等优先级:public final static int NORM_PRIORITY = 5;
    3. 最低优先级:public final static int MIN_PRIORITY = 1;

    范例:设置优先级

    class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5 ; i++) {
                System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = "
                        +i);
            }
        }
    }
    public class Test {
        public static void main(String[] args)  {
            MyThread mt = new MyThread();
            Thread t1 = new Thread(mt,"1") ;
            Thread t2 = new Thread(mt,"2") ;
            Thread t3 = new Thread(mt,"3") ;
            t1.setPriority(Thread.MIN_PRIORITY);
            t2.setPriority(Thread.MIN_PRIORITY);
            t3.setPriority(Thread.MAX_PRIORITY);
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    1)主方法只是中等优先级

    2)线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的

    4.7守护线程

    守护线程是一种特殊的线程,它属于是一种陪伴线程。

    Java 中有两种线程:用户线程和守护线程。可以通isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

    典型的守护线程就是垃圾回收线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随JVM一同停止工作。

    主线程是用户线程

    范例:守护线程

    class A implements Runnable {
        private int i;
        @Override
        public void run() {
            try {
                while (true) {
                    i++;
           System.out.println("线程名称:" + Thread.currentThread().getName() + ",i=" + i + ",是否为守护线程:"
                            + Thread.currentThread().isDaemon());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程了");
            }
        }
    
    }
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(new A(),"子线程A");
            thread1.setDaemon(true); // 设置线程A为守护线程,此语句必须在start方法之前执行
            thread1.start();
            Thread thread2 = new Thread(new A(),"子线程B");
            thread2.start();
            Thread.sleep(3000);//主线程休眠
            thread2.interrupt();// 中断非守护线程
            Thread.sleep(10000);//主线程休眠
            System.out.println("代码结束");
        }
        }

    注:B是用户线程当它中断了之后守护线程还没有结束,是因为主线程(用户线程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。

    展开全文
  • 前面在pyqt5多线程(QThread)遇到的坑(一)中提到了先实例化类,再把实例对象传参到线程类,这样的确实可行,但是还是遇到了新坑。 pyqt5多线程(QThread)遇到的坑(一 起因 被实例化的类是做数据处理的,传入和...
  • 多线程终止方法(停止线程)

    千次阅读 2019-07-08 23:30:07
    异常终止 暴力终止 停止线程 停止线程是多线程中的一个重要技术点,本篇文章将对线程的停止操作进行详细讲解。 停止线程不能像for或while循环中使用break停止那样直接,而是需要一些技巧来终止。 如何判断...
  • Java多线程探究-线程异常逃逸

    千次阅读 2017-04-12 14:45:24
    在Thread的run方法中,Java是不允许抛出受检异常的,所以必须由自己捕获,但是对于一些运行异常,难免有时候完全捕获到,继而传递到上一层,导致不可预料的程序终止,所以需要在上一层捕获 来看看我们到底不能...
  • 昨天开始看《Java多线程编程核心技术》一书。记录一些所学:1.通过查看“Windows资源管理器”中的列表,完全可以将运行在内存中的.exe文件理解成一个线程,线程是受操作系统管理的基本运行单元。2.非线程安全:指多...
  • Python异常处理和多线程

    千次阅读 2017-06-30 09:38:46
    由于每个进程只要干一件事,所以,一个进程只要有一个线程,当然,想 Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多线程是一样的,也是由操作系统在多个线程之间快速切换,让每个...
  • 多线程中一个线程抛出异常(不捕获);主线程及其他子线程如何表现 结论: 语言 主线程 子线程 C++ 挂死 挂死 Java 继续运行 继续运行 C++ code #include &amp;amp;lt;iostream&amp;amp;gt; #...
  • C#多线程编程详解

    2021-01-20 06:41:16
    C#提供了丰富的多线程...当所有前台线程关闭,所有的后台线程也会被直接终止,不会抛出异常。 3、挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行情况不可预知,所以使用挂起和唤醒容易发生死锁的
  • 当所有前台线程关闭,所有的后台线程也会被直接终止,不会抛出异常。 3、挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行情况不可预知,所以使用挂起和唤醒容易发生死锁的情况,在实际应用中...
  • Java多线程之线程停止的正确方式

    万次阅读 2018-08-17 15:06:08
    在开发中,经常会遇到需要停止一个...1、该方式是通过立即抛出ThreadDeath异常来达到停止线程的目的,而且此异常抛出可能发生在程序的任何一个地方,包括catch、finally等语句块中。 2、由于抛出ThreadDeatch异常...
  • 多线程异常如何抛出及处理***

    千次阅读 2020-12-04 14:44:40
    特别重要,因为如果程序中使用了多线程,那么这个异常处理不好的话,无法捕捉异常,可能会导致系统奔溃
  • 个人工作和学习中积累和总结的多线程笔记 包含异步 线程等待 线程异常 线程终止 线程安全 lock monitor mutex autoresetevent semephore readerwriterlock readerwriterlockslim
  • 【java并发编程】多线程异常处理

    千次阅读 2020-01-09 20:50:05
    多线程充分发挥了系统的性能,但是调用Thread.start()方法之后,如果线程有异常造成线程终止,主线程无法及时获取。 public static void main(String[] args) { Thread thread = new Thread(() -> { //todo...
  • QT多线程编程详解

    万次阅读 多人点赞 2019-04-24 22:08:20
    一、线程基础 1、GUI线程与工作线程 每个程序启动后拥有的第一个线程称为主线程,即GUI线程。QT中所有的组件类和几个相关的类只能工作在GUI线程,不能工作在次...二、QT多线程简介 QT通过三种形式提供了对线程...
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • C++11多线程异常

    千次阅读 2016-12-05 21:00:17
    一旦开始了线程,需要显示决定要等待线程函数完成或分离它自行完成。如果detach()线程不等待,你要确保通过...join()背后的含义有两层,一是等待子线程执行完毕,避免主线程先完成,从而导致子线程终止,二是join()会
  • python多线程异常处理

    千次阅读 2016-08-06 18:31:44
    python 多线程异常
  • 多线程核心(2):线程异常处理

    千次阅读 2020-01-12 19:28:07
    线程的未捕获异常UncaughtException应该如何处理?
  • 架构师:『试试使用多线程优化』 第二天 头发很多的程序员:『师父,我已经使用了多线程,为什么接口还变慢了?』 架构师:『去给我买杯咖啡,我写篇文章告诉你』 ……吭哧吭哧买咖啡去了 在实际工作中,错误...
  • Java多线程学习(吐血超详细总结)

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

    千次阅读 2015-08-28 15:41:59
    stop()函数终止线程就像是强行拔掉电源线关机一样,可能会带来未知风险,因此目前不再推荐使用这种方式。请忘记它吧~~ 2. 改变标志变量状态 通常我们会在线程中使用一个标志变量来控制线程的运行,如: public ...
  • Win32多线程程序设计--源代码

    热门讨论 2012-04-22 17:09:08
    OS/2、Windows NT、Windows 95这类"新一代PC操作系统"初上市,便一再强调其抢先式多任务(preemptive multitasking)的多线程 (multithreaded)环境。拜强势行销之赐,霎时间线头到处飞舞,高深的计算机术语在...
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程...
  • 使用退出标识,使得线程正常退出,即当run方法完成后进程终止。 private int tickeys=10; private boolean flag=true; @Override public void run() { while (flag){ try { buyTickey(); } catch ...
  • 狂神说多线程笔记整理

    千次阅读 多人点赞 2021-02-16 13:29:26
    狂神说多线程笔记整理 笔记总结来自狂神说Java多线程详解 目录狂神说多线程笔记整理一、线程简介1.多任务2.多线程3.程序.进程.线程4.Process与Thread5.核心概念二、线程实现1.线程创建(三种方法)1.1继承Thread类...
  • 多线程与高并发笔记

    千次阅读 多人点赞 2020-09-13 10:14:52
    1. 创建线程的三种方式 实现Runnable 重写run方法 继承Thread 重写run方法 线程池创建 Executors.newCachedThreadPool() 2. Thread线程操作方法 Thread.sleep([mills]) 当前线程睡眠指定mills毫秒 Thread....
  • 停止线程,也许我们首先会有一种错感,觉得使用Thread.stop()或者Thread.interrupt()与Thread.interrupted()等组合就能退出线程了。可是在实际运用过程中,这样真的可以达到安全退出线程... 不用说,直接简单粗暴...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 128,279
精华内容 51,311
关键字:

多线程异常时终止多线程