一、实现多线程的方法

1、继承Thread类,重写run( )方法
2、实现Runnable接口,重写run( )方法
3、实现Callable接口,重写call( )方法并使用FutureTask获取call( )方法的返回结果
利用Thread类实现多线程

二、多线程高级知识

1、线程五大状态,

创建状态,就绪状态,阻塞状态,运行状态,死亡状态

image.png

2、线程常用方法

image.png

  1. 五种状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;

1)线程状态

  • 停止:stop()方法已经丢弃
  • 休眠:sleep()睡眠多少秒mm为单位
  • 礼让:yield()方法表示,暗示调度器让当前线程出让正在使用的处理器。调度器可自由地忽略这种暗示。也就是说让或者不让是看心情哒
  • 合并线程:join()方法表示待此线程执行完成后,再执行其他线程,其他线程阻塞
    2)线程优先级
    Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,调度线程按照优先级决定应该调度到哪个线程来执行。
    线程的优先级用数字表示,范围从1-10
    Thread.MIN_PRIORITY=1
    Thread.MAX_PRIORITY=10
    Thread.NORM_PRIORITY=10
    使用以下方式改变或获取优先级
    getPriority(),setPriority(int xxx)


    3、线程的一些概念


    1)线程同步
    形成条件:队列+锁

锁机制sychronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后立即释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

4、多线程造成的问题和解决方案

1)线程的三大不安全案例

  • 往一个池子里加东西两个线程同时拿到这个池子往里面加东西会造成只有一个线程加上。
  • 多个线程去获取一个资源,不断减少它,直到最后两个线程拿到那个资源去减少它会导致那个资源出现负数。
  • 两个线程同时取钱,有100,一个线程取了50,一个线程取了100两个线程同时执行导致最后只有100却去了150

2)同步块或者同步方法去

  1. 同步块或者同步方法去解决线程不安全,即使用sychronized关键词。
  2. sychronized修饰方法的局限性
        sychronized 多线程中使用的方法()
    • sychronized方法默认锁this对象,当多线程去修改的资源不是你锁的对象的话无法解决多线程造成的问题
  3. 同步块:sychronized(obj){ },obj资源会被锁。
    • 可以指定锁的对象,对那个对象的增删改查操作都会多线程上锁。
      3)死锁
      多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形某一个同步块同时拥有”两个以上对象的锁“时,就可能会发生死锁问题。
  4. 避免死锁的方法

image.png
4)lock锁

  • jdk5.0开始,java提供了更强大的线程同步机制—-通过显示定义同步锁对象来实现同步。同步锁使用lock对象充当。
  • java.lang.utils.lock.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象访问,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock(可重入锁)实现了Lock,它拥有与sychronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock可以显示的加锁释放锁。

5)sychronized与lock对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchrorted是隐式锁出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时问来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在法体之外)

5、线程协作

1)生产着与消费者模式

这是一个线程同步问题,生产者与消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件:

  • 对于生产者,没有生产产品之前,要通知消费者等待,而产生了产品后又要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
  • 在生产者消费问题中,仅有synchronized是不够的
    • synchronized可阻止并发跟新同一个共享资源,实现同步
    • synchronized不能用来实现不同线程之间的消息传递(通信)

解决方法1:

并发协作模式”生产者/消费者模式”—>管程法

  1. package com.yinxin.consumer;
  2. //测试:生产者消费者模型-->利用缓冲区解决:<<管程法>>
  3. //生产者,消费者,产品,缓冲区
  4. public class consumerAndProductor {
  5. public static void main(String[] args) {
  6. //容器
  7. SynContainer synContainer = new SynContainer();
  8. //让生产者和消费者开始跑
  9. new Productor(synContainer).start();
  10. new Consumer(synContainer).start();
  11. }
  12. }
  13. //生产者
  14. class Productor extends Thread {
  15. SynContainer container;
  16. public Productor(SynContainer container){
  17. this.container=container;
  18. }
  19. //生产
  20. @Override
  21. public void run() {
  22. for (int id = 0; id < 100; id++) {
  23. container.push(new Chicken(id));
  24. System.out.println("生产了"+id+"只鸡");
  25. }
  26. }
  27. }
  28. //消费者
  29. class Consumer extends Thread {
  30. SynContainer container;
  31. public Consumer(SynContainer container){
  32. this.container=container;
  33. }
  34. @Override
  35. public void run() {
  36. for (int i = 0; i < 100; i++) {
  37. System.out.println("消费了-->"+container.pop().id+"只鸡");
  38. }
  39. }
  40. }
  41. //产品
  42. class Chicken {
  43. int id;//产品编号
  44. public Chicken(int id) {
  45. this.id = id;
  46. }
  47. }
  48. //缓冲区
  49. class SynContainer {
  50. //需要一个容器大小
  51. Chicken[] chickens = new Chicken[10];
  52. //容器计数器
  53. int count = 0;
  54. //生产者放入产品
  55. public synchronized void push(Chicken chicken) {
  56. //如果容器满了,就需要等待消费者消费
  57. if (count == chickens.length) {
  58. //通知消费者消费。生产等待
  59. try {
  60. this.wait();
  61. } catch (InterruptedException e) {
  62. e.printStackTrace();
  63. }
  64. }
  65. //如果没有满,我们就需要丢入产品
  66. chickens[count] = chicken;
  67. count++;
  68. //可以通知消费者消费了。
  69. this.notifyAll();
  70. }
  71. public synchronized Chicken pop() {
  72. //判断能否消费
  73. if (count == 0) {
  74. //等待生产者生产,消费者等待**
  75. try {
  76. this.wait();
  77. } catch (InterruptedException e) {
  78. e.printStackTrace();
  79. }
  80. }
  81. //如果可以消费
  82. count--;
  83. Chicken chicken = chickens[count];
  84. //通知生产者生产**
  85. this.notifyAll();
  86. //消费者消费产品
  87. return chicken;
  88. }
  89. }

这里还是讲解一下wait、notify和notifyAll吧。 这三个都是Object类里的方法,可以用来控制线程的状态。

  • 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
  • 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
  • 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行

6、线程池

image.png

image.png

三、锁机制

悲观锁:总是假设会遇到最坏的情况,每次进行数据的读取时候都默认其他线程会对数据修改,所以需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。独占锁和共享锁都是悲观锁。会产生死锁。
乐观锁:乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。不会产生死锁。