zoukankan      html  css  js  c++  java
  • Java 多线程

    多线程

    进程: 正在进行中的程序 其实进程就是一个应用程序运行时的内存分配空间

    线程: 其实就是进程中一个程序执行控制单元 一条执行路径 进程负责的是应用程序的空间的标示 线程负责的是应用程序的执行顺序

    一个进程至少有一个线程在运行 当一个进程中出现多个线程时 就称这个应用程序是多线程应用程序 每个线程在栈区中都有自己的执行空间 自己的方法区, 自己的变量

    jvm在启动的时 首先有一个主线程 负责程序的执行 调用的是main函数 主线程执行的代码都在main方法中

    当产生垃圾时 收垃圾的动作 是不需要主线程来完成 因为这样 会出现主线程中的代码执行会停止 会去运行垃圾回收器代码 效率较低 所以由单独一个线程来负责垃圾回收

    1. 随机性的原理

    因为cpu的快速切换造成 哪个线程获取到了cpu的执行权 哪个线程就执行

    2. 返回当前线程的名称

    Thread.currentThread().getName();

    线程的名称是由: Thread-编号定义的 编号从0开始

    线程要运行的代码都统一存放在了run方法中

    3. 线程的启动

    线程要运行必须要通过类中指定的方法开启 start方法(启动后 就多了一条执行路径)

    start方法:

    1> 启动了线程

    2> jvm调用了run方法

    4. 创建线程的2种方式

    1> 继承Thread 由子类复写run()

    步骤

    a. 定义类继承Thread

    b. 目的是复写run方法 将要让线程运行的代码都存储到run方法中

    c. 通过创建Thread类的子类对象 创建线程对象

    d. 调用线程的start方法 开启线程 并执行run方法

    2> 实现一个接口Runnable

    步骤

    a. 定义类实现Runnable接口

    b. 覆盖接口中的run方法(用于封装线程要运行的代码)

    c. 通过Thread类创建线程对象

    d. 将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数 让线程对象明确要运行的run方法所属的对象

    e. 调用Thread对象的start方法 开启线程 并运行Runnable接口子类中的run方法

    5. 线程状态

    被创建: start();

    运行: 具备执行资格 同时具备执行权

    冻结: sleep(time), wait()—notify()唤醒, 线程释放了执行权 同时释放执行资格

    临时阻塞状态: 线程具备cpu的执行资格 没有cpu的执行权

    消亡: stop(); 

    6. 为什么要有Runnable接口的出现?

    1> 通过继承Thread类的方式 可以完成多线程的建立 但是这种方式有一个局限性 如果一个类已经有了自己的父类 就不可以继承Thread 因为java单继承的局限性 如果该类中的还有部分代码需要被多个线程同时执行 只有对该类进行额外的功能扩展 java就提供了一个接口Runnable 这个接口中定义了run方法 其实run方法的定义就是为了存储多线程要运行的代码 所以 通常创建线程都用第二种方式 因为实现Runnable接口可以避免单继承的局限性

    2> 其实是将不同类中需要被多线程执行的代码进行抽取 将多线程要运行的代码的位置单独定义到接口中 为其他类进行功能扩展提供了前提 所以Thread类在描述线程时 内部定义的run方法 也来自于Runnable接口 实现Runnable接口可以避免单继承的局限性 而且 继承Thread 是可以对Thread类中的方法 进行子类复写的 但是不需要做这个复写动作的话 只为定义线程代码存放位置 实现Runnable接口更方便一些 所以Runnable接口将线程要执行的任务封装成了对象 

    7. 多线程安全问题的原因

    1> 多个线程在操作共享数据

    2> 有多条语句对共享数据进行运算

    原因 这多条语句 在某一个时刻被一个线程执行时 还没有执行完 就被其他线程执行了 

    8. 解决安全问题的原理

    只要将操作共享数据的语句在某一时段让一个线程执行完 在执行过程中 其他线程不能进来执行就可以解决这个问题

    java中提供了一个解决方式 就是同步代码块

    格式:

    synchronized(对象) {  // 任意对象都可以 这个对象就是锁

        需要被同步的代码;

    }

     

    同步

    好处: 解决了线程安全问题

    弊端: 相对降低性能 因为判断锁需要消耗资源 产生了死锁

    1. 前提

    1> 必须要有两个或者两个以上的线程 才需要同步

    2> 多个线程必须保证使用的是同一个锁 

    2. 同步的二种表现形式

    1> 同步代码块: 其实就是将多句操作共享数据的代码进行封装

    2> 同步函数: 其实就是将同步关键字定义在函数上 让函数具备了同步性

    3. 同步函数是用的哪个锁呢?

    函数都有自己所属的对象this 所以同步函数所使用的锁就是this 

    4. 当同步函数被static修饰时 这时的同步用的是哪个锁呢?

    静态函数在加载时所属于类 这时有可能还没有该类产生的对象 但是该类的字节码文件加载进内存就已经被封装成了对象 这个对象就是该类的字节码文件对象 所以静态加载时 只有一个对象存在 那么静态同步函数就使用的这个对象 这个对象就是 类名.class

    5. 同步代码块和同步函数的区别?

    同步代码块使用的锁可以是任意对象

    同步函数使用的锁是this 静态同步函数的锁是该类的字节码文件对象

    6. 小结

    在一个类中只有一个同步 可以使用同步函数 如果有多同步 必须使用同步代码块 来确定不同的锁 所以同步代码块相对灵活一些 

    7. 多线程下 高效率的懒汉式单例(使用双重判断 效率优于同步机制)

    class Single{
        private static Single s = null;
        private Single() {}
        public static Single getInstance() { //锁是谁? 字节码文件对象
            if(s == null) {
                synchronized(Single.class) {
                    if(s == null)
                        s = new Single();
                }
            }
            return s;
        }
    }

    8. 同步死锁

    通常只要将同步进行嵌套 就可以看到现象 同步函数中有同步代码块 同步代码块中还有同步函数

    9. 线程间通信

    思路: 多个线程在操作同一个资源 但是操作的动作却不一样

    1> 将资源封装成对象

    2> 将线程执行的任务(任务其实就是run方法)也封装成对象

    10. 等待唤醒机制涉及的方法

    wait: 将同步中的线程处于冻结状态 释放了执行权 释放了资格 同时将线程对象存储到线程池中

    notify: 唤醒线程池中某一个等待线程

    notifyAll: 唤醒的是线程池中的所有线程

    注意:

    1> 这些方法都需要定义在同步中

    2> 因为这些方法必须要标示所属的锁 (A锁上的线程被wait 那这个线程就相当于处于A锁的线程池中 只能A锁的notify唤醒)

    3> 这三个方法都定义在Object类中 因为这三个方法都需要定义同步内 并标示所属的同步锁 既然被锁调用 而锁又可以是任意对象 那么能被任意对象调用的方法一定定义在Object类中

    11. waitsleep区别

    wait: 可以指定时间也可以不指定时间 不指定时间 只能由对应的notify或者notifyAll来唤醒

    sleep: 必须指定时间 时间到自动从冻结状态转成运行状态(临时阻塞状态) 

    wait: 线程会释放执行权 而且线程会释放锁

    Sleep: 线程会释放执行权 但不是不释放锁

    12. 线程的停止

    通过stop方法就可以停止线程 但是这个方式过时了

    停止线程的原理 就是让线程运行的代码结束 也就是结束run方法 一般run方法里肯定定义循环 所以只要结束循环即可

    第一种方式: 定义循环的结束标记

    第二种方式: 如果线程处于了冻结状态 是不可能读到标记的 这时就需要通过Thread类中的interrupt方法 将其冻结状态强制清除 让线程恢复具备执行资格的状态 让线程可以读到标记 并结束

    13. java.lang.Thread 常用方法

    1> Thread.sleep(10); 让线程停10毫秒

    2> interrupt(); 中断线程

    3> setPriority(int newPriority); 更改线程的优先级

    4> getPriority(); 返回线程的优先级

    5> toString(); 返回该线程的字符串表示形式 包括线程名称 优先级和线程组

    6> Thread.yield(); 暂停当前正在执行的线程对象 并执行其他线程

    7> setDaemon(true); 将该线程标记为守护线程或用户线程 将该线程标记为守护线程或用户线程 当正在运行的线程都是守护线程时 Java虚拟机退出 该方法必须在启动线程前调用

    8> join; 临时加入一个线程的时候可以使用join方法 A线程执行到了B线程的join方式 A线程处于冻结状态 释放了执行权 B开始执行 只有当B线程运行结束后 A才从冻结状态恢复运行状态执行

      

    Lock接口

    1. 多线程在JDK1.5版本升级时 推出一个接口Lock接口

    2. 解决线程安全问题使用同步的形式(同步代码块 要么同步函数)其实最终使用的都是锁机制 

    3. 到了后期版本 直接将锁封装成了对象 线程进入同步就是具备了锁 执行完 离开同步 就是释放了锁 在后期对锁的分析过程中 发现 获取锁 或者释放锁的动作应该是锁这个事物更清楚 所以将这些动作定义在了锁当中 并把锁定义成对象 所以同步是隐示的锁操作 Lock对象是显示的锁操作 它的出现就替代了同步 

    4. 在之前的版本中使用Object类中wait notify notifyAll的方式来完成的 那是因为同步中的锁是任意对象 所以操作锁的等待唤醒的方法都定义在Object类中

    5. 而现在锁是指定对象Lock 所以查找等待唤醒机制方式需要通过Lock接口来完成 Lock接口中并没有直接操作等待唤醒的方法 而是将这些方式又单独封装到了一个对象中 这个对象就是Condition Object中的三个方法进行单独的封装 并提供了功能一致的方法await(), signal(), signalAll()体现新版本对象的好处

      

  • 相关阅读:
    java移位的具体应用
    mysql计划任务(轮询执行脚本)
    算法题(1)
    transient关键字及Serializable的序列化与反序列化
    java后台调用短信接口,实现发送短信验证码的控制层实现
    防卫导弹
    C++ STL
    字母转换
    三分·三分求极值
    各种数据类型取值范围
  • 原文地址:https://www.cnblogs.com/huangyi-427/p/4705013.html
Copyright © 2011-2022 走看看