多线程笔记

==== =多线程= ====

1、创建线程有四种方式:
 继承 Thread 类;实现 Runnable 接口;实现 Callable 接口;
 使用 Executors 工具类创建线程池继承 Thread 类
2、线程的 run()和 start()有什么区别?
 1)start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
 2)run() 可以重复调用,而 start() 只能调用一次。 
 3)调用start()方法无需等待 run方法体代码执行完,可以继续执行其他的代码;此时线程是处
      于就绪状态,没有运行。通过调用run()来完成运行状态, run()方法运行结束, 此线程终止。
      然后CPU再调度其它线程。
 4)run()方法是在本线程里的,不是多线程的。 直接调用run()方法必须等待run()方法执行完毕才
      能执行下面的代码,所以在多线程执行时要使用start()方法而不是run()方法
3、什么是 Callable 和 Future?
 Callable 接口类似于 Runnable, Runnable 不会返回结果,无法抛出返回结果的异常,Callable 
 有返回值,Future 可以拿到异步执行任务的返回值。
 Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。
 Callable用于产生结果,Future 用于获取结果。
4、什么是 FutureTask
  FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个Callable 的具体实现类,
  可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当
  运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以
  对调用了Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现
  类,所以 FutureTask 也可以放入线程池中。
5、线程的生命周期
  新建状态(New):线程刚被创建,但尚未启动。如:Thread t = new MyThread();
  就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。
  处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行
  了t.start()此线程立即就会执行;
  运行状态(Running):当CPU开始调度处于就绪状态的线程时,线程才真正执行
  阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止
  执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状
  态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
  1)等待阻塞—位于对象等待池中的阻塞状态(Blocked in object’s wait pool):当线程处于运行
       状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池
       中,这涉及到“线程通信”的内容。
  2)同步阻塞 --位于对象锁池中的阻塞状态(Blocked in object’s lock pool):当线程处于运行状
       态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机
       就会把这个线程放到这个对象的锁池中。
  3)其他阻塞状态(Otherwise Blocked):当前线程执行了sleep()方法,或者调用了其他线程的
        join()方法,或者发出了I/O请求时,就会进入这个状态。线程会进入到阻塞状态。当sleep()状
        态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
6、sleep() 和 wait() 有什么区别?
 1)sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
 2)sleep() 不释放锁;wait() 释放锁。
 3)Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
 4)wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 
      notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)
      超时后线程会自动苏醒。
  调用 wait() 方法使用 if 块还是循环?
  wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满
  足,所以在处理前,循环检测条件是否满足会更好。
7、为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
 当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,然后释放这个对象
 锁并进入等待状态直到其他线程调用这个对象上的notify()方法。当线程需要调用对象notify()方法
 时,它会释放这个对象的锁,其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需
 要线程持有对象的锁,只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
8、线程的 sleep()方法和 yield()方法有什么区别?
  1)调用sleep()方法转入阻塞状态,调用yield()方法由运行状态转入就绪状态;
  2)sleep()方法给其他线程运行机会时不考虑线程的优先级,低优先级的线程会有运行的机会;
        yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
  4)sleep()方法比 yield()方法具有更好的可移植性,通常不建议使用yield()
9、notify() 和 notifyAll() 有什么区别?
  notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
  notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争
10、什么是线程同步和线程互斥,有哪几种实现方式?
   线程的同步:当一个线程对共享的数据进行操作时,在没有完成相关操作之前,不允许其他线程
                        打断它,否则,就会破坏数据的完整性。
                        在多线程应用中,当两个或多个线程之间同时等待对方释放资源的时候就会形成线
                        程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。
   线程互斥:若干个线程使用共享资源时,只允许一个线程去使用,其它要使用该资源的线程必须
                    等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
11、线程池 Executors类创建四种常见线程池什么是线程池?有哪几种创建方式?
   1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,
        也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新
        的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
   2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线
        程达到线程池的大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行
        异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用 
        newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
   3)newCachedThreadPool:创建可缓存的线程池。如果线程池的大小超过了处理任务所需要的
        线程,就会回收部分空闲的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理
        任务。此线程池不会对线程池大小做限制,依赖于操作系统能够创建的大线程大小。
  4)newScheduledThreadPool:创建一个大小无限的线程池。支持定时周期性执行任务。
12、线程池核心参数
   1)corePoolSize:指定了线程池中的线程数量。
   2)maximumPoolSize:指定了线程池中的最大线程数量。
   3)keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间
   4)unit:keepAliveTime 的单位。
   5)workQueue:任务队列,被提交但尚未被执行的任务。
         ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
         LinkedBlockingQueue :由链表结构组成的有界阻塞队列。
         PriorityBlockingQueue :支持优先级排序的无界阻塞队列。
         DelayQueue:使用优先级队列实现的无界阻塞队列。
         SynchronousQueue:不存储元素的阻塞队列。
         LinkedTransferQueue:由链表结构组成的无界阻塞队列。
         LinkedBlockingDeque:由链表结构组成的双向阻塞队列
   6)threadFactory:线程工厂,用于创建线程,一般用默认的即可。
   7)handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。
        AbortPolicy : 直接抛出异常,阻止系统正常运行。
        CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任
                                        务。任务提交线程的性能极有可能会急剧下降。
        DiscardOldestPolicy : 丢弃最老的一个请求,尝试再次提交当前任务。
        DiscardPolicy : 丢弃无法处理的任务,不予任何处理。
13、线程池原理
   1)线程池刚创建时,任务队列是作为参数传进来的。就算队列里面有任务,线程池也不会马上
        执行它们。
  2)当调用 execute() 方法添加一个任务时,线程池会做如下判断:
       a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
       b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
       c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建
           非核心线程立刻运行这个任务;
       d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛
           出异常 RejectExecutionException。
  3)当一个线程完成任务时,它会从队列中取下一个任务来执行。
  4)当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行
       的线程数大于 corePoolSize,那么这个线程就被停掉。线程池的所有任务完成后,它最终会收
       缩到 corePoolSize 的大小。
14、java线程池如何合理的设置大小
   CPU密集型、IO密集型、混合型
   1)CPU密集型
        尽量使用较小的线程池,一般Cpu核心数+1
        因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次
        数,带来额外的开销
   2)IO密集型
        方法一:可以使用较大的线程池,一般CPU核心数 * 2
        IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间
        方法二:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越
        少线程。
        最佳线程数目 = (线程等待时间 / 线程CPU时间  + 1)* CPU数目
   3)混合型
        可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软软的铲屎官

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值