zoukankan      html  css  js  c++  java
  • 多进程与多线程

    一、进程

    1、什么是进程

    通过任务管理器看到了进程的存在,只有运行的程序才会出现进程

    进程:

    • 就是正在运行的程序,进程是系统进行资源分配和调用的独立单位。
    • 每一个进程都有它自己的内存空间和系统资源。

    2、多进程有什么意义?

    (1)单进程的计算机只能做一件事,而我们现在的计算机都可以做多件事情。

    • 举例:一边玩游戏,一遍听音乐
    • 现在的计算机都是支持多进程的,就可以在一个时间段内执行多个任务
    • 提高CPU的使用率

    二、线程

    1、什么是线程

    在同一个进程内又可以执行多个任务,而这每一个任务就可以看成是一个线程

    • 线程是进程的执行单元(执行路径)。是程序使用cpu的基本单位
    • 单线程:程序只有一条执行路径
    • 多线程:程序有多条执行路径

    2、多线程有什么意义

    (1)提高程序的使用率。

    • 程序的执行其实都是在抢CPU的资源,cpu的使用权。
    • 多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到cpu的执行权
    • 线程的执行有随机性

    3、线程的生命周期

    (1)新建:创建线程

    (2)就绪:有执行资格,没有执行权

    (3)运行:有执行资格,有执行权

    • 阻塞:由于一些操作让线程处于了该状态。没有执行资格,没有执行权。而另一些操作却可以把它给激活后处于就绪状态

    (4)死亡:线程对象变成垃圾,等待被回收

    图解:

    4、实现多线程的两种方式

    (1)继承Thread类

    • 自定义类MyThread类继承Thread类
    • 在MyThread类中重写run()
    • 创建MyThread类的对象
    • 启动线程对象
    package cn.itcast_01;
    public class MyThread extends Thread {
    
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(getName() + ":" + i);
            }
        }
    }
    package cn.itcast_01;
    public class ThreadDemo {
        public static void main(String[] args) {
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
            
            t1.setName("线程1");
            t2.setName("线程2");
            t1.start();
            t2.start();
        }
    }

    问题:

    • 为什么重写run()方法?

              run()里面封装的是被线程执行的代码

    • 启动线程对象用的是哪个方法?

            start()

    • run()和start()方法的区别?

              run直接调用仅仅是普通方法

              start()先启动线程,再由jvm调用run()方法

    (2)实现Runnable接口

    • 自定义类MyRunnable实现Runnable接口
    • 在MyRunnable里面重写run()
    • 创建MyRunnable类的对象
    • 创建Thread类的对象,并把上一步骤的对象作为构造参数传递
    package cn.itcast_02;
    public class MyRunnable implements Runnable {
    
        public void run() {
            for(int i = 0; i<100;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    package cn.itcast_02;
    public class MyRunnableDemo {
        public static void main(String[] args) {
            MyRunnable my = new MyRunnable();
            Thread t1 = new Thread(my, "线程1");
            Thread t2 = new Thread(my,"线程2");
            t1.start();
            t2.start();
        }
    }

     

    5、有了方式1,为什么还要方式2

    实现接口的好处

    • 可以避免由于Java单继承带来的局限性
    • 可以适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

    6、线程安全问题

    (1)出现的原因

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据

    (2)如何解决线程安全问题

    • 同步代码块
    synchronized(对象){
    需要同步的代码; }

    注意:同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能

    多个线程同一把锁,锁对象是任意对象

    • 同步方法

               把同步加在方法上

              锁对象:this

    • 静态方法

              把同步加载方法上

              这里的锁对象是当前类的字节码文件对象

     

    7、死锁问题

    两个或者两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象

    package cn.itcast_02;
    public class DieLock extends Thread {
        private boolean flag;
    
        public DieLock(boolean flag) {
            this.flag = flag;
        }
        public void run() {
            if(flag){
                synchronized (MyLock.objA){
                    System.out.println("if objA");
                    synchronized (MyLock.objB) {
                        System.out.println("if objB");
                    }
                }
            }else{
                synchronized (MyLock.objB){
                    System.out.println("else objB");
                    synchronized (MyLock.objA) {
                        System.out.println("else objA");
                    }
                }
            }
        }
    }
    package cn.itcast_02;
    public class DieLockDemo {
        public static void main(String[] args) {
            DieLock dl1 = new DieLock(true);
            DieLock dl2 = new DieLock(false);
    
            dl1.start();
            dl2.start();
        }
    }

    (1)死锁产生的原因

    •  系统资源的竞争:

    通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机,打印机等。

    只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会产生死锁的

    • 进程推进顺序法

    进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁

    信号量使用不当也会造成死锁。进程间相互等待对方发来的消息,结果也会使得这些进程间无法向前推进。

    例如,进程A等待进程B发的消息,进程B又在等待进程A发的消息,可以看出进程A和进程B不是因为竞争同一资源,而是在等待对方的资源导致死锁

    (2)产生死锁的四个必要条件:

    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
    • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

    (3)如何避免死锁

    • 加锁顺序(线程按照一定的顺序加锁)

    当多个需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易产生

    如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生

    • 加锁时限(线程尝试获取锁的时候加上一定的时限,并释放自己占有的锁)

    若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试,这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)

    • 死锁检测

    主要针对那些不可能实现按序加锁并且锁超时也不可行的场景

    每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。

    当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生

    做法:

    释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁 (编者注:原因同超时类似,不能从根本上减轻竞争)。

    一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。

    8、多线程状态转换图

    常见的情况:

    • 新建---就绪---运行---死亡
    • 新建---就绪---运行---就绪---运行---死亡
    • 新建---就绪---运行---其他阻塞---就绪---运行---死亡
    • 新建---就绪---运行---同步阻塞---就绪---运行---死亡
    • 新建---就绪---运行---等待阻塞---同步阻塞---就绪---运行---死亡

    9、线程池

    程序启动一个新线程成本是比较高的

    使用线程池可以很好的提高性能

    线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用

    方法一:

    public class MyRunnable implements Runnable {
    
        @Override
        public void run() {
            for(int i = 0; i< 100; i++)
                System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    /*
     * 线程池的好处:
     * 如何实现线程的代码?
     * A:创建一个线程池对象,控制要创建几个线程对象
     *       public static ExecutorService newFixedThreadPool(int nThreads)
     * B:这种线程池的线程可以执行
     *       可执行Runnable对象或者Callable对象代表的线程
     *       做一个类实现Runnable接口
     * C:调用如下方法
     *       Future<?> submit(Runnable task)
     *       <T> Future<T> submit(Callable<T> task)
     * */
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ExecutorsDemo {
        public static void main(String[] args) {
            //创建一个线程池对象,控制要创建几个线程对象
            ExecutorService pool = Executors.newFixedThreadPool(2);
            
            //可以执行Runnable对象或者CAllable对象表示的线程
            pool.submit(new MyRunnable());
            pool.submit(new MyRunnable());
            
            //结束线程池
            pool.shutdown();
        }
    }

    方法二:

    import java.util.concurrent.Callable;
    
    public class MyCallable implements Callable<Integer> {
        private int number;
    
        public MyCallable(int number) {
            this.number = number;
        }
    
        public Integer call() throws Exception {
            int sum = 0;
            for (int x = 1; x <= number; x++)
                sum += x;
            return sum;
        }
    }
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class CallableDemo {
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            ExecutorService pool = Executors.newFixedThreadPool(2);
            
            Future<Integer> f1 = pool.submit(new MyCallable(100));
            Future<Integer> f2 = pool.submit(new MyCallable(200));
            
            System.out.println(f1.get());
            System.out.println(f2.get());
            pool.shutdown();
    
        }
    }
  • 相关阅读:
    AJPFX总结java开发常用类(包装,数字处理集合等)(三)
    AJPFX总结java开发常用类(包装,数字处理集合等)(二)
    AJPFX总结java开发常用类(包装,数字处理集合等)(一)
    AJPFX关于面向对象之封装,继承,多态 (下)
    AJPFX关于面向对象之封装,继承,多态 (上)
    Android IntentFilter匹配规则
    细说Activity与Task(任务栈)
    androidStudio 打包与混淆
    Android activity之间的跳转和数据传递
    android开发中的 Activity 与 Context 区别与联系
  • 原文地址:https://www.cnblogs.com/yinqanne/p/9571593.html
Copyright © 2011-2022 走看看