Java多线程

作者: 情安 | 来源:发表于2021-06-27 22:44 被阅读0次

    1.进程

    1.介绍
    就是正在运行的程序。也就是代表了程序锁占用的内存区域。

    2.特点

    • 独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

    • 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。

    • 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

    3.cpu的分时调度
    时间片,即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。
    如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。

    2.线程

    1.介绍
    线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程。多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。简而言之,一个程序运行后至少一个进程,一个进程里包含一个线程(单线程)或者多个线程。


    2.进程和线程的关系
    一个操作系统中可以有多个进程,一个进程中可以有多个线程。每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。

    3.并行和并发
    并行:指在同一时刻,有多条指令在多个处理器上同时执行。
    并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,及多个事件在同一时间间隔发生。

    3.多线程的特性

    1.随机性


    2.线程状态
    线程生命周期,总共有五种状态:
    1. 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
    2. 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
    3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
    4. 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
    5. 根据阻塞产生的原因不同,阻塞状态又可以分为三种:
      a) 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
      b) 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
      c) 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
    6. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。


    4.多线程创建:继承Thread

    1.介绍
    Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类直接extends Thread, 并复写run()方法,就可以启动新线程并执行自己定义的run()方法。模拟开启多个线程,每个线程调用run()方法。
    2.常用方法

    Thread()     分配新的 Thread 对象。
    Thread(String name)    分配新的 Thread 对象。
    Thread(Runnable target)        分配新的 Thread 对象。
    Thread(Runnable target, String name)          分配新的 Thread 对象。
    static Thread currentThread()          返回对当前正在执行的线程对象的引用。
    long getId()          返回该线程的标识符。
    String getName()          返回该线程的名称。
    void run()          如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;
    static void sleep(long millis)          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
    void start()          使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
    

    案例

    package cn.tedu.thread;
    //这个类用来测试多线程,编程方式1:extends Thread
    public class Test1_Thread { 
        public static void main(String[] args) {
           //4,创建Thread对象
           MyThread t = new MyThread();//新建状态
           //5,启动线程调用start(),JVM会自动调用run()的业务 ---使线程从新建转成 可运行状态,等待CPU调度
           t.start();
           //---6,模拟多线程,需要启动最少2个线程叫多线程程序,如果只启动一个线程,叫单线程程序。
           MyThread t2 = new MyThread();
           t2.start();//---使线程从新建转成 可运行状态,等待CPU调度
           //7,run()和start()区别是什么?run()在执行时只是当做一个普通方法在执行,并没有多线程效果
    //     t.run();
           //8,线程的随机性:CPU会自动调度可运行状态的线程们,但是哪个时间片执行哪个线程我们无法控制
    //     0=Thread-0
    //     0=Thread-1
    //     1=Thread-0
    //     1=Thread-1
    //     2=Thread-0
        }
    }
    //1、自定义多线程类,方式1:extends Thread
    class MyThread extends Thread{
        //2、重写了Thread里的run()alt+/,把业务放入run()
        @Override
        public void run() {
    //     super.run();//默认使用Thread类里的业务
           //3、写业务? 输出10次当前的线程名称
           for (int i = 0; i < 10; i++) {
               //getName() 可以获取正在执行任务的线程名称
               System.out.println(i+"="+getName());
           }
        }
    }
    

    5.多线程创建:实现Runnable接口

    1.介绍
    Runnable接口是线程辅助类,仅定义了一个方法run()方法。Runnable可以继承其他类实现对Runnable实现类的增强,避免了Thread类由于继承Thread类而无法继承其他类的问题。Runnable接口的run()方法可以被多个线程共享,适用于多个进程处理一种资源的问题。
    2.案列

    package cn.tedu.thread;
    //这个类用来测试多线程,编程方式2:implements Runnable
    public class Test2_Runnable {
        public static void main(String[] args) {
           //4,创建线程对象
           MyRunnable target = new MyRunnable();
           //5,问题:怎么把接口的实现类和Thread类绑定
           Thread thread = new Thread(target,"熊大");
           //6,启动线程?
           thread.start();
           //7,--模拟多线程编程,需要创建多个线程对象并启动
           Thread thread2 = new Thread(target,"熊二");
           thread2.start();
           //8,自己测试start()和run()的区别?run()只是一个普通方法执行的效果也就是单线程顺序执行效果,没有多线程现象
           //9,自己修改线程名称?--使用Thread类的含参构造
        }
    }
    //1,自定义多线程类 ,方式2:implements Runnable
    class MyRunnable implements Runnable{
        //2,把业务放入run(),重写了Runnable接口里的
        @Override
        public void run() {
           //3,写业务,打印10次线程名称
           for (int i = 0; i < 10; i++) {
               //问题:Runnable接口中,没有提供多余的方法唯独只有一个run()
        //Thread.currentThread()获取当前正在执行业务的线程对象 .getName()名称
               System.out.println(Thread.currentThread().getName()+"="+i);
           }
        }
    }
    

    6.线程锁

    synchronized 互斥锁(悲观锁,有罪假设):采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
    ReentrantReadWriteLock 读写锁(乐观锁,无罪假设):ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
    二种方式的区别:需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
    与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。

    7.售票案例

    设计4个售票窗口,总计售票100张。
    用多线程的程序设计并写出代码。

    package cn.tedu.thread;
    //这个类用来测试多线程编程的同步锁
    public class Test5_Synchrionized {
        public static void main(String[] args) {
           TicketSync target = new TicketSync();
           //1、线程池ExecutorService:用来存储线程的池子。把新建线程,启动线程,关闭线程的任务交给池子来处理。
           //2、Executors用来辅助创建线程池对象,newFixedThreadPool()创建具有固定线程数的线程池
           ExecutorService pool = Executors.newFixedThreadPool(5);
           for (int i = 0; i < 5; i++) {
               //3、execute()让线程池执行任务,每次执行都会启动一个线程
               pool.execute(target);
           }
        }
    }
    // 改造多线程售票案例
    class TicketSync implements Runnable {
        int tickets = 100;
        Object o = new Object();
        //4、如果方法里的代码通通被 同步了,直接把方法用同步修饰,称之为同步方法,用的锁对象this
        @Override
    //  synchronized public void run() {
        public void run() {
           while(true) {
               //同步代码块:是指,同一时间资源会被独占没人抢
    // 1、多线程中数据安全隐患的前提: 多线程程序 + 有共享数据(成员变量) + 多条语句操作共享数据
    //2、同步锁的位置:太大也不行,程序的效率太低。太小也不行,就相当于给自己加了一把锁,从问题发生的位置开始,截止到结束的位置
    //3、锁对象:多线程间使用的必须是同一把锁对象,同步代码块的锁对象可以是任意对象
    //         synchronized(new Object()) {//不对,相当于每个线程用了自己的锁,多个线程间并不是用的同一个锁
    //         synchronized (o) {//让线程间使用了同一个o对象
               synchronized (this) {//让线程间使用了同一个o对象
                  if(tickets>0) {
                      try {
                         Thread.sleep(10);//延迟访问
                      } catch (InterruptedException e) {
                         e.printStackTrace()
                      }
                      System.out.println(Thread.currentThread().getName()+"="+tickets--);
                  }
                  if(tickets<=0) break;
               }
           }
        }
    }
    

    相关文章

      网友评论

        本文标题:Java多线程

        本文链接:https://www.haomeiwen.com/subject/sqloyltx.html