操作系统编程实践课程设计
一、 线程的创建与启动
1.1 进程与线程
1.线程的定义:
线程是指程序在执行过程中,能够执行程序代码的一个执行单元。在java语言中,线程有四种状态:运行、就绪、挂起和结束。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
2.进程的定义:
指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。进程是程序的一次执行过程,是一个程序及其数据在处理机上顺序执行时所发生的活动,是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
3.线程和进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间。
(2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。
(3)线程是处理器调度的基本单位,但进程不是。
(4) 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
总结:进程是资源分配的基本单位,线程是调度的基本单位。进程包含线程,线程共用进程的资源。
1.2 Java中的Thread和Runnable类
1.通过继承Thread类,重写Thread的run()方法
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的时候,没有为其上锁,其他线程访问时就会报错。
eg:list.remove("1"),然后再由其他线程访问list对象的1时就会报错。
2.2 synchronize关键字和同步块
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
①修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
②修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
③ 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
④ 修改一个类,其作用的范围是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();
}
}
四、 总结
通过本次操作系统编程实践课的学习,学会了用eclipse,Java语言来创建操作系统中的进程,如何实现经典进程的同步问题-生产者消费者问题。更加深刻的理解了本学习操作系统这门课的理论内容。需要加强的方面是打字速度问题,java语法要强化。