精华内容
下载资源
问答
  • 主要介绍了Java线程批量数据导入的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面小编和大家来一起学习下吧
  • 下面小编就为大家带来一篇Java实现监控多个线程状态的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • Java线程面试题

    千次阅读 2019-10-30 13:58:47
    Java线程面试题 在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程, 如何创建线程,用什么方式创建线程比较好(比如:继承thread类还是调用Runnable接口),然后逐渐问到并发问题像在...

    Java线程面试题

    在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程, 如何创建线程,用什么方式创建线程比较好(比如:继承thread类还是调用Runnable接口),然后逐渐问到并发问题像在Java并发编程的过程中遇到了什么挑战,Java内存模型,JDK1.5引入了哪些更高阶的并发工具,并发编程常用的设计模式,经典多线程问题如生产者消费者,哲学家就餐,读写器或者简单的有界缓冲区问题。仅仅知道线程的基本概念是远远不够的, 你必须知道如何处理死锁,竞态条件,内存冲突和线程安全等并发问题。掌握了这些技巧,你就可以轻松应对多线程和并发面试了。

    1) 什么是线程?
    线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支 持,它也是一个很好的卖点。

    2) 线程和进程有什么区别?
    线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。

    3) 如何在Java中实现线程?
    在语言层面有两种方式。java.lang.Thread 类的实例就是一个线程但是它需要调用java.lang.Runnable接口来执行,由于线程类本身就是调用的Runnable接口所以你可以继承 java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程。

    4) 用Runnable还是Thread?
    这个问题是上题的后续,大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使 用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好 了。

    6) Thread 类中的start() 和 run() 方法有什么区别?
    这个问题经常被问到,但还是能从此区分出面试者对Java线程模型的理解程度。start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。

    7) Java中Runnable和Callable有什么不同?
    Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

    8) Java中CyclicBarrier 和 CountDownLatch有什么不同?
    CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

    9) Java内存模型是什么?
    Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一 个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。比如,先行发生关系确保 了:
    线程内的代码能够按先后顺序执行,这被称为程序次序规则。
    对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
    前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
    一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
    一个线程的所有操作都会在线程终止之前,线程终止规则。
    一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
    可传递性
    我强烈建议大家阅读《Java并发编程实践》第十六章来加深对Java内存模型的理解。

    10) Java中的volatile 变量是什么?
    volatile是一个特殊的修饰符,只有成员变量才能使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作对其它线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生,就是上一题的volatile变量规则。

    11) 什么是线程安全?Vector是一个线程安全类吗?
    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

    12) Java中什么是竞态条件?举个例子说明。
    竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了, 那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。

    13) Java中如何停止一个线程?
    Java提供了很丰富的API但没有为停止线程提供API。JDK 1.0本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用volatile 布尔变量来退出run()方法的循环或者是取消任务来中断线程。

    14) 一个线程运行时发生异常会怎样?
    这是我在一次面试中遇到的一个很刁钻的Java面试题, 简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中 断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来 查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法 进行处理。

    15) 如何在两个线程间共享数据?
    你可以通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。这篇教程《Java线程间通信》(涉及到在两个线程间共享对象)用wait和notify方法实现了生产者消费者模型。

    16) Java中notify 和 notifyAll有什么区别?
    这又是一个刁钻的问题,因为多线程可以等待单监控锁,Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

    17) 为什么wait, notify 和 notifyAll这些方法不在thread类里面?
    这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在 Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通 过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁 就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

    18) 什么是ThreadLocal变量?
    ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被 彻底消除了。它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的,因 为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通 过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。线程局部变量的另一个不错的例子是 ThreadLocalRandom类,它在多线程环境中减少了创建代价高昂的Random对象的个数。

    19) 什么是FutureTask?
    在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完 成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包 装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

    20) Java中interrupted 和 isInterruptedd方法的区别?
    interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

    21) 为什么wait和notify方法要在同步块中调用?
    主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

    22) 为什么你应该在循环中检查等待条件?
    处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来 时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方 法效果更好的原因,你可以在Eclipse中创建模板调用wait和notify试一试。如果你想了解更多关于这个问题的内容,我推荐你阅读《Effective Java》这本书中的线程和同步章节。

    23) Java中的同步集合与并发集合有什么区别?
    同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在 多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分 区等现代技术提高了可扩展性。

    24) Java中堆和栈有什么不同?
    为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈 调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己 的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

    25) 什么是线程池?为什么要使用它?
    创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。

    26) 如何写代码来解决生产者消费者问题?
    在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比 较低级的办法是用wait和notify来解决这个问题,比较赞的办法是用Semaphore 或者 BlockingQueue来实现生产者消费者模型,这篇教程有实现它。

    27) 如何避免死锁?
    Java多线程中的死锁
    死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
    互斥条件:一个资源每次只能被一个进程使用。
    请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
    循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
    避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

    28) Java中活锁和死锁有什么区别?
    这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个 人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者 进程的状态可以改变但是却不能继续执行。

    29) 怎么检测一个线程是否拥有锁?
    我一直不知道我们竟然可以检测一个线程是否拥有锁,直到我参加了一次电话面试。在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

    30) 你如何在Java中获取线程堆栈?
    对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当你获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在 Windows你可以使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。你也可以用jstack这个工具来获取,它对线程id进行操作,你可以用jps这个工具找到id。

    31) JVM中哪个参数是用来控制线程的栈堆栈小的
    这个问题很简单, -Xss参数用来控制线程的堆栈大小。

    32) Java中synchronized 和 ReentrantLock 有什么不同?
    Java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁 时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。

    33) 有三个线程T1,T2,T3,怎么确保它们按顺序执行?
    在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

    34) Thread类中的yield方法有什么作用?
    Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。

    35) Java中ConcurrentHashMap的并发度是什么?
    ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。

    36) Java中Semaphore是什么?
    Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前 会阻塞每一个 acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采 取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。

    37)如果你提交任务时,线程池队列已满。会时发会生什么?
    这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。

    38) Java线程池中submit() 和 execute()方法有什么区别?
    两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中, 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线 程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

    39) 什么是阻塞式方法?
    阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是 指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。

    40) Swing是线程安全的吗?为什么?
    你可以很肯定的给出回答,Swing不是线程安全的,但是你应该解释这么回答的原因即便面试官没有问你为什么。当我们说swing不是线程安全的常 常提到它的组件,这些组件不能在多线程中进行修改,所有对GUI组件的更新都要在AWT线程中完成,而Swing提供了同步和异步两种回调方法来进行更 新。

    41) Java中invokeAndWait 和 invokeLater有什么区别?
    这两个方法是Swing API 提供给Java开发者用来从当前线程而不是事件派发线程更新GUI组件用的。InvokeAndWait()同步更新GUI组件,比如一个进度条,一旦进 度更新了,进度条也要做出相应改变。如果进度被多个线程跟踪,那么就调用invokeAndWait()方法请求事件派发线程对组件进行相应更新。而 invokeLater()方法是异步调用更新组件的。

    42) Swing API中那些方法是线程安全的?
    这个问题又提到了swing和线程安全,虽然组件不是线程安全的但是有一些方法是可以被多线程安全调用的,比如repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是线程安全的。

    43) 如何在Java中创建Immutable对象?
    这个问题看起来和多线程没什么关系, 但不变性有助于简化已经很复杂的并发程序。Immutable对象可以在没有同步的情况下共享,降低了对该对象进行并发访问时的同步化开销。可是Java 没有@Immutable这个注解符,要创建不可变类,要实现下面几个步骤:通过构造方法初始化所有成员、对变量不要提供setter方法、将所有的成员 声明为私有的,这样就不允许直接访问这些成员、在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。我的文章how to make an object Immutable in Java有详细的教程,看完你可以充满自信。

    44) Java中的ReadWriteLock是什么?
    一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程 持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读 锁。

    45) 多线程中的忙循环是什么?
    忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可 能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。

    46)volatile 变量和 atomic 变量有什么不同?
    这是个有趣的问题。首先,volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

    47) 如果同步块内的线程抛出异常会发生什么?
    这个问题坑了很多Java程序员,若你能想到锁是否释放这条线索来回答还有点希望答对。无论你的同步块是正常还是异常退出的,里面的线程都会释放锁,所以对比锁接口我更喜欢同步块,因为它不用我花费精力去释放锁,该功能可以在finally block里释放锁实现。

    48) 单例模式的双检锁是什么?
    这个问题在Java面试中经常被问到,但是面试官对回答此问题的满意度仅为50%。一半的人写不出双检锁还有一半的人说不出它的隐患和 Java1.5是如何对它修正的。它其实是一个用来创建线程安全的单例的老方法,当单例实例第一次被创建时它试图用单个锁进行性能优化,但是由于太过于复 杂在JDK1.4中它是失败的,我个人也不喜欢它。无论如何,即便你也不喜欢它但是还是要了解一下,因为它经常被问到。

    49) 如何在Java中创建线程安全的Singleton?
    这是上面那个问题的后续,如果你不喜欢双检锁而面试官问了创建Singleton类的替代方法,你可以利用JVM的类加载和静态变量初始化特征来创建Singleton实例,或者是利用枚举类型来创建Singleton,我很喜欢用这种方法。

    50) 写出3条你遵循的多线程最佳实践
    这种问题我最喜欢了,我相信你在写并发代码来提升性能的时候也会遵循某些最佳实践。以下三条最佳实践我觉得大多数Java程序员都应该遵循:
    给你的线程起个有意义的名字。
    这样可以方便找bug或追踪。OrderProcessor, QuoteProcessor or TradeProcessor 这种名字比 Thread-1. Thread-2 and Thread-3 好多了,给线程起一个和它要完成的任务相关的名字,所有的主要框架甚至JDK都遵循这个最佳实践。
    避免锁定和缩小同步的范围
    锁花费的代价高昂且上下文切换更耗费时间空间,试试最低限度的使用同步和锁,缩小临界区。因此相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
    多用同步类少用wait 和 notify
    首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 这些同步类简化了编码操作,而用wait和notify很难实现对复杂控制流的控制。其次,这些类是由最好的企业编写和维护在后续的JDK中它们还会不断 优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
    多用并发集合少用同步集合
    这是另外一个容易遵循且受益巨大的最佳实践,并发集合比同步集合的可扩展性更好,所以在并发编程时使用并发集合效果更好。如果下一次你需要用到map,你应该首先想到用ConcurrentHashMap。

    51) 如何强制启动一个线程?
    这个问题就像是如何强制进行Java垃圾回收,目前还没有觉得方法,虽然你可以使用System.gc()来进行垃圾回收,但是不保证能成功。在Java里面没有办法强制启动一个线程,它是被线程调度器控制着且Java没有公布相关的API。

    52) Java中的fork join框架是什么?
    fork join框架是JDK7中出现的一款高效的工具,Java开发人员可以通过它充分利用现代服务器上的多处理器。它是专门为了那些可以递归划分成许多子模块 设计的,目的是将所有可用的处理能力用来提升程序的性能。fork join框架一个巨大的优势是它使用了工作窃取算法,可以完成更多任务的工作线程可以从其它线程中窃取任务来执行。

    53) Java多线程中调用wait() 和 sleep()方法有什么不同? Java多线程中调用wait() 和 sleep()方法有什么不同?
    Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

    展开全文
  • 本文给大家分享java线程实现异步调用的方法,感兴趣的朋友跟着脚本之家小编一起学习吧
  • 主要介绍了Java创建多线程异步执行实现代码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了利用Java线程技术导入数据到Elasticsearch的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • java线程安全

    千次阅读 2018-12-12 16:03:33
    线程不安全产生的主要原因:因为多个线程共享一个内存,所以当多个线程共享一个全局变量的时候,可能会受到其他干扰。 如线程更新会先在本地内存更新,然后再同步到共享内存中,当多个线程同时读写的时候,数据会...

    下面将从六个方面介绍
    一、线程间的同步
    二、线程死锁
    三、多线程的特性
    四、java内存模型详解
    五、volatile
    六、threadlocal初体验

    Java内存模型:如下图

    在这里插入图片描述

    线程不安全产生的主要原因:因为多个线程共享一个内存,所以当多个线程共享一个全局变量的时候,可能会受到其他干扰。
    如线程更新会先在本地内存更新,然后再同步到共享内存中,当多个线程同时读写的时候,数据会出现错误,就产生了线程不安全的现象。

    注意:如果线程对自己的局部变量进行修改是不会受影响,就是没有线程安全的问题

    一、线程间的同步

    针对上面提到的问题,因此我没要对对象(上面提到的全局变量)加锁!即保证一次只有一个线程对对象进行操作。

    本次主要为大家简单介绍两种锁
    synchronized(自动锁)
    lock(JDK1.5并发包,手动锁,即需要手动进行加锁以及解锁的操作)

    1.1 synchronized

    1.1.1简介:

    使用 synchronized(obj) 对象锁,表示一次只有一个obj对象内被调用。
    也可以修饰方法: public synchronized void test(),此时锁的对象默认是这个类.class文件,缺点是效率非常低。

    使用前提两个线程以上,需要同步
    要求多个线程需要同步 必须使用同一把锁(即obj是同一个对象)
    作用保证同一时间只有一个线程在执行被锁住的代码块
    原理一次只有一个锁会拿到该对象
    锁什么时候释放代码执行完成之后
    其他线程执行时间只有在锁被释放之后其他线程才可以进行同步
    同步函数使用什么锁使用的是this锁即类似于 synchronized(this),对象是 类.class文件
    怎么判断是否使用的同一把锁在线程执行的中间更换所对象,如果输出结果没有冲突,则说明是同一把锁,即判断两把锁是否同步,只要判断锁对象是否相同即可
    非静态同步函数使用this锁
    静态同步函数使用当前字节码文件 必须明确指明 类.class锁

    总结:

    • 线程安全解决办法:
      问:如何解决多线程之间线程安全问题?
      答:使用多线程之间同步synchronized或使用锁(lock)。
    • 问:为什么使用线程同步或使用锁能解决线程安全问题呢?
      答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
    • 问:什么是多线程之间同步?
      答:当多个线程共享同一个资源,不会受到其他线程的干扰。

    1.1.2 针对上述说明的相关例子:

    同步代码块

    • 什么是同步代码块?
      答:就是将可能会发生线程安全问题的代码,给包括起来。
      synchronized(同一个数据){
      可能会发生线程冲突问题
      }
      就是同步代码块
      synchronized(对象)//这个对象可以为任意对象
      {
      需要被同步的代码
      }
      对象如同锁,持有锁的线程可以在同步中执行
      没持有锁的线程即使获取CPU的执行权,也进不去
    • 同步的前提:
      1,必须要有两个或者两个以上的线程
      2,必须是多个线程使用同一个锁
      必须保证同步中只能有一个线程在运行
      好处:解决了多线程的安全问题
      弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
    private static Object oj = new Object();   	
    public void sale() {
    		// 前提 多线程进行使用、多个线程只能拿到一把锁。
    		// 保证只能让一个线程 在执行 缺点效率降低
    		 synchronized (oj) {
    		if (count > 0) {
    			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
    			count--;
    		}
    		 }
    	}
    

    同步函数

    • 什么是同步函数?
      答:在方法上修饰synchronized 称为同步函数
    public synchronized void sale() {
    			if (trainCount > 0) { 
    try {
    					Thread.sleep(40);
    				} catch (Exception e) {
    				}
    				System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
    				trainCount--;
    			}
    	}
    
    • 同步函数用的是什么锁?
      答:同步函数使用this锁。
      证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。
    class ThreadTrain2 implements Runnable {
    	private int count = 100;
    	public boolean flag = true;
    	private static Object oj = new Object();
    
    	@Override
    	public void run() {
    		if (flag) {
    
    			while (count > 0) {
    
    				synchronized (this) {
    					if (count > 0) {
    						try {
    							Thread.sleep(50);
    						} catch (Exception e) {
    							// TODO: handle exception
    						}
    						System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
    						count--;
    					}
    				}
    
    			}
    
    		} else {
    			while (count > 0) {
    				sale();
    			}
    		}
    
    	}
    
    	public synchronized void sale() {
    		// 前提 多线程进行使用、多个线程只能拿到一把锁。
    		// 保证只能让一个线程 在执行 缺点效率降低
    		// synchronized (oj) {
    		if (count > 0) {
    			try {
    				Thread.sleep(50);
    			} catch (Exception e) {
    				// TODO: handle exception
    			}
    			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
    			count--;
    		}
    		// }
    	}
    }
    
    public class ThreadDemo2 {
    	public static void main(String[] args) throws InterruptedException {
    		ThreadTrain2 threadTrain1 = new ThreadTrain2();
    		Thread t1 = new Thread(threadTrain1, "①号窗口");
    		Thread t2 = new Thread(threadTrain1, "②号窗口");
    		t1.start();
    		Thread.sleep(40);
    		threadTrain1.flag = false;
    		t2.start();
    	}
    }
    
    • 静态同步函数
      答:什么是静态同步函数?
      方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
      静态的同步函数使用的锁是 该函数所属字节码文件对象
      可以用 getClass方法获取,也可以用当前 类名.class 表示。
    synchronized (ThreadTrain.class) {
    			System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
    			trainCount--;
    			try {
    				Thread.sleep(100);
    			} catch (Exception e) {
    			}
    }
    

    总结:
    synchronized 修饰方法使用锁是当前this锁。
    synchronized 修饰静态方法使用锁是当前类的字节码文件

    二、线程死锁

    在这里插入图片描述

    即线程1需要a锁继续执行,线程2需要b锁继续执行,但是他们必须分别在自身程序执行结束之后才会释放锁,因此线程1等b锁,但是它又不能释放a锁,线程2等a锁,但它又不能释放b锁,造成互斥等待。

    三、 多线程的三大特性

    原子性、可见性、有序性

    • 什么是原子性
      即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
      一个很经典的例子就是银行账户转账问题:
      比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
      我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
      原子性其实就是保证数据一致、线程安全一部分,
    • 什么是可见性
      当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
      若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
    • 什么是有序性
      程序执行的顺序按照代码的先后顺序执行。
      一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
      int a = 10; //语句1
      int r = 2; //语句2
      a = a + 3; //语句3
      r = a*a; //语句4
      则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
      但绝不可能 2-1-4-3,因为这打破了依赖关系。
      显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

    四、 Java 内存模型

    共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
    在这里插入图片描述

    从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

    1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
    2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
      下面通过示意图来说明这两个步骤:
      在这里插入图片描述

    如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
    从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。

    总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。

    五、 Volatile

    5.1 什么是Volatile

    Volatile 关键字的作用是变量在多个线程之间可见。

    class ThreadVolatileDemo extends Thread {
    	public    boolean flag = true;
    	@Override
    	public void run() {
    		System.out.println("开始执行子线程....");
    		while (flag) {
    		}
    		System.out.println("线程停止");
    	}
    	public void setRuning(boolean flag) {
    		this.flag = flag;
    	}
    
    }
    
    public class ThreadVolatile {
    	public static void main(String[] args) throws InterruptedException {
    		ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
    		threadVolatileDemo.start();
    		Thread.sleep(3000);
    		threadVolatileDemo.setRuning(false);
    		System.out.println("flag 已经设置成false");
    		Thread.sleep(1000);
    		System.out.println(threadVolatileDemo.flag);
    
    	}
    }
    

    在这里插入图片描述

    已经将结果设置为fasle为什么?还一直在运行呢。
    原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。
    解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值

    5.2 Volatile非原子性

    注意: Volatile非原子性

    public class VolatileNoAtomic extends Thread {
    	private static volatile int count;
    
    	// private static AtomicInteger count = new AtomicInteger(0);
    	private static void addCount() {
    		for (int i = 0; i < 1000; i++) {
    			count++;
    			// count.incrementAndGet();
    		}
    		System.out.println(count);
    	}
    
    	public void run() {
    		addCount();
    	}
    
    	public static void main(String[] args) {
    
    		VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
    		for (int i = 0; i < 10; i++) {
    			arr[i] = new VolatileNoAtomic();
    		}
    
    		for (int i = 0; i < 10; i++) {
    			arr[i].start();
    		}
    	}
    
    }
    

    在这里插入图片描述

    结果发现 数据不同步,因为Volatile不用具备原子性。
    使用AtomicInteger原子类
    AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。

    public class VolatileNoAtomic extends Thread {
    	static int count = 0;
    	private static AtomicInteger atomicInteger = new AtomicInteger(0);
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 1000; i++) {
    			//等同于i++
    			atomicInteger.incrementAndGet();
    		}
    		System.out.println(count);
    	}
    
    	public static void main(String[] args) {
    		// 初始化10个线程
    		VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
    		for (int i = 0; i < 10; i++) {
    			// 创建
    			volatileNoAtomic[i] = new VolatileNoAtomic();
    		}
    		for (int i = 0; i < volatileNoAtomic.length; i++) {
    			volatileNoAtomic[i].start();
    		}
    	}
    
    }
    

    六、 ThreadLocal

    6.1 什么是ThreadLocal

    ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

    6.2 ThreadLocal的接口方法

    ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

    • void set(Object value)设置当前线程的线程局部变量的值。
    • public Object get()该方法返回当前线程所对应的线程局部变量。
    • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
    • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

    6.3 案例:创建三个线程,每个线程生成自己独立序列号。

    代码:

    class Res {
    	// 生成序列号共享变量
    	public static Integer count = 0;
    	public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    		protected Integer initialValue() {
    
    			return 0;
    		};
    
    	};
    
    	public Integer getNum() {
    		int count = threadLocal.get() + 1;
    		threadLocal.set(count);
    		return count;
    	}
    }
    
    public class ThreadLocaDemo2 extends Thread {
    	private Res res;
    
    	public ThreadLocaDemo2(Res res) {
    		this.res = res;
    	}
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 3; i++) {
    			System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
    		}
    
    	}
    
    	public static void main(String[] args) {
    		Res res = new Res();
    		ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
    		ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
    		ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
    		threadLocaDemo1.start();
    		threadLocaDemo2.start();
    		threadLocaDemo3.start();
    	}
    
    }
    
    展开全文
  • Java线程超详解

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

    引言

    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。
    那么话不多说,今天本帅将记录自己线程的学习。

    程序,进程,线程的基本概念+并行与并发:

    程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
    进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
    线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

    即:线程《线程(一个程序可以有多个线程)
    程序:静态的代码 进程:动态执行的程序
    线程:进程中要同时干几件事时,每一件事的执行路径成为线程。

    并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
    并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

    线程的相关API

    //获取当前线程的名字
    Thread.currentThread().getName()

    1.start():1.启动当前线程2.调用线程中的run方法
    2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3.currentThread():静态方法,返回执行当前代码的线程
    4.getName():获取当前线程的名字
    5.setName():设置当前线程的名字
    6.yield():主动释放当前线程的执行权
    7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
    8.stop():过时方法。当执行此方法时,强制结束当前线程。
    9.sleep(long millitime):线程休眠一段时间
    10.isAlive():判断当前线程是否存活

    判断是否是多线程

    一条线程即为一条执行路径,即当能用一条路径画出来时即为一个线程
    例:如下看似既执行了方法一,又执行了方法2,但是其实质就是主线程在执行方法2和方法1这一条路径,所以就是一个线程

    public class Sample{
    		public void method1(String str){
    			System.out.println(str);
    		}
    	
    	public void method2(String str){
    		method1(str);
    	}
    	
    	public static void main(String[] args){
    		Sample s = new Sample();
    		s.method2("hello");
    	}
    }
    

    在这里插入图片描述

    线程的调度

    调度策略:
    时间片:线程的调度采用时间片轮转的方式
    抢占式:高优先级的线程抢占CPU
    Java的调度方法:
    1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
    2.对高优先级,使用优先调度的抢占式策略

    线程的优先级

    等级:
    MAX_PRIORITY:10
    MIN_PRIORITY:1
    NORM_PRIORITY:5

    方法:
    getPriority():返回线程优先级
    setPriority(int newPriority):改变线程的优先级

    注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

    多线程的创建方式

    1. 方式1:继承于Thread类

    1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
    2.重写Thread类的run()方法
    3.创建Thread子类的对象
    4.通过此对象调用start()方法

    start与run方法的区别:

    start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
    调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
    run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

    总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
    在这里插入图片描述

    多线程例子(火车站多窗口卖票问题)

    	package com.example.paoduantui.Thread;
    	
    	import android.view.Window;
    	
    	/**
    	 *
    	 * 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
    	 * 用静态变量保证三个线程的数据独一份
    	 * 
    	 * 存在线程的安全问题,有待解决
    	 *
    	 * */
    	
    	public class ThreadDemo extends Thread{
    	
    	    public static void main(String[] args){
    	        window t1 = new window();
    	        window t2 = new window();
    	        window t3 = new window();
    	
    	        t1.setName("售票口1");
    	        t2.setName("售票口2");
    	        t3.setName("售票口3");
    	
    	        t1.start();
    	        t2.start();
    	        t3.start();
    	    }
    	
    	}
    	
    	class window extends Thread{
    	    private static int ticket = 100; //将其加载在类的静态区,所有线程共享该静态变量
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    2. 方式2:实现Runable接口方式

    1.创建一个实现了Runable接口的类
    2.实现类去实现Runnable中的抽象方法:run()
    3.创建实现类的对象
    4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
    5.通过Thread类的对象调用start()

    具体操作,将一个类实现Runable接口,(插上接口一端)。
    另外一端,通过实现类的对象与线程对象通过此Runable接口插上接口实现

    	package com.example.paoduantui.Thread;
    	
    	public class ThreadDemo01 {
    	    
    	    public static  void main(String[] args){
    	        window1 w = new window1();
    	        
    	        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
    	        
    	        Thread t1=new Thread(w);
    	        Thread t2=new Thread(w);
    	        Thread t3=new Thread(w);
    	
    	        t1.setName("窗口1");
    	        t2.setName("窗口2");
    	        t3.setName("窗口3");
    	        
    	        t1.start();
    	        t2.start();
    	        t3.start();
    	    }
    	}
    	
    	class window1 implements Runnable{
    	    
    	    private int ticket = 100;
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    比较创建线程的两种方式:
    开发中,优先选择实现Runable接口的方式
    原因1:实现的方式没有类的单继承性的局限性
    2:实现的方式更适合用来处理多个线程有共享数据的情况
    联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

    3.新增的两种创建多线程方式

    1.实现callable接口方式:

    与使用runnable方式相比,callable功能更强大些:
    runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
    方法可以抛出异常
    支持泛型的返回值
    需要借助FutureTask类,比如获取返回结果

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * 创建线程的方式三:实现callable接口。---JDK 5.0新增
     *是否多线程?否,就一个线程
     *
     * 比runable多一个FutureTask类,用来接收call方法的返回值。
     * 适用于需要从线程中接收返回值的形式
     * 
     * //callable实现新建线程的步骤:
     * 1.创建一个实现callable的实现类
     * 2.实现call方法,将此线程需要执行的操作声明在call()中
     * 3.创建callable实现类的对象
     * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
     * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
     * 
     * */
    
    
    //实现callable接口的call方法
    class NumThread implements Callable{
    
        private int sum=0;//
    
        //可以抛出异常
        @Override
        public Object call() throws Exception {
            for(int i = 0;i<=100;i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    
    public class ThreadNew {
    
        public static void main(String[] args){
            //new一个实现callable接口的对象
            NumThread numThread = new NumThread();
    
            //通过futureTask对象的get方法来接收futureTask的值
            FutureTask futureTask = new FutureTask(numThread);
    
            Thread t1 = new Thread(futureTask);
            t1.setName("线程1");
            t1.start();
    
            try {
                //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
               Object sum = futureTask.get();
               System.out.println(Thread.currentThread().getName()+":"+sum);
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    使用线程池的方式:

    背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)
    好处:提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止
    。。。。。。

    JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
    void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
    Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池。

    Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
    Executors.newCachedThreadPool()创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(n)创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(n)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    线程池构造批量线程代码如下:

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 创建线程的方式四:使用线程池(批量使用线程)
     *1.需要创建实现runnable或者callable接口方式的对象
     * 2.创建executorservice线程池
     * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
     * 4.关闭线程池
     *
     * */
    
    class NumberThread implements Runnable{
    
    
        @Override
        public void run() {
            for(int i = 0;i<=100;i++){
                if (i % 2 ==0 )
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    
    class NumberThread1 implements Runnable{
        @Override
        public void run() {
            for(int i = 0;i<100; i++){
                if(i%2==1){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
    }
    
    public class ThreadPool {
    
        public static void main(String[] args){
    
            //创建固定线程个数为十个的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            //new一个Runnable接口的对象
            NumberThread number = new NumberThread();
            NumberThread1 number1 = new NumberThread1();
    
            //执行线程,最多十个
            executorService.execute(number1);
            executorService.execute(number);//适合适用于Runnable
    
            //executorService.submit();//适合使用于Callable
            //关闭线程池
            executorService.shutdown();
        }
    
    }
    

    目前两种方式要想调用新线程,都需要用到Thread中的start方法。

    java virtual machine(JVM):java虚拟机内存结构

    程序(一段静态的代码)——————》加载到内存中——————》进程(加载到内存中的代码,动态的程序)
    进程可细分为多个线程,一个线程代表一个程序内部的一条执行路径
    每个线程有其独立的程序计数器(PC,指导着程序向下执行)与运行栈(本地变量等,本地方法等)
    在这里插入图片描述

    大佬传送门:https://blog.csdn.net/bluetjs/article/details/52874852

    线程通信方法:

    wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

    由于wait,notify,以及notifyAll都涉及到与锁相关的操作
    wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
    notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

    有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

    所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

    线程的分类:

    java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

    若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

    线程的生命周期:

    JDK中用Thread.State类定义了线程的几种状态,如下:

    线程生命周期的阶段描述
    新建当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    就绪处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
    运行当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
    阻塞在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
    死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    在这里插入图片描述

    线程的同步:在同步代码块中,只能存在一个线程。

    线程的安全问题:

    什么是线程安全问题呢?
    线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

    上述例子中:创建三个窗口卖票,总票数为100张票
    1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
    2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
    生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
    3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
    4.在java中,我们通过同步机制,来解决线程的安全问题。

    方式一:同步代码块
    使用同步监视器(锁)
    Synchronized(同步监视器){
    //需要被同步的代码
    }
    说明:

    1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
    2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
    3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

    Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

    方式二:同步方法
    使用同步方法,对方法进行synchronized关键字修饰
    将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
    对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
    而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

    总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
    2.非静态的同步方法,同步监视器是this
    静态的同步方法,同步监视器是当前类本身。继承自Thread。class

    方式三:JDK5.0新增的lock锁方法

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.locks.ReentrantLock;
    
    class Window implements Runnable{
        private int ticket = 100;//定义一百张票
        //1.实例化锁
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            
                while (true) {
    
                    //2.调用锁定方法lock
                    lock.lock();
    
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票");
                        ticket--;
                    } else {
                        break;
                    }
                }
    
    
            }
    }
    
    public class LockTest {
    
        public static void main(String[] args){
           Window w= new Window();
    
           Thread t1 = new Thread(w);
           Thread t2 = new Thread(w);
           Thread t3 = new Thread(w);
    
           t1.setName("窗口1");
           t2.setName("窗口1");
           t3.setName("窗口1");
    
           t1.start();
           t2.start();
           t3.start();
        }
    
    }
    

    总结:Synchronized与lock的异同?

    相同:二者都可以解决线程安全问题
    不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
    lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

    优先使用顺序:
    LOCK-》同步代码块-》同步方法

    判断线程是否有安全问题,以及如何解决:

    1.先判断是否多线程
    2.再判断是否有共享数据
    3.是否并发的对共享数据进行操作
    4.选择上述三种方法解决线程安全问题

    例题:

    	package com.example.paoduantui.Thread;
    	
    	/***
    	 * 描述:甲乙同时往银行存钱,存够3000
    	 *
    	 *
    	 * */
    	
    	//账户
    	class Account{
    	    private double balance;//余额
    	    //构造器
    	    public Account(double balance) {
    	        this.balance = balance;
    	    }
    	    //存钱方法
    	    public synchronized void deposit(double amt){
    	        if(amt>0){
    	            balance +=amt;
    	            try {
    	                Thread.sleep(1000);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	            System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
    	        }
    	    }
    	}
    	
    	//两个顾客线程
    	class Customer extends Thread{
    	     private Account acct;
    	
    	     public Customer(Account acct){
    	         this.acct = acct;
    	     }
    	
    	
    	
    	    @Override
    	    public void run() {
    	        for (int i = 0;i<3;i++){
    	            acct.deposit(1000);
    	        }
    	    }
    	}
    	
    	//主方法,之中new同一个账户,甲乙两个存钱线程。
    	public class AccountTest {
    	
    	    public static void main(String[] args){
    	        Account acct = new Account(0);
    	        Customer c1 = new Customer(acct);
    	        Customer c2 = new Customer(acct);
    	
    	        c1.setName("甲");
    	        c2.setName("乙");
    	
    	        c1.start();
    	        c2.start();
    	    }
    	
    	}
    

    解决单例模式的懒汉式的线程安全问题:

    单例:只能通过静态方法获取一个实例,不能通过构造器来构造实例
    1.构造器的私有化:
    private Bank(){}//可以在构造器中初始化东西
    private static Bank instance = null;//初始化静态实例

    public static Bank getInstance(){
    if(instance!=null){
    instance = new Bank();
    }
    return instance;
    }

    假设有多个线程调用此单例,而调用的获取单例的函数作为操作共享单例的代码块并没有解决线程的安全问题,会导致多个线程都判断实例是否为空,此时就会导致多个实例的产生,也就是单例模式的线程安全问题。

    解决线程安全问题的思路:

    1. 将获取单例的方法改写成同部方法,即加上synchronized关键字,此时同步监视器为当前类本身。(当有多个线程并发的获取实例时,同时只能有一个线程获取实例),解决了单例模式的线程安全问题。
    2. 用同步监视器包裹住同步代码块的方式。

    懒汉式单例模式的模型,例如:生活中的限量版的抢购:
    当一群人并发的抢一个限量版的东西的时候,可能同时抢到了几个人,他们同时进入了房间(同步代码块内)
    但是只有第一个拿到限量版东西的人才能到手,其余人都不能拿到,所以效率稍高的做法是,当东西被拿走时,我们在门外立一块牌子,售罄。
    这样就减少了线程的等待。即下面效率稍高的懒汉式写法:

    package com.example.paoduantui.Thread;
    
    public class Bank {
        //私有化构造器
        private Bank(){}
        //初始化静态实例化对象
        private static  Bank instance = null;
    
        //获取单例实例,此种懒汉式单例模式存在线程不安全问题(从并发考虑)
    
        public static  Bank getInstance(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
    
        //同步方法模式的线程安全
        public static synchronized Bank getInstance1(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
        //同步代码块模式的线程安全(上锁)
        public  static Bank getInstance2(){
            synchronized (Bank.class){
                if(instance==null){
                    instance = new Bank();
                }
                return  instance;
            }
        }
        
        //效率更高的线程安全的懒汉式单例模式
        /**
         * 由于当高并发调用单例模式的时候,类似于万人夺宝,只有第一个进入房间的人才能拿到宝物,
         * 当多个人进入这个房间时,第一个人拿走了宝物,也就另外几个人需要在同步代码块外等候,
         * 剩下的人只需要看到门口售罄的牌子即已知宝物已经被夺,可以不用进入同步代码块内,提高了效率。
         * 
         * 
         * */
        public static Bank getInstance3(){
            if (instance==null){
                synchronized (Bank.class){
                    if(instance==null){
                        instance = new Bank();
                    }
                }
            }
            return  instance;
        }
    }
    

    线程的死锁问题:

    线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
    出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续

    package com.example.paoduantui.Thread;
    
    
    /**
     * 演示线程的死锁问题
     *
     * */
    public class Demo {
    
        public static void main(String[] args){
    
            final StringBuffer s1 = new StringBuffer();
            final StringBuffer s2 = new StringBuffer();
    
    
            new Thread(){
                @Override
                public void run() {
                    //先拿锁一,再拿锁二
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        synchronized (s2) {
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            //使用匿名内部类实现runnable接口的方式实现线程的创建
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
    
                        synchronized (s1) {
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    
    }
    

    运行结果:
    1.先调用上面的线程,再调用下面的线程:
    在这里插入图片描述
    2.出现死锁:
    在这里插入图片描述
    3.先调用下面的线程,再调用上面的线程。
    在这里插入图片描述

    死锁的解决办法:

    1.减少同步共享变量
    2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
    3.减少锁的嵌套。

    线程的通信

    通信常用方法:

    通信方法描述
    wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    notify一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
    notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

    使用前提:这三个方法均只能使用在同步代码块或者同步方法中。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的例子:使用两个线程打印1—100,线程1,线程2交替打印
     *
     * 当我们不采取线程之间的通信时,无法达到线程1,2交替打印(cpu的控制权,是自动分配的)
     * 若想达到线程1,2交替打印,需要:
     * 1.当线程1获取锁以后,进入代码块里将number++(数字打印并增加)操作完以后,为了保证下个锁为线程2所有,需要将线程1阻塞(线程1你等等wait())。(输出1,number为2)
     * 2.当线程2获取锁以后,此时线程1已经不能进入同步代码块中了,所以,为了让线程1继续抢占下一把锁,需要让线程1的阻塞状态取消(通知线程1不用等了notify()及notifyAll()),即应该在进入同步代码块时取消线程1的阻塞。
     *
     * */
    
    class Number implements Runnable{
    
        private int number = 1;//设置共享数据(线程之间对于共享数据的共享即为通信)
    
    
        //对共享数据进行操作的代码块,需要线程安全
        @Override
        public synchronized void run() {
    
            while(true){
                //使得线程交替等待以及通知交替解等待
                notify();//省略了this.notify()关键字
                if(number<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
    
    public class CommunicationTest {
    
        public static void main(String[] args){
            //创建runnable对象
            Number number = new Number();
    
            //创建线程,并实现runnable接口
            Thread t1 = new Thread(number);
            Thread t2 = new Thread(number);
    
            //给线程设置名字
            t1.setName("线程1");
            t2.setName("线程2");
    
            //开启线程
            t1.start();
            t2.start();
    
        }
    
    }
    

    sleep和wait的异同:

    相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
    不同点:
    1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
    2.调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
    3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

    经典例题:生产者/消费者问题:

    生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

    这里可能出现两个问题:
    生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
    消费者比生产者快时,消费者会去相同的数据。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的应用:生产者/消费者问题
     *
     * 1.是否是多线程问题?是的,有生产者线程和消费者线程(多线程的创建,四种方式)
     * 2.多线程问题是否存在共享数据? 存在共享数据----产品(同步方法,同步代码块,lock锁)
     * 3.多线程是否存在线程安全问题? 存在----都对共享数据产品进行了操作。(三种方法)
     * 4.是否存在线程间的通信,是,如果生产多了到20时,需要通知停止生产(wait)。(线程之间的通信问题,需要wait,notify等)
     *
     * */
    
    
    	class Clerk{
    	
    	    private int productCount = 0;
    	
    	
    	    //生产产品
    	    public synchronized void produceProduct() {
    	
    	        if(productCount<20) {
    	            productCount++;
    	
    	            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
    	            notify();
    	        }else{
    	            //当有20个时,等待wait
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	
    	    //消费产品
    	    public synchronized void consumeProduct() {
    	        if (productCount>0){
    	            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
    	            productCount--;
    	            notify();
    	        }else{
    	            //当0个时等待
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	}
    	
    	class Producer extends Thread{//生产者线程
    	
    	    private Clerk clerk;
    	
    	    public Producer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        try {
    	            sleep(10);
    	        } catch (InterruptedException e) {
    	            e.printStackTrace();
    	        }
    	        System.out.println(Thread.currentThread().getName()+";开始生产产品......");
    	
    	        while(true){
    	            clerk.produceProduct();
    	        }
    	    }
    	}
    	
    	class Consumer implements Runnable{//消费者线程
    	
    	    private Clerk clerk;
    	
    	    public Consumer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        System.out.println(Thread.currentThread().getName()+":开始消费产品");
    	
    	        while(true){
    	            try {
    	                Thread.sleep(1);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	
    	            clerk.consumeProduct();
    	        }
    	
    	    }
    	}
    	
    	public class ProductTest {
    	
    	    public static void main(String[] args){
    	        Clerk clerk = new Clerk();
    	
    	        Producer p1 = new Producer(clerk);
    	        p1.setName("生产者1");
    	
    	        Consumer c1 = new Consumer(clerk);
    	        Thread t1 = new Thread(c1);
    	        t1.setName("消费者1");
    	
    	        p1.start();
    	        t1.start();
    	
    	    }
    	
    	}
    
    展开全文
  • Java线程(超详细)

    千次阅读 2021-01-12 21:14:38
    线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该...

    多线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该怎么解决? =》 怎么使用synchronized(同步)决解? =》使用同步可能会产生死锁,该怎么决解? =》 线程之间是如何通信的? =》 线程有返回值吗?该如何拿到? =》 怎么才能一次性启动几百上千个的线程?

    线程的概念

    什么是进程
    进程是操作系统中正在执行的不同的应用程序,例如:我们可以同时打开Word和记事本

    什么是线程
    线程是一个应用程序进程中不同的执行路径,例如:我们的WEB服务器,能够为多个用户同时提供请求服务
    进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。线程总是在某个进程环境中创建的,而且它的整个寿命期都在该进程中。

    – Java的多线程
    • Java 中的多线程是通过java.lang.Thread类来实现的.
    • 一个Java应用程序java.exe,其实至少有三个线程: main()主线程, gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
    • 使用多线程的优点。
    – 背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短(因为单线程的可以减少cup的调度消耗的时间),为何仍需多线程呢?
    – 多线程程序的优点:
    1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。同时做多个事情。比如:一边听歌、一边写代码。
    2.提高计算机系统CPU的利用率。
    3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
    • 何时需要多线程
    – 程序需要同时执行两个或多个任务。
    – 需要一些后台运行的程序时。比如:Java后台运行的GC功能。
    主线程
    – 概念
    • 即使Java程序没有显示的来声明一个线程,Java也会有一个线程存在该线程叫做主线程
    • 可以调用Thread.currentThread()来获得当前线程

    线程的创建方法

    有两种方法来创建线程

    • 继承Thread类
    – MyThread extends Thread
    » 需要覆盖run方法

    • 实现Runnable接口
    – Runnable 中有一个方法run用来定义线程执行代码
    – public void run();

    后面还会介绍两种,一共是四种创建方式。

    线程的启动和终止

    线程的启动
    • 线程的启动需要调用Thread的start方法,不能直接调用run方法,如果直接调用run方法相当于方法调用。
    线程的终止
    • 当run方法返回,线程终止,一旦线程终止后不能再次启动。
    • 线程的终止可以调用线程的interrupt方法,但该方法不是最佳方法,最好是设置一个标记来控制线程的终止。
    注意事项:一个线程只能启动一次,不能多次启动同一个线程。

    线程控制基本方法
    • Thread类的有关方法(1)
    – void start():启动线程并执行对象的run()方法
    – run():线程在被调度时执行的操作
    – String getName():返回线程的名称
    – void setName(String name):设置该线程名称
    – static Thread currentThread():返回当前线程。
    • 线程控制的基本方法
    在这里插入图片描述

    线程的优先级
    – 线程的优先级越高占用CPU时间越长
    – 最高为10级,最低为1级,默认是5级
    线程的状态转换
    在这里插入图片描述
    线程创建的选择
    • 创建线程的两种方式。
    开发中:优先选择实现Runnable 接口的方式来创建线程。
    – 1.实现接口的方式没有类的单继承性的局限性。
    – 2.实现接口的方式更适合来处理多个线程有共享数据的情况。

    线程的练习
    创建三个窗口卖票,总票数为100张。(通过线程的两种实现方式分别来完成)
    方式一:

    //练习,100张票三人卖
    public class WindowTest2  implements Runnable {
    	private int ticket = 100;
    	@Override
    	public void run() {
    		while (true) {
    			if (ticket <= 0) {
    				return;
    			}
    			System.out.println(Thread.currentThread().getName() + "-->售出第:" + (101 - ticket) + "票");
    			ticket--;
    			try {
    				Thread.sleep(50);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	public static void main(String[] args) {
    
    		WindowTest2 wt1 = new WindowTest2();
    		//创建三个线程,并启动,通过的是同一个对象来创建,所以票数可以是非静态的
    		new Thread(wt1).start();
    		new Thread(wt1).start();
    		new Thread(wt1).start();
    		//线程安全问题:同一个时间,多个线程访问(修改)同一个对象,造成结果不可预测(混乱)
    		//线程安全问题的条件:1.同一时间、2.多个线程一起访问、3操作的是同一个对象
    		/*输出的部分结果:
    		 * Thread-1-->售出第:1票 
    		 * Thread-0-->售出第:1票 
    		 * Thread-2-->售出第:1票 
    		 * Thread-1-->售出第:4票
    		 * Thread-2-->售出第:4票 
    		 * Thread-0-->售出第:4票 
    		 * Thread-1-->售出第:7票 
    		 * Thread-2-->售出第:7票
    		 * Thread-0-->售出第:7票
    		 */
    
    	}
    }
    
    

    方式二:

    //练习,100张票三人卖
    public class WindowTest1 extends Thread {
    	private static int ticket = 100;
    	@Override
    	public void run() {
    		while (true) {
    				if (ticket <= 0) {
    					return;
    				}
    				getTicket();
    		}
    	}
    	private static void getTicket() {
    		if(ticket==0) return;
    		System.out.println(Thread.currentThread().getName() + "-->售出第:" + (101 - ticket) + "票");
    		ticket--;
    		try {
    			sleep(100);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	public static void main(String[] args) {
    		WindowTest1 wt1 = new WindowTest1();
    		WindowTest1 wt2 = new WindowTest1();
    		WindowTest1 wt3 = new WindowTest1();
    		wt1.start();
    		wt2.start();
    		wt3.start();
    		//线程安全问题:同一个时间,多个线程访问(修改)同一个对象,造成结果不可预测(混乱)
    		//线程安全问题的条件:1.同一时间、2.多个线程一起访问、3操作的是同一个对象
    		/*输出的部分结果:
    		 * Thread-1-->售出第:1票 
    		 * Thread-0-->售出第:1票 
    		 * Thread-2-->售出第:1票 
    		 * Thread-1-->售出第:4票
    		 * Thread-2-->售出第:4票 
    		 * Thread-0-->售出第:4票 
    		 * Thread-1-->售出第:7票 
    		 * Thread-2-->售出第:7票
    		 * Thread-0-->售出第:7票
    		 */
    	}
    }
    
    

    在这里买票的三个窗口,会出现买了同一张票的问题,后面将会决解这个问题。

    线程的同步

    • 为什么需要线程同步:一个银行账号在同一时间不能接受多个线程的访问,因为这样会造成混乱
    • 线程的同步
    synchronized
    线程安全问题:
    1 同一时间
    2 多个线程
    3 操作同一个账号
    就会出现混乱情况,这种由于多线程引发的混乱情况,我们就称他为:线程安全问题
    如何解决呢?同步操作了解决线程问题。

    synchronized关键字
    • synchronized 是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
    1、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
    2、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
    3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
    4、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
    • 方式一:同步代码块
    synchronized(同步监视器){
    //需要被同步的代码
    }
    说明:
    1. 操作共享数据的代码,即为需要被同步的代码
    2.共享数据:多个线程共同操作的变量。比如: ticket 就是共享数据。
    3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
    补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

    通过第一种同步方式修改售票问题

    //练习,100张票三人卖
    public class WindowTest4 implements Runnable {
    	private int ticket = 100;
    	@Override
    	public void run() {
    		while (true) {
    			// 同步关键字括号中是同步监视器
    			if (ticket <= 0) {
    				return;
    			}
    			getTicket();
    		}
    	}
    
    	private synchronized void getTicket() {
    		if (ticket == 0)
    			return;
    		System.out.println(Thread.currentThread().getName() + "-->售出第:" + (101 - ticket) + "票");
    		ticket--;
    		try {
    			Thread.sleep(50);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    
    	public static void main(String[] args) {
    		WindowTest4 wt1 = new WindowTest4();
    		// 创建三个线程,并启动,通过的是同一个对象来创建,所以票数可以是非静态的
    		new Thread(wt1).start();
    		new Thread(wt1).start();
    		new Thread(wt1).start();
    		/*部分输出结果:
    		 * Thread-1-->售出第:1票 
    		 * Thread-1-->售出第:2票 
    		 * Thread-1-->售出第:3票 
    		 * Thread-1-->售出第:4票
    		 * Thread-2-->售出第:5票 
    		 * Thread-2-->售出第:6票 
    		 * Thread-2-->售出第:7票 
    		 * Thread-0-->售出第:8票
    		 * Thread-2-->售出第:9票 
    		 * Thread-2-->售出第:10票 
    		 * Thread-1-->售出第:11票 
    		 * Thread-1-->售出第:12票
    		 */
    	}
    }
    

    单例模式:懒汉模式的同步问题及解决

    /*
     * 单例模式:懒汉模式
     */
    public class UserManager {
    	// 懒汉模式:用的时候才创建,不用的时候为null
    	private static UserManager instance;
    	private int id;
    	private String name;
    
    	private UserManager() {
    
    	}
    	// 方式一:给方法加入synchronized关键字
    	// public static synchronized UserManager getInstance() {
    	//
    	// if(instance==null) {
    	// instance = new UserManager();
    	// }
    	// return instance;
    	// }
    
    	// 方式二:通过同步代码块的方式实现线程安全问题
    	// public static UserManager getInstance() {
    	//
    	// synchronized (UserManager.class) {
    	// if (instance == null) {
    	// instance = new UserManager();
    	// }
    	// }
    	// return instance;
    	// }
    
    	// 方式三:通过双检测机制实现对象的创建,更安全,效率更高
    	public static UserManager getInstance() {
    		if (instance == null) {
    			synchronized (UserManager.class) {
    				if (instance == null) {
    					instance = new UserManager();
    				}
    			}
    		}
    		return instance;
    	}
    
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    

    死锁

    线程同步带来的问题:死锁
    理解什么是死锁?
    死锁问题的产生:
     不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
     出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
    解决方法
    ➢通过逻辑算法来避免出现死锁。
    ➢尽量减少同步资源的定义。
    ➢尽量避免嵌套同步。

    线程的通信

    • wait/notify/notifyAll
    – wait():执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
    – notify():执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的。
    – notifyAll():执行此方法,就会唤醒所有被wait的线程。
    • 说明:

    1. wait(), notify(), notifyAll()三个方法必须使用在同步代码块或同步方法中。
    2. wait(), notify(), notifyAll三个方法的调用者必须是同步代码块或同步方法中的同步监视器否则,会出现IllegaLMonitorStateException异常.

    经典案例:生成者、消费者问题

    public class ProducerConsumer {
    
    	public static void main(String[] args) {
    		BaoziStack baoziStack = new BaoziStack();
    		Producer p1 = new Producer(baoziStack);
    		Consumer c1 = new Consumer(baoziStack);
    
    		p1.start();
    		c1.start();
    
    	}
    }
    
    // 包子类
    class Baozi {
    	int id;
    
    	public Baozi(int id) {
    		this.id = id;
    	}
    
    	@Override
    	public String toString() {
    		return "包子  : " + id;
    	}
    }
    
    // 包子筐
    class BaoziStack {
    	Baozi[] bz = new Baozi[10];
    	int index = 0;
    
    	// 装包子
    	public synchronized void pushBZ(Baozi baozi) {
    		if (index >= bz.length) {
    			try {
    				wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    
    		bz[index] = baozi;
    		index++;
    		notify();
    	}
    
    	// 取包子
    	public synchronized Baozi popBZ() {
    
    		if (index <= 0) {
    			try {
    				wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		index--;
    		Baozi baozi = bz[index];
    		notify();
    		return baozi;
    	}
    }
    
    // 生产者:生产包子,放到包子筐里
    class Producer extends Thread {
    
    	private BaoziStack baoziStack;
    
    	public Producer(BaoziStack baoziStack) {
    		this.baoziStack = baoziStack;
    	}
    
    	@Override
    	public void run() {
    		// 生产包子(一天生产100个包子)
    		for (int i = 1; i <= 100; i++) {
    			Baozi baozi = new Baozi(i);
    			System.out.println("生产者生产了一个包子ID为: " + i);
    			baoziStack.pushBZ(baozi);
    			try {
    				sleep(50);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    // 消费者
    class Consumer extends Thread {
    
    	private BaoziStack baoziStack;
    
    	public Consumer(BaoziStack baoziStack) {
    		this.baoziStack = baoziStack;
    	}
    
    	@Override
    	public void run() {
    		// 一天的消费量为100个包子
    		for (int i = 1; i <= 100; i++) {
    			Baozi baozi = baoziStack.popBZ();
    			System.out.println("消费者消费了一个包子ID为:" + baozi.id + "的包子");
    			try {
    				sleep(1500);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    

    线程拓展部分内容:

    JDK5.0新增方式一:实现Callable接口
    Runnable和Callable的区别:
    1、Callable规定的方法是call(),Runnable规定的方法是run().
    2、Callable的任务执行后可返回值,而Runnable的任务是不能有返回值。
    3、call方法可以抛出异常,run方法不可以。

    在这里插入图片描述
    在这里插入图片描述

    DK5.0新增方式二:使用线程池
    背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

    JDK5.0起提供了线程池相关API: ExecutorService 和Executors
    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    void execute(Runnable command):执行任务1命令,没有返回值,-般用来执行Runnable
    < T > Future< T > submit(Callable< T > task):执行任务, 有返回值,一般来执行Callable
    void shutdown():关闭连接池
    Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    Executors.newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor(): 创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(n): 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
    好处:
    1.提高响应速度(减少创建新线程的时间)
    2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3.便于线程管理
    corePoolSize:核心池的大小
    maximumPoolsize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止

    线程池实例如下
    在这里插入图片描述
    newFixedThreadPool() 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    在这里插入图片描述

    收工

    感谢阅览~~~
    看只是学习的一种输入途径而已,重要的是理解、实践和输出。输入和输出要保持好唷~~不然只有输入没有多上输出很快就会把学过的知识忘记了ヽ(*。>Д<)o゜

    展开全文
  • java线程同步的实现方式

    万次阅读 2019-03-08 01:47:21
    这里抛砖引玉,为何要使用同步?...下面总结一些java线程实现同步方式,大致有下面几种: 1.同步方法 使用 synchronized关键字,可以修饰普通方法、静态方法,以及语句块。由于java的每个对象都有一个内置锁...
  • Java线程等待唤醒机制(加深理解)

    万次阅读 多人点赞 2019-08-04 16:28:06
    或者说想要把一个异步的操作封装成一个同步的过程。这里就用到了线程等待唤醒机制,下面具体看一下。 等待唤醒机制示例 下面代码是一个简单的线程唤醒机制示例,主要就是在Activity启动的时候初始化并start线程,...
  • JAVA线程和线程池

    千次阅读 2019-08-25 11:06:12
    1、线程状态 (1) 新建状态 (2) 就绪状态 (3) 运行状态 (4) 阻塞状态 (5) 死亡状态 2、线程优先级 3、同步工具synchronized、wait、notify 4、创建线程 (1) 实现 Runnable 接口 (2) 继承 Thread 类 (3) ...
  • Java线程详解(深度好文)

    万次阅读 多人点赞 2018-09-16 21:03:21
    Java线程:概念与原理 一、进程与线程    进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。进程不依赖于线程而独立存在,一个进程中可以启动多个线程。比如...
  • Java线程面试题合集(含答案)

    万次阅读 多人点赞 2018-06-18 14:30:48
    来源:Java线程面试题下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档: 《Java核心技术 卷一》 Java线程面试题 Top 50:http://www.importnew.com/12773.html JAVA多线程和并发...
  • Java线程面试题,我丝毫不慌

    万次阅读 多人点赞 2020-07-28 09:18:51
    文章目录 一、什么是多线程 一、初识多线程 1.1介绍进程 1.2回到线程 1.3进程与线程 1.4并行与并发 1.5Java实现多线程 1.5.1继承Thread,重写run方法 1.5.2实现Runnable接口,重写run方法 1.6Java实现多线程需要注意...
  • Java线程同步方式和线程本地变量——Java经典面试题(其二)实现线程同步的几种方式1.为何要使用同步? Java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如数据的增删改查),将会导致数据不...
  • java线程批量插入数据 带返回值

    千次阅读 2019-12-17 19:11:28
    之前写过一篇文章是关于多线程如何操作数据库,且控制事务的全局回滚,今天继续上一次进行扩展,上一次主要是针对单个线程操作没有返回值,而有时候我们希望进行多个线程批量操作数据库的同时,能返回每次成功插入到...
  • Java之多线程创建

    千次阅读 2019-03-20 19:10:31
    Java线程归纳前言传统的线程创建继承Thread类实现Runnable接口两者的共同点两者差别JDK 1.5开始出现的线程创建 前言 进程是资源分配的最小单位,而线程是执行任务的最小的单位。进程里的线程可以共享该进程拥有的...
  • java线程异步

    千次阅读 2019-05-17 17:49:29
    考虑一定得用多线程处理,那就首先需要线程池了,毕竟没有线程池的情况下很容易出现无休止创建线程是非常危险的。 实现 代码 伪代码 //创建线程池 ExecutorService executor = Executors.newFixedThreadPool(4)...
  • 线程Java中不可避免的一个重要主体。下面是对“JDK中新增JUC包”之前的Java线程内容的讲解,JUC包是由Java大师Doug Lea完成并在JDK1.5版本添加到Java中的
  • Android--从Java线程到安卓线程

    千次阅读 2016-04-25 21:34:49
    写在前面:本文为安卓线程与JAVA线程的异同,请多多指正! 简述:相信很多学安卓的都是从java入门之后开始进行安卓的学习,而当我们面临安卓线程的书写的时候,发现安卓线程并不是我们想象中使用java的线程写法就...
  • Java 线程阻塞、中断及优雅退出

    千次阅读 2018-07-18 20:39:44
    本文转自:Java 线程阻塞、中断及优雅退出 线程阻塞 一个线程进入阻塞状态的原因可能如下(已排除Deprecated方法): sleep() sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前...
  • JAVA线程——实现同步

    千次阅读 2018-07-26 17:20:33
    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了...
  • 通俗易懂的java线程(又干货又可爱哦)

    千次阅读 多人点赞 2020-04-14 19:55:08
    小李开始了日复一日的修炼,然而在修炼了一年之后,小李的进境缺很慢,小李百思不得其解,正在恼怒之际,一位仙风道骨的老人传授他一门心法,名叫“多线程”,这门功法的强大之处就在于可以分心多用,同时修炼多种...
  • Java线程Condition接口原理详解

    千次阅读 2017-05-21 19:15:23
    Condition接口详解Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话
  • java中多线程下载学习,又新增了断点的实现,可以实现暂停继续下载网络文件的功能
  • Java 基础 —— 线程安全

    千次阅读 2021-05-18 23:48:34
    一、线程安全问题 线程安全 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的...
  • JAVA线程知识点

    千次阅读 2018-09-12 18:30:12
    java线程知识点大全 java线程知识点大全 1、 什么是线程? 1、 什么是线程? 线程是操作系统能够进行运算的最小单位,他包含在实际的运作单位里面,是进程中的实际运作单位。 程序员可以通过它进行...
  • Java线程实现接口调用

    千次阅读 2019-11-23 19:37:24
    CustQueryOneThread.java 线程类 CustInfoOneServiceImpl.java 业务逻辑类 每次取数据的mybatis的 xml文件 < select id = " selectCustInfoList " resultMap = " BaseResultMap " > select < ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 102,372
精华内容 40,948
热门标签
关键字:

java线程新增过程

java 订阅