进程和线程

  • 进程
    进程是操作系统分配资源的基本单位,是程序的一次执行过程。例如,在windows中,我们可以在任务管理器里看到运行中的进程。

  • 线程
    线程是操作系统调度的基本单位,它是比进程粒度更细的执行单位。一个进程是由一个或多个线程组成的,进程内的线程共享进程的资源。

  • 操作系统中进程的通信方式

1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全

  • Java线程得通信方式

1.使用synchronized关键字

/**
 * 示例对象
 */
public class DemoObject {

    public synchronized void method1(){
        System.out.println("this function name is method1");
    }

    public synchronized void method2(){
        System.out.println("this function name is method2");
    }
}
/**
 * 线程1
 */
public class Thread1 extends Thread{
    private DemoObject demoObject;

    public Thread1(DemoObject demoObject){
        this.demoObject = demoObject;
    }

    @Override
    public void run() {
        demoObject.method1();
    }
}
/**
 * 线程2
 */
public class Thread2 extends Thread {

    private DemoObject demoObject;

    public Thread2(DemoObject demoObject){
        this.demoObject = demoObject;
    }

    @Override
    public void run() {
        demoObject.method2();
    }
}
/**
 * 测试类
 */
public class Test01 {
    public static void main(String[] args) {
        //创建示例对象
        DemoObject demoObject = new DemoObject();
        //线程1
        Thread thread1 = new Thread1(demoObject);
        //线程2
        Thread thread2 = new Thread2(demoObject);

        thread1.start();
        thread2.start();
    }
}

由于我们将synchronized关键字修饰在实例方法,所以在执行该方法时,线程会在执行完该方法之前一直示例对象的对象锁;虽然线程1和线程2调用示例对象的方法不同,但不能同时获取对象锁,所以可以同步两个线程。
2.使用while循环
示例代码

/**
 * 线程1
 */
public class Thread1 extends Thread{
    private ArrayList<Integer> list;

    public Thread1(ArrayList<Integer> list){
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            list.add(i);
            System.out.println("count:"+i);
        }
    }
}
/**
 * 线程2
 */
public class Thread2 extends Thread {

    private ArrayList<Integer> list;

    public Thread2(ArrayList<Integer> list){
        this.list = list;
    }

    @Override
    public void run() {
        while(true){
            if(list.size() == 10){
                System.out.println("thread2 开始工作");
                System.out.println("thread2 结束工作");
                break;
            }
        }
    }
}
/**
 * 测试类
 */
public class Test01 {
    public static void main(String[] args) {
        //创建示例对象
        ArrayList<Integer> list = new ArrayList();
        //线程1
        Thread thread1 = new Thread1(list);
        //线程2
        Thread thread2 = new Thread2(list);
        thread1.start();
        thread2.start();
    }
}

只有当list的size等于10的时候,线程2才会开始工作;不过这样很浪费系统资源,因为线程2获得CPU使用权时只在轮询判断条件是否满足,不推荐使用这种方式。

3.使用notify和wait
示例代码

/**
 * 线程1
 */
public class Thread1 extends Thread{
    private ArrayList<Integer> list;

    public Thread1(ArrayList<Integer> list){
        this.list = list;
    }

    @Override
    public void run() {
        synchronized (list){
            for (int i = 0; i < 100; i++) {
                list.add(i);
                if(i == 10){
                    list.notify();
                    System.out.println("已经通知其他线程停止等待状态");
                }
                System.out.println("count:"+i);
            }
        }
    }
}
/**
 * 线程2
 */
public class Thread2 extends Thread {

    private ArrayList<Integer> list;

    public Thread2(ArrayList<Integer> list){
        this.list = list;
    }

    @Override
    public void run() {
        synchronized (list){
            if(list.size() < 10){
                System.out.println("不满足执行条件,进入等待状态");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("已被通知,停止等待");
            }
        }
    }
}
/**
 * 测试类
 */
public class Test01 {
    public static void main(String[] args) {
        //创建示例对象
        ArrayList<Integer> list = new ArrayList();
        //线程1
        Thread thread1 = new Thread1(list);
        //线程2
        Thread thread2 = new Thread2(list);

        thread2.start();
        thread1.start();
    }
}

在线程2发现不满足执行条件时,释放对象锁并进入等待状态;线程1发现当前满足线程1状态时,唤醒线程1,这样便减少了CPU资源的浪费,不过也可能提早唤醒线程2,但是并不满足线程2的条件,存在使线程2一直处于等待状态。

线程的生命周期

  • 创建

当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

  • 就绪

当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

  • 运行

线程获得CPU资源正在执行任务(run()方法),此时除非线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

  • 阻塞

由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

  • 死亡

一般有三种情况会使线程进入到死亡状态:run()或call()方法执行完成,线程正常结束、线程抛出一个未捕获的Exception或Error以及直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。

多线程的优缺点

  • 优点:一般情况下,可以提高系统的资源利用率。
  • 缺点:程序设计复杂,稍有不慎,可能会造成内存泄漏,死锁以及系统资源闲置等问题。

死锁

  • 示例代码

    public class DeadLockDemo extends Thread{
    
      private Object object1;
      private Object object2;
      public DeadLockDemo(Object o1,Object o2){
          this.object1 = o1;
          this.object2 = o2;
      }
    
      @Override
      public void run() {
          method1();
      }
    
      public void method1(){
          synchronized (object1){
              System.out.println("已经请求到了对象1的锁");
              System.out.println("正在请求对象2的锁");
              synchronized (object2){
                  System.out.println("已经请求到了对象2的锁");
              }
          }
      }
    }
public class DeadLockDemo2 extends Thread{
    private Object object1;
    private Object object2;
    public DeadLockDemo2(Object o1,Object o2){
        this.object1 = o1;
        this.object2 = o2;
    }
    @Override
    public void run() {
        method2();
    }
    public void method2(){
        synchronized (object2){
            System.out.println("已经请求到了对象2的锁");
            System.out.println("正在请求对象1的锁");
            synchronized (object1){
                System.out.println("已经请求到了对象1的锁");
            }
        }
    }
}
public class DeadLockTest {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();

        DeadLockDemo deadLockDemo1 = new DeadLockDemo(object1,object2);
        DeadLockDemo2 deadLockDemo2 = new DeadLockDemo2(object1,object2);

        deadLockDemo1.start();
        deadLockDemo2.start();
    }
}
  • 死锁的四个必要条件

1.资源互斥:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2.不可剥夺:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
3.占有且等待:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
4.形成环路:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。

完整工程代码链接:https://github.com/youzhihua/Java-training