• 多线程


    多线程

    进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。本质上就是一块内存空间。
    线程:就是进程中的程序执行的最小单元。

    一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。
    jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。

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


    随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。

    1. 关于运行线程代码,要知道的是:
    • 返回当前线程的名称:Thread.currentThread().getName()
    • 线程的名称是由:Thread-编号定义的。编号从0开始。(当然可以自己去命名)
    • 线程要运行的代码都统一存放在了run方法中。

    • 线程要运行必须要通过类中指定的方法开启。start方法。

    3.线程状态:

    它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换

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

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

    • notify 是随机唤醒。 notify全部释放
    1. 运行状态,如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

    2. 阻塞状态,当处于运行状态的线程失去所占用资源之后,便进入阻塞状态,有wait , sleep可以变成阻塞

    • wait 不会被被唤醒后是不会释放锁, sleep会释放锁
    1. 在线程的生命周期当中,线程的各种状态的转换过程!
    创建线程的第二种方式:实现一个接口Runnable

    1,定义类实现Runnable接口。
    2,覆盖接口中的run方法(用于封装线程要运行的代码)。
    3,通过Thread类创建线程对象;
    4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。
    为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。
    5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法

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

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

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

    wait(),notify(),notifyAll()用来操作线程为什么定义在Object类中,sleep 存在thread中?

    这些方法存在于同步中;
    使用这些方法必须标识同步所属的锁;
    锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。
    而sleep休眠是需要设定固定时间而不是通过锁的方式。

    
    new Thread(new Runnable(){  //匿名
    public void run(){
    System.out.println("runnable run");
    }
    })
    {
    public void run(){
    System.out.println("subthread run");
    }
    }.start();  //结果:subthread run
    
    Try {
    Thread.sleep(10);
    }catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu切换情况
    
    1. 多线程的安全问题:
      多线程环境下, 数据资源的争抢。
    2. 原因: 在某个时刻多条语句被一个线程执行的时候, 还没执行完, 就被其他的线程执行了。
    3. 解决方案: 同步代码块:
    synchronized(对象){
    	需要被同步的代码
    }
    

    同步

    好处:解决了线程安全的问题
    弊端:相对降低了性能, 因为判断锁需要消耗资源, 产生了死锁。

    1. 定义同步是有前提的:

    2. 必须是有两个以上的线程, 才需要同步

    3. 多个线程必须保证使用的是同一把锁

    4. 同步的第二种表现形式: 将同步关键字定义再函数上面, 让函数具备了同步性

    5. 同步函数用的是个锁, 函数都有自己所属的对象this, 所以同步函数所使用的锁就是本对象锁。

    6. 同步函数被static修饰的时候, 这个时候锁就是所属的类,也可以说是字节码文件对象, 也就是类名.class

    7. 同步代码块和同步函数的区别: 同步代码块使用的锁可以是任何对象

    8. 同步函数使用的锁是this, 静态同步函数的锁是字节码文件对象。再一个类中只有一个同步, 可以使用同步函数

    延迟加载的单例模式:
    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;
    }
    }
    
    1. 同步死锁:将同步进行嵌套的时候就可以看见同步死锁
    2. 线程间的通信:思路:多个线程按照序列操作同一个资源,仓储模式
    • 将资源封装成对象
    • 将线程执行的任务(run)方法也封装成了对象
    1. 等待唤醒机制:
    • wait:将线程对象存储到线程池
    • notify:到线程池唤醒对象
    • notifyall: 唤醒线程池中所有的纤层
      注意点:
    • 这些方法都需要定义再同步中, 因为要标识所属的锁。
    • 者三个方法都定义再Object类中,因为三个方法都需要定义同步内,并标识同步锁, 而同步锁可以定义为任何对象, 所以这个对象最好的选择就是object
    wait和sleep区别:

    wait可以指定时间也可以不指定时间,会释放执行权
    sleep必须指定时间,不会释放执行权

    线程停止:

    1.通过定义循环的结束标记
    2. 通过interrupt中断线程

    关于优先级的问题:

    1. 设置优先级, 只会再大体上进行控制,就是说数量达到一定的程度的时候才可以。
    2. 而要真正的实现只有通过join和interrupt才可以终端线程, 而不是通过yield让步和设置优先级
    3. setDamon设置,将该线程标记为守护线程, 或者是用户线程
    4. toString可以返回线程的许多信息。 setProprity,getProprity.
    Lock接口:

    同步是隐示的锁操作,而Lock对象是显示的锁操作

    < java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
    --------------------------------------------------------
    class BoundedBuffer {
       final Lock lock = new ReentrantLock();
       final Condition notFull  = lock.newCondition();
       final Condition notEmpty = lock.newCondition();
       final Object[] items = new Object[100];
       int putptr, takeptr, count;
       public void put(Object x) throws InterruptedException {
         lock.lock();
         try {
           while (count == items.length)
             notFull.await();
           items[putptr] = x;
           if (++putptr == items.length) putptr = 0;
           ++count;
           notEmpty.signal();
         }
    finally {
           lock.unlock();
         }
       }
       public Object take() throws InterruptedException {
         lock.lock();
         try {
           while (count == 0)
             notEmpty.await();
           Object x = items[takeptr];
           if (++takeptr == items.length) takeptr = 0;
           --count;
           notFull.signal();
           return x;
         }
    finally {
           lock.unlock();
         }
       }
    }
    

    API:

    (Application Programming Interface,应用程序编程接口)就是让别人通过访问封装好的类, 而不用去理解底层直接调用即可。

  • 相关阅读:
    Leetcode Binary Tree Level Order Traversal
    Leetcode Symmetric Tree
    Leetcode Same Tree
    Leetcode Unique Paths
    Leetcode Populating Next Right Pointers in Each Node
    Leetcode Maximum Depth of Binary Tree
    Leetcode Minimum Path Sum
    Leetcode Merge Two Sorted Lists
    Leetcode Climbing Stairs
    Leetcode Triangle
  • 原文地址:https://www.cnblogs.com/jwlxtf/p/7932357.html
Copyright © 2020-2023  润新知