操作系统实践课程总结

本文总结了操作系统实践课程中的核心内容,重点讨论Java中的线程创建,包括通过继承Thread类和实现Runnable接口两种方式。此外,文章还深入讲解了线程同步的概念、synchronized关键字的使用,以及生产者消费者问题的解决方案,结合具体代码展示了如何利用ReentrantLock实现线程安全的队列。最后,作者强调了实践课程对于深化操作系统理解和提升编程技能的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 线程的创建与启动

1.1 进程与线程

1.线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。线程可以创建和撤消线程,从而实现程序的并发执行。

 2. 进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

 3.Start()…… 启动线程……

4.(1)创建一个Runnable接口的对象并使用Thread对象启动它。

 (2)继承Thread对象

1.2 Java中的Thread和Runnable类

   1.一个Thread类的对象对应一个线程

   2. Thread t = new Thread(obj);

  3. Obj:Runnable的对象  Runnable的实现类是线程执行的主体

   4.(一)继承Thread类创建多线程----单线程

       (二)通过继承Thread类实现多线程:

     如果希望(一)中的两个循环打印语句都能够执行的话,那么就需要实现多线程。为此jdk提供了一个多线程类Thre  ad,通过继承Thread类,并重写Thread类中的run()方法便可以实现多线程。在Thread类中,提供了一个start()方法用于启动新线程,线程启动后,系统就会自动调用run()方法。

    (三)实现Runnable接口创建多线程

       在(二)中通过继承Thread类实现多线程,但是这种方式有一定的局限性。因为在java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类,比如学生类Student继承了person类,就无法再继承Thread类创建的线程。为了克服这种弊端,Thread类提供了另外一种构造方法Thread(Runnable target),其中Runnable是一个接口,它只有一个run()方法。当通过Thread(Runnabletarget)构造方法创建一个线程对象时,只需该方法传递一个实现了Runnable接口的实例对象,这样创建的线程将调用实现了Runnable接口中的run()方法作为运行代码,二不需要调用Thread类中的run()方法。

1.   



1.3 三种创建线程的办法

     第一种方法:继承Thread类,重写run()方法,run()方法代表线程要执行的任务。
     
第二种方法:实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务。
     
第三种方法:实现callable接口,重写call()方法,call()作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出

2 线程简单同步(同步块)

   同步锁:

    1.Java5开始,Java提供了一种功能更加强大的线程同步机制——通过显式定义同步锁对象来实现同步,这里的同步锁由Lock对象充当。

     Lock 对象提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock是控制多个线程对共享资源进行访问的工具。通常, 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。

    某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock,ReadWriteLock是Java5提供的两个根接口,并为 Lock提供了ReentrantLock实现类,为ReadWriteLock提供了            ReentrantReadWriteLock实现类。在 Java8中提供了新型的StampLock类,在大多数场景下它可以替代传统的ReentrantReadWriteLock。ReentrantReadWriteLock为读写操作提供了三种锁模式:Writing,ReadingOptimistic,Reading。

    2.在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。



2.1 同步的概念和必要性

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。

"同"字从字面上容易理解为一起动作

其实不是,"同"字应是指协同、协助、互相配合。

如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。


2.2 synchronize关键字和同步块

     synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种: 
     1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块          的对象; 
     2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
     3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
     4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

2.3 实例

packagezheng;

importcom.sun.media.jfxmedia.events.NewFrameEvent;

publicclass testhread {

static int c=0;

   static Object lock = new Object();

   public static void main(String[] args) {

     Thread[] thread = new Thread[1000];

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

        final int index = i;

        thread[i] = new Thread(()->{

          synchronized(lock) {

             System.out.println("thread"+index+"enter");

            

          int a = c;//获取c的值

          a++;//将值加一

          try {//模拟复杂处理过程

             Thread.sleep((long)(Math.random()*1000));

          }

          catch(InterruptedException e) {

             e.printStackTrace();

          }

          c=a;//存回去

          System.out.println("thread"+index+"leave");

          }

        });

        thread[i].start();//线程开始

     }

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

        try {

          thread[i].join();//等待thread i完成

        }catch(InterruptedException e) {

          e.printStackTrace();

        }

     }//循环后所有的线程都完成了

     System.out.println("c="+c);//输出c的结果

 

   }

}

      

3 生产者消费者问题

3.1 问题表述

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

3.2 实现思路

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

3.3 Java实现该问题的代码

package org.zheng; 

import java.util.LinkedList;

importjava.util.concurrent.locks.Condition; 

importjava.util.concurrent.locks.Lock; 

import java.util.concurrent.locks.ReentrantLock; 

public class Queue { //队列

//(1)建立一个锁,俩信号量 

private Lock lock =new ReentrantLock(); // 

private Condition fullC//信号量 

private Condition emptyC//信号量 

private int size; 

public Queue(int size) { 

this.size = size; 

//2)为信号量赋初值 

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

emptyC.signalAll(); lock.unlock();

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; 

} 

}

3.4 测试

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

    当生产力超出消费能力时,没有缓冲区让消费者去放产品,此时消费者等待,消费者消费,当有缓冲区空闲,可以放产品时,生产者才可以去放产品。

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

     当生产力弱于消费能力时,缓冲区的产品不够消费,供不应求,会有空的缓冲区,当缓冲区空了之后,让消费者等待,当生产者生产了产品,缓冲区不再为空,则唤醒消费者。

4 总结

      经过一天的学习,让我对操作系统的实践有了更深的认识,学到了更多的知识,这些知识是平常课堂上学不到的。所以我对每一次课程设计的机会都非常珍惜。不一定我的课程设计能够完成得有多么完美,但是要用心去做,去发现问题解决问题,发现自己的不足,让自己在编程的能力上有所进步。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值