多进程与多线程

一、进程

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();

    }
}
原文地址:https://www.cnblogs.com/yinqanne/p/9571593.html