操作系统编程实践课程设计
程序员文章站
2022-06-24 17:00:35
...
线程的创建与启动
1.1 进程与线程
进程:进程是程序的一次执行,进程是一个程序及其数据在处理机上的顺序执行时所发生的运动,进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统运行资源分配和调度的一个独立单位线程: 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的基本单位。
进程和线程的差别:
(1) 调度。在传统的操作系统中,拥有资源和独立调度的基本单位都是进程。在引入线程的操作系统中,线程是独立调度的基本单位,进程是资源拥有的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。
(2)拥有资源。不论是传统操作系统还是设有线程的操作系统,进程都是拥有资源的基本单位,而线程不拥有系统资源(也有一点必不可少的资源),但线程可以访问其隶属进程的系统资源。
(3)并发性。在引入线程的操作系统中,不仅进程之间可以并发执行,而且多个线程之间也可以并发执行,从而使操作系统具有更好的并发性,提高了系统的吞吐量。
(4)系统开销。由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、 I/O设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程CPU环境的保存及新调度到进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
1.2 Java中的Thread和Runnable类
在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口;Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作
Java中的Thread和Runnable类的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
(1):适合多个相同的程序代码的线程去处理同一个资源
(2):可以避免java中的单继承的限制
(3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
(4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
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 线程简单同步(同步块)
2.1 同步的概念和必要性
线程同步:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程处于等待状态。
必要性:当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常
如
1如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚
2一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。
2.2 synchronize关键字和同步块
1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明synchronized方法。如:
public synchronized voidaccessVal(int newVal);
synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个synchronized方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronized的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。 在Java中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为synchronized,以控制其对类的静态成员变量的访问。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized将会大大影响效率,典型地,若将线程类的方法run()声明为synchronized,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何synchronized方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为synchronized,并在主方法中调用来解决这一问题,但是Java为我们提供了更好的解决办法,那就是synchronized块。
2.synchronized 块:通过synchronized关键字来声明synchronized块。语法如下:
synchronized(syncObject){
//允许访问控制的代码
}
synchronized 块是这样一个代码块,其中的代码必须获得对象syncObject(如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。可以针对任意代码块,且可任意指定上锁的对象,灵活性较高。
2.3 实例
mport java.util.ArrayList;
public class TestSync {
staticint c = 0;
staticObject lock = new Object(); //(1) 随便建立了一个变量,作为锁变量
publicstatic void main(String[] args) {
Thread[]threads = new Thread[1000];
for(inti=0;i<1000;i++) {
finalint index = i; //(4) 建立了一个final变量,放半在lamba中使用
threads[i] = new Thread(()->{
synchronized(lock) { //(2) 创建一个同步块, 需要一个锁。
System.out.println("thread"+index+"enter"); //(5)输出
inta = c; //获取c的值
a++;//将值加一
try{//模拟复杂处理过程
Thread.sleep((long)(Math.random()*10));
}catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
c=a;//存回去
System.out.println("thread"+index+"leave");//(6)输出
}//(3) 这是块的终结
});
threads[i].start();//线程开始
}
for(inti=0;i<1000;i++) {
try{
threads[i].join();//等待 thread i 完成
}catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}//循环后,所有的线程都完成了
System.out.print("c="+c);//输出c的结果
}
}
3 生产者消费者问题
3.1 问题表述
有一群生产者进程在生产产品,并将这些产品提供给消费者去消费。要保证生产者不会在产品满时生产产品,消费者也不会在产品空时消费产品。
3.2 实现思路
为使生产者消费者能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池,生产者进程将其所产生的产品放入一个缓冲区中,消费者进程可以从一个缓冲区中取走产品去消费,尽管所有的生产者进程和消费者进程都是以异步方式运行的,但他们之间必须保持同步,即不允许消费者进程到一个到空缓冲区去取产品,也不允许生产者进程向一个已装满产品且尚未被取走的缓冲区投放产品
3.3 Java实现该问题的代码
importjavax.net.ssl.SSLException;
public classTestPC {
static Queue queue=new Queue(5);
public static void main(String[] args) {
for(int i=0;i<3;i++) {
final int index=i;
new Thread(()-> {
intdata=(int)(Math.random()*1000);
System.out.printf("thread%d want to EnQueue %d\n"+data);
queue.EnQueue(data);
System.out.printf("thread%d EnQueue %d Success\n"+data);
sleep();
}).start();
}
for(int i=0;i<3;i++) {
final int index=i;
new Thread(()-> {
while(true) {
System.out.printf("customerthread %d want to DnQueue %d\n",index);
int data=queue.DeQueue();
System.out.printf("customerthread %d DnQueue %dSuccess\n",index,data);
sleep();
}
}).start();
}
}
public static void sleep() {
int t=(int)(Math.random()*1000);
try {
Thread.sleep(t);
} catch (InterruptedException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}
3.4 测试
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();
}
}
4 总结
但之前对于操作系统的概念也只是存在于理论,这次和实践结合,让我对它有了一个深刻的理解。深入了解了其逻辑,让之前只会一点java语言算法的我能更加熟练的进行编程并且顺利的完成了一个项目,让我编程能力大大提高。
上一篇: Vue中props用法介绍
下一篇: jQuery 实现双击编辑表格功能