欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

操作系统编程实践课程设计

程序员文章站 2022-06-24 17:02:19
...

一、 线程的创建与启动

1.1 进程与线程

1.线程的定义:

      线程是指程序在执行过程中,能够执行程序代码的一个执行单元。在java语言中,线程有四种状态:运行、就绪、挂起和结束。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

2.进程的定义:

      指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。进程是程序的一次执行过程,是一个程序及其数据在处理机上顺序执行时所发生的活动,是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

3.线程和进程的区别:

(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间。

(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。

(3)线程是处理器调度的基本单位,但进程不是。

(4) 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

总结:进程是资源分配的基本单位,线程是调度的基本单位。进程包含线程,线程共用进程的资源。

1.2 Java中的Thread和Runnable类

1.通过继承Thread类,重写Threadrun()方法

public class TestThread2 {

    public static void main (String[ ] args) {

       TestThread2  testThread2 =  new TestThread2();

       Runnable runnable = new Runnable() {

           @Override

           public void run() {

              while(true) {

              try {

                  Thread.sleep(1000);

                  System.out.println("haha");

              }catch (InterruptedException e) {

                  e.printStackTrace();

                  break;

                  }

               }

           }

       };

       Thread thread = new Thread(runnable);

       thread.start();

    }

}

2.通过实现Runnable接口,实例化Thread类。

class MyR implements Runnable{

       private String msg;

       public MyR (String msg) {

             this.msg = msg;

   }

    @Override

    public void run() {

         while(true) {

              try {

                  Thread.sleep(1000);

                  System.out.println(msg);

            }catch (InterruptedException e) {

                e.printStackTrace();

                break;

           }

       }

    }  

}

public class TestThread {

    public static void main (String[ ] args) {

       Thread thread1= newThread (new MyR("Hello"));

       thread1.start();

       Thread thread2 = new Thread  (new MyR("wowo"));

       thread2.start();

    }

}

1.3 三种创建线程的办法

(1)继承Thread类创建线程

①定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

②创建Thread子类的实例,也就是创建了线程对象

③启动线程,即调用线程的start()方法

(2)实现Runnable接口创建线程

①定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

②创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

③第三部依然是通过调用线程对象的start()方法来启动线程

(3)使用Callable和Future创建线程

①创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

②使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

③使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

二、 线程简单同步(同步块)

2.1 同步的概念和必要性

同步的概念:线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。

同步的必要性:线程同步是多线程中必须考虑和解决的问题,因为很可能发生多个线程同时访问(主要是写操作)同一资源,如果不进行线程同步,很可能会引起数据混乱,造成线程死锁等问题。

 举例说明
  • 1当创建一个Vector对象时候, 

    Vector ve=new Vector(); 
    ve.add("1"); 
    当在多线程程序中,第一个线程调用修改对象ve的时候,就为其上了锁,其他线程只有等待。 

  • 2当创建一个ArrayList对象时候, 
    ArrayList list=new ArrayList(); 
    list.add("1"); 
    当在多线程程序中,第一个线程调用修改对象list的时候,没有为其上锁,其他线程访问时就会报错。 
    eglist.remove("1"),然后再由其他线程访问list对象的1时就会报错。 

2.2 synchronize关键字和同步块

synchronizedJava中的关键字,是一种同步锁。它修饰的对象有以下几种: 
①修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
③ 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 

④ 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

synchronized修饰一个代码块,被修饰的代码块称为同步语句块.

2.3 实例

class SyncThread implements Runnable {

   private static int count;

   public SyncThread() {

      count = 0;

   }

   public  void run() {

      synchronized(this) {

         for (int i = 0; i < 5; i++) {

            try {

               System.out.println(Thread.currentThread().getName()+ ":" + (count++));

               Thread.sleep(100);

            } catch (InterruptedExceptione) {

               e.printStackTrace();

            }

         }

      }

   }

   public int getCount() {

      return count;

   }

}

三、 生产者消费者问题

3.1 问题表述

      生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区线程——即所谓的生产者消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

3.2 实现思路

       要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

3.3 Java实现该问题的代码

package wxf;

import java.util.LinkedList;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.ReentrantLock;

public class Queue { //队列

   private Lock lock=new ReentrantLock();

   private Condition fullC;//信号量

   private Condition emptyC;//信号量

   private int size;

   public Queue(int size) {

      this.size  = size;//为信号量赋初值

      fullC=lock.newCondition();

      emptyC=lock.newCondition();

   }

   LinkedList<Integer>list = new LinkedList<Integer>();

   /**

    * 入队

    * @return

    */

   public boolean EnQueue(int data) {

      lock.lock();

      while(list.size()>=size){

        try{

           fullC.await();

        }catch (InterruptedException e) {

           lock.unlock();

           return false;

        }

      }

      list.addLast(data);

      return true;

   }

   /**

    * 出队

    * @return

    */

   public int DeQueue() {

      lock.lock();

      while(list.size()== 0) {

        try{

           emptyC.await();

      }catch (InterruptedException e) {

        lock.unlock();

        return -1;

      }

   }

   int r= list.removeFirst();

   fullC.signalAll();

   lock.unlock();

   return r;

   }

   public boolean isFull() {

      return list.size()>=size;

   }

   public boolean isEmpty() {

      return list.size()==0;

   }

public Object size() {

      return list.size();

   }

}

生产者消费者

package wxf;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

/**

 * 生产者

 */

public class Producer implements Runnable {

   private Queue q;

   private Condition isFull; //信号量如果满了则等待

   private Condition isEmpty; //信号量如果空了则等待

   private Lock lock;

   private int index; //生产者的编号

   public Producer(int index,Queue q,Lock lock,Condition isFull,Condition isEmpty) {

      this.index= index;

      this.q=q;

      this.isFull= isFull;

      this.isEmpty= isEmpty;

      this.lock= lock;

   }

   @Override

   public void run() {

      lock.lock();

      while(q.isFull()){

        try{

           isFull.await();//如果队列为慢,则等待

        }catch (InterruptedException e) {

           return;

        }      

      }

      //生产并入队

      inta = (int) (Math.random()*1000);

      q.EnQueue(a);

      //生产完后

      isEmpty.signalAll();//把消费者唤醒。

      lock.unlock();

   }

}

3.4 测试

package wxf;

public class TestPC {

static Queue queue=new Queue(5);

   public static void main(String[ ] args) {//创建三个生产者

      for(inti=0;i<3;i++) {

        final int index=i;

        newThread(()-> {

            while(true) {

            int data=(int)(Math.random()*1000);

            System.out.printf("producerthread %d want to EnQueue %d\n",index,data);

        queue.EnQueue(data);

            System.out.printf("producerthread %d EnQueue %d Sucsess\n",index,data);

            sleep();//随机休息一段时间

            }

        }).start();

      }

//创建消费者

for(int i=0;i<3;i++) {

      final int index=i;

      new Thread(()-> {

        while(true){

        System.out.printf("customer thread%d want to DeQueue\n",index);

      int data = queue.DeQueue();

        System.out.printf("customer thread%d DeQueue %d,size=%d\n Sucsess\n",index,data,queue.size());

        sleep2();//随机休息一段时间

        }

      }).start();

   }

}

   //sleep随机时间

   public static void sleep() {

      int t = (int)(Math.random()*100);

      try{

           Thread.sleep(t);

           }catch (InterruptedExceptione) {

              e.printStackTrace();

           }

   }

//sleep随机时间

   public static void sleep2() {

      int t = (int)(Math.random()*100);

      try{

           Thread.sleep(t);

           }catch (InterruptedExceptione) {

              e.printStackTrace();

           }

   }

}

3.4.1 当生产能力超出消费能力时的表现

通过sleep函数产生随机数的频率来判断,当他的值小的时候说明生产者能力超出消费者能力

public static void sleep() {

    int t = (int)(Math.random()*100);

    try {

          Thread.sleep(t);

          }catch (InterruptedException e) {

             e.printStackTrace();

          }

  }

3.4.2 当生产能力弱于消费能力时的表现

通过sleep函数产生随机数的频率来判断,当他的值大的时候说明生产者能力弱于消费者能力

public static void sleep2() {

    int t=(int)(Math.random()*1000);

    try {

          Thread.sleep(t);

          }catch (InterruptedException e) {

             e.printStackTrace();

          }

  }

四、 总结

      通过本次操作系统编程实践课的学习,学会了用eclipseJava语言来创建操作系统中的进程,如何实现经典进程的同步问题-生产者消费者问题。更加深刻的理解了本学习操作系统这门课的理论内容。需要加强的方面是打字速度问题,java语法要强化。