进程与线程2

进程与线程

程序:为了完成特定任务,用某种编程语言编写的一组指令的集合,即指一段静态的代码,静态对象;

进程:APP

  • 进程是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;

  • 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。

  • 进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算

  • 特点

    1. 独立性:进程是系统中独立运行的实体,可以拥有自己的独立资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
    2. 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
    3. 并发性:多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.

一个Java 应用程序,Java.exe其实最少因该有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程,如果发生异常则会影响主线程

线程

概念:线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.是一个程序内部的一条执行路径。
一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
我们看到的进程的切换,切换的也是不同进程的主线程
多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能

进程与线程的关系

  • 线程必然属于某一个进程,线程要运行必须要有相应的资源,而进程就是这个资源的提供者,所以线程存在于进程中。所以进程=线程+资源。
  • 总结:线程是具有能动性、执行力、独立的代码块。进程=线程+资源。执行流、调度单元、运行实体都是针对线程而言的,线程才是解决问题的思路、步骤,只有它才能上处理器运行。
  • 进程和线程的区别是线程没有自己独享的资源,因此没有自己的地址空间,它要依附在进程的地址空间中从而借助进程的资源运行
  • 其实一个java.exe程序至少有三个线程:一个主线程main(),gc()垃圾回收线程,异常处理线程;

创建线程的方式一共四种:

jkd5.0新增2种

多线程

特性:

  1. 随机性一个CPU【单核】只能执行一个进程中的一个线程。

    • 是因为CPU以纳秒级别甚至是更快的速度高效切换着,看起来像同时运行

    • 串行是指同一时刻一个CPU只能处理一件事,类似于单车道
      并行是指同一时刻多个CPU可以处理多件事,类似于多车道

    • 并发:一个CPU处理多个线程

  2. 分时调度:时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
    注意:我们无法控制OS选择执行哪些线程,我们控制不了OS的时间片分配,OS底层有自己规则,如:

    • FCFS(First Come First Service 先来先服务算法)
    • SJS(Short Job Service短服务算法
  3. 线程的状态:三态模型

    就绪: 可运行状态,只要获得CPU,就可立即执行 start()

    执行:运行状态,正在执行的状态 --在时间片内

    堵塞:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程运行堵塞。

4.可以再添加2种状态

  • 创建状态new:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中

  • 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统

    执行完run方法或者直接调用stop()方法会让线程立刻死亡,或者出现异常未处理;

    • 操作系统为每一个进程都提供了一个PCB:程序控制块,它记录着与此进程相关的信息:进程状态、优先级、PID等等。

      每个进程都有自己的PCB,所有的PCB都放在一张表格中维护,这就是进程表。调度器根据这个表来选择处理器上运行的进程。我们设计的系统中PCB只占一页:4K。

多线程实现方法

方法一:继承thread类

  • 1.自定义一个类,继承thread类。

  • thread类中的常用方法:

  • 1.start():启动线程和调用run方法;
    2.run():重写run()方法,写需要执行的代码
    3.currentThread():静态方法,可以直接由Thread类调用,返回当前正在执行的线程
    4.getname():获取线程名
    5.setname():修改线程名
    6.yield():释放当前CPU的执行权;
    7.join():在线程a中调用线程b的join方法,此时会执行线程b,线程a处于阻塞的状态,直到线程B完全执行完毕,a才解除阻塞,进入就绪状态;
    8.sleep():可以设定时间,让线程每隔多少时间执行一次;    
    
  • 2.在自定义类中重写父类的run()方法,这个方法里放自己的业务。

  • 3.创建自定义线程类对象–对应的是线程的新建状态

  • 4.调用start方法,以多线程方式将线程对象加入到就绪队列中等待OS选中,调用start方法,JVM会自动调用run()方法

  • run()和start()区别:

  • run():主要用来封装我们自定义的代码业务,直接调用本方法相当于普通方法的调用

  • start():主要用来以多线程的方式启动线程,然后由**jvm调用本线程的run()**方法执行业务。

  • 注意:这里的启动指的是将线程对象加入到就绪队列,具体什么时间执行要看OS调用。

//定义一个线程类继承Thread类
class MyThread extends Thread{
    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            /*
            public final String getName() {
                   return name;}
             */
            //获取线程的名字
      System.out.println(getName()+"="+i);//getName()是父类的方法,子类继承之后可以直接调用
      //对应的是线程的新建状态
        MyThread mt1 = new MyThread();
        MyThread mt2= new MyThread();
        MyThread mt3 = new MyThread();
        MyThread mt4 = new MyThread();
        //测试1:如果自己调用run方法,只是一个普通的方法,不能起到多线程的效果。
//        mt1.run();
//        mt2.run();
//        mt3.run();
//        mt4.run();
        //测试二:调用start方法,以多线程方式启动,调用start方法,JVM会自动调用run()方法
        mt1.start();/*对应的是线程的就绪状态*/
        mt2.start();
        mt3.start();
        mt4.start();       
 

方法二:实现Runnable接口

  • 1.自定义多线程类,并实现Runnable接口

  • 2.重写接口中的抽象run()方法,run()方法里放着我们的业务

  • 3.创建自定义对象,只创建一次,作为业务对象存在

  • 4.创建多个Thread线程对象,并将业务对象交给线程对象来完成

  • 5.以多线程的方式启动多线程对象。

  • 注意:在获取执行中的线程的名字时,先通过Thread.currentThread(),因为它返回的是正在执行的线程对象的引用,然后通过引用.来调用getName()方法,获取名字。

  • 关于如何获取Thread中的Start()方法,只需要用到Thread的构造方法:

    public Thread(Runnable target):括号里面可以等价于Runnable target=new MyRunnable();

    代表的接口类型引用变量指向接口实现类的对象。然后将这个对象当成参数传入Thread的构造方法。—这也表示所有 的线程共享此一个实现类的对象。

 @Override
    public void run() {
        //完成业务:打印10此当前正在运行的线程名
        for (int i = 0; i <10 ; i++) {
            /*
            由于自定义类和接口中都没有获取名字的方法,所以需要到Thread类里找
            public static native Thread currentThread()
            currentThread():静态方法,获取当前正在执行的线程对象
            getName():获取当前正在执行的线程对象的名称
             */
            System.out.println(Thread.currentThread().getName()+"="+i);
        }
        
        public static void main(String[] args) {
        Runnable target = new MyRunnable();//接口类型引用变量指向接口的实现类
        //public Thread(Runnable target)--Thread的构造方法
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        //以多线程的方式启动线程
        t1.start();
        t2.start();
        t3.start();
通过匿名内部类实现接口:
        public static void main(String[] args) {
        new MyRunnable().go();
    }
}
class MyRunnable{
    public void go() {
        //创建匿名内部类来实现接口
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <10 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"="+i);
                }
                System.out.println("匿名内部类,实现Runnable,启动线程");
            }
        };
        //通过线程类调用start方法。
        Thread tread1 = new Thread(r);
        Thread tread2 = new Thread(r);
        Thread tread3 = new Thread(r);
        tread1.start();
        tread2.start();
        tread3.start();
    }

使用

对于以上2种创建方式:优先使用实现Runnable接口

  • 首先是java是单继承结构,如果采用继承Thread类,会对本类的后续继承产生影响;
  • 如果需要多线程共享数据,那么实现接口比较方便,不需要创建静态属性;
创建多线程方式三:线程池
Excutors是用来辅助创建线程池的工具类
         public static ExecutorService newFixedThreadPool(int nThreads)
          常用方法:newFixedThreadPool(int)这个方法可以创建指定数目线程的线程池对象
          public interface ExecutorService extends Executor
          创建出来的线程池对象就是ExecutorService,用来新建/启动/销毁 线程*/
        ExecutorService pool = Executors.newFixedThreadPool(4);
        //使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService
        for (int i = 0; i <5 ; i++) {
        //execute(target)让线程池中的线程来执行业务,每次调用都会将一个线程加入就绪队列。
            pool.execute(target);//本方法的参数就是需要执行的业务,也就是目标业务类对象
        }
等价于:Thread t1=new Thread(target);
       Thread t2=new Thread(target);
       Thread t3=new Thread(target);
       Thread t4=new Thread(target);
       t1.start();
       t2.start();
       t3.start();
       t4.start();

方法四 实现Callable 接口

  • 与Runnble接口相比 Callable功能更加强大,效率更高;

  • 相比run()方法 可以有返回值

  • 方法可以抛出异常,被外面的操作捕获 获取异常信息

  • 支持泛型的返回值 \

  • 需要借助FutureTask类,比如获取返回值

  • 步骤:

  • 1.继承Callable接口
    2.重写接口中的call方法
    3.创建实现类的对象
    4.创建FutureTask类对象 
    5.调用FutureTask类中的get方法 来接收线程的返回值
    6.调用Thread类中的start方法启动线程
    
  • //1.创建类实现Callable接口
    class NumThread implements Callable {
        //1.1重写接口中的call方法  类似Runnable接口中的run方法;
                //将此线程需要的操作放在此方法中
        @Override
        public Object call() throws Exception {
            int sum=0;
            for (int i = 1; i <=100 ; i++) {
                if (i%2==0){
                    System.out.println(i);
                     sum+=i;
                }
            }
            //1.2返回值类型为Object类型 自动装箱 Integer;
            return sum;
    
        }
    }
    public class ThreadNew {
        public static void main(String[] args) {
            NumThread num=new NumThread();
            //2.要实现Callable 还需要借助FutureTask
                //FutureTask的构造器参数为Callable接口的实现类对象
            FutureTask futureTask = new FutureTask<>(num);
              //4.启动线程的话还需要调用Thread类中的start方法;
            new Thread(futureTask).start();
            try {
                //3.通过FutureTask类对象调用get方法来接call方法的返回值
                Object sum = futureTask.get();
                System.out.println(sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
    

如何判断线程是否存在安全问题

在多线程程序中+有共享数据+多条语句操作共享数据

当同时出现上面3中情况,就存在安全问题!!!—多线程在抢占时会出现重载或者过载的情况!!

  • 由于业务的需要,当前业务是需要数据共享的,所以只能在第三点上进行操作

    把可能出现问题的代码包裹起来,一次只让一个线程执行。

同步和异步
  • 同步:A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去

  • 异步:A线程要请求某个资源,但是此资源正在被B线程使用中,因为没有同步机制存在,A线程仍然请求的到,A线程无需等待

  • 什么时候使用同步:

    • 只要在几个线程之间共享非 final 变量,就必须使用synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改
    • 因为多线程将异步行为引进程序,所以在需要同步时,必须有一种方法强制进行。例如:如果2个线程想要通信并且要共享一个复杂的数据结构,如链表,此时需要确保它们互不冲突,也就是必须阻止B线程在A线程读数据的过程中向链表里面写数据(A获得了锁,B必须等A释放了该锁)。
解决方案
方法一:同步代码块

同步锁synchronized–同步的

锁的范围:操控共享数据的代码,即为需要被同步的代码

当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的

锁对象必须保证唯一,才能出现同步的效果!

写法

synchronized (锁对象){undefined
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}

public void run() {
        while (true){
            /*同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
              在同步代码块中的代码,同一时刻只会被一个线程执行
              注意:同步代码块必须保证所有线程对象使用同一把唯一的锁
              锁对象必须唯一,锁对象的类型不做限制,唯一就行。
            * */
            // synchronized (Object o)这些写是不对的,锁不唯一
               synchronized (o){  //上面new了一次,所以锁是唯一的。
                   if (tickets>0){
                       try {
                           Thread.sleep(1);//强制休眠
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       //打印当前正在售票的线程,每次售票,票数减1
                       System.out.println(Thread.currentThread().getName()+"="+tickets--);
                   }
                   if (tickets<=0) break;//注意设置循环出口
               }
               }
    
    
     public void run() {
        /*我们每通过class关键字创建一个类,就会在工作空间生成唯一类名.class字节码文件
        这个类名.class对应的对象,我们称之为字节码对象
        字节码对象及其重要,是反射技术的基石,字节码对象包含了当前类所有关键信息
        所以,用这样一个唯一且明确的对象作为同步代码块的锁对象,再合适不过了。
        */
        while (true) {
            synchronized (MyTreadV2.class) {
                if (sum > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "=" + sum--);//getName()是父类的方法,子类继承之后可以直接调用
                }
                if (sum <= 0) break;
            }
        }

使用前提:1.必须有2个及以上的线程,单线程不存在多线程安全问题

​ 2.多个线程之间必须使用同一把锁,也就是锁必须是唯一的。

方法二:同步方法

注意:

  • 共享数据如果操作共享数据的代码完整的声明在一个方法中,就可以把此方法声明成同步的;

  • 同步监视器:锁是系统默认存在,不需要显示声明;

一:当通过Runnable接口去实现多线程时:非静态同步方法 就创建一个实现类的对象 全局共享
public synchronized void show () {}
此时存在默认的同步监视器(锁):this-- 代表的是本类的对象
  
二:当通过继承Thread类实现多线程时:静态同步方法
public static synchronized void show () {}
此时存在默认的同步监视器为:Sail.class -- 代表当前类本身
当通过Runnable接口去实现多线程时
class Sail implements Runnable{
        int tickets=100;
    @Override
  //2.通过run方法去调用同步方法;
    public void run() {
        while (true) {
            show();
        }
    }
  //1.show方法里面包含操作共享数据的代码
    public synchronized void show () {
            if (tickets > 0) {
       System.out.println(Thread.currentThread().getName() + "=" + tickets);
            tickets--;
            }

        }
  
通过继承Thread类实现多线程时  
  public class MyThread extends Thread{

    private static int tickets=100;

    @Override
    public void run() {

        while (true) {
            //在操作共享数据的代码上面加锁(同步监视器)
           show();
        }
    }

    private static synchronized void show(){  //此时锁this有4个  不唯一 需要加static 此时锁变成MyThread.class

            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "=" + tickets--);
            }
    } 

特点:

  1. synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一!

  2. synchronized同步关键字可以用来修饰方法,称为同步方法

  3. 同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的

  4. 但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了

    注意:

    为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
    因为同步代码块可以保证同一个时刻只有一个线程进入
    但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步

方法三:lock锁

概述:JDK5.0之后,java提供了更强大的线程同步机制

  • 通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当
  • Lock接口是控制多个线程对共享资源进行访问的工具;
    • 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock锁对象加锁;
    • 线程开始访问共享资源前现获取Lock对象
  • ReentrantLock类实现了Lock接口,它拥有和synchornized相同的并发性和内存语义,可以显示加锁和解锁;

举例:卖票问题

class Window implements Runnable{

     private int tickets=100;
     //1.创建ReentrantLock对象
     private ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {

        while (true){
            try { 
 对共享数据进行加锁 //2.调用lock()方法进行上锁;
                lock.lock();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets>0){                    System.out.println(Thread.currentThread().getName()+"="+tickets--);
                }
                else break;
            }finally {
执行完之后进行解锁 //执行完之后解锁unlock()
                lock.unlock();
            }
        }
    }
打印结果:
  public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }

synchornized与lock的异同

相同

  • 都可以解决线程的安全问题

不同

  • synchornized机制在执行完相应的同步代码后 自动释放锁
  • lock需要手动的启动同步(lock()方法), 结束时也需要手动去实现(unlock()方法)
  • 使用lock锁,jvm将花费较少的时间来调度线程,性能更好,并且扩展性较好,提供更多的子类;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值