os编程实践总结

这篇博客探讨了操作系统的线程管理,包括进程与线程的区别、Java中的Thread和Runnable实现线程的方式,以及线程同步的概念和必要性。通过示例详细解释了synchronized关键字在同步块中的应用,并展示了生产者消费者问题的Java实现,强调了线程同步在多线程编程中的重要性。
摘要由CSDN通过智能技术生成


1 线程的创建与启动


1.1 进程与线程

进程:是进程实体的运行过程中,是系统进行资源分配和调度的一个独立单位。

线程:是调度和分派的基本单位。

进程、线程概念和差别:

(一)调度的基本单位。

在传统的os中,进程是作为独立调度和分派的基本单位,因而进程是能独立运行的基本单位。

在引入线程的os中,把线程进程作为独立调度和分派的基本单位,因而线程是能独立运行的基本单位。

(二)并发性。

在引入线程的os中,进程之间可以并发执行,而且在一个进程的多个线程之间亦可以并发执行。同样,不同进程中的线程也能并发执行。

(三)拥有资源。

进程可以拥有资源,并作为系统中拥有资源的一个基本单位。

线程拥有很少系统资源。

(四)独立性。

在统一进程中的不同线程之间的独立性比不同进程之间低得多。

每个进程都拥有一个独立的地址空间和其它资源,不允许其它进程的访问。而同一进程中的不同线程共享进程的内存空间地址和资源。

(五)系统开销。

创建或撤销进程时,系统都要为之分配和回收内存、I/O设备等资源。

线程只保存少量寄存器,不涉及存储器管理;线程的通信与同步更容易,无需内核的干预。

(六)支持多处理机系统

在多处理机系统中,对于传统的进程,即单线程进程,不管有多少处理机,该进程只能运行在一个处理机上。但对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行。

 

1.2 Java中的ThreadRunnable

Thread类:

1)start方法
  start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
2)run方法
  run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
3)sleep方法
    sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
  但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

Runnable创建线程步骤:

(1)创建一个实现Runnable接口的类,并且在这个类中重写run方法

classMyThreadimplementsRunnable{

    publicvoid run(){                                                                               

        ..........

        }

(2)使用关键字new创建一个MyThread的实例

MyThread myThread=new MyThread();

3)通过Runnable的实例创建一个线程对象,在创建线程对象时,调用的构造函数是

new Thread(myThread),它用myThread中实现的run()方法作为新线程对象的run()方法。Threadthread=newThread(myThread);

4)通过调用ThreadType对象的start()方法启动线程运行

thread.start();


 1.3 三种创建线程的办法

方法一、定义实现Runnable接口的一般方法

package org;

/**

 * Runnable的实现类,是线程执行的主体。

 * run函数是入口

 *

 */

class MyR implements Runnable{

   private String msg;

   public MyR(String msg) {

      this.msg = msg;

   }

   //线程的入口

   @Override

   publicvoid run() {

      while(true) {

         try {          

            System.out.println(msg);

            Thread.sleep(1000);

         }catch (InterruptedException e) {

            e.printStackTrace();

            break;

         }

      }

  

}

publicclass TestThread{

   publicstaticvoid main(String[] args) {

      //创建了线程

      Threadthread1 = new Thread(new MyR("hello"));

      thread1.start(); //启动了线程

     

      Threadthread2 = new Thread(new MyR("wuwu"));

      thread2.start(); //启动了线程

   }

}

实现:

结果:



方法二、定义实现Runnable接口的匿名类

package org;

publicclass TestTheard2 {

   publicstaticvoid main(String[] args) {

      TestTheard2testThread2 = new TestTheard2();

      //匿名信 匿名类   引用就是指针

      Runnablerunnable = new Runnable() {

         @Override

         publicvoid run() {

            while(true) {

               try {          

                  System.out.println("haha");

                  Thread.sleep(1000);

               }catch (InterruptedException e) {

                  e.printStackTrace();

                  break;

               }

            }

         }

      };

      Threadthread = new Thread(runnable);

      thread.start();

   }

}

实现:


结果:


方法三、用lamda表达式创建线程

package org;

publicclass TestTheard3 {

   publicstaticvoid main(String[] args) {

      //new Thread(new Runnable() {

      // @Override

      // publicvoid run() {

      //    System.out.println("haha");

      // }

      //}).start();

      // lamda 表达式 java 1.8+  (5-12行相当于)

      new Thread(()->{

      System.out.println("haha");      

      }).start();

   }

}

运行:

结果:




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

2.1 同步的概念和必要性

  线程同步解决的是在一个程序中多个线程之间的关系的协调,对竞争资源的访问的一种处理方式,避免一个线程长期占用一个资源的目的。

    必要性:当线程共享数据时,为了保证数据的准确性和安全性,需要线程同步。

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


2.2 synchronize关键字和同步块

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



2.3 实例

package org;

publicclass TestSync {

   staticintc=0;

   publicstaticvoid main(String[] args) {

      Thread[]threads = new Thread[1000];

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

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

            inta=c; //获取c的值

            a++; //将值加一

             try//模拟复杂处理过程

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

                }catch (InterruptedException e) {

                   e.printStackTrace();

                }

             c=a//存回去

          });

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

      }

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

             try {

                   threads[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;

import java.util.LinkedList;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

publicclass Queue {  //队列

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

   private Lock lock =new ReentrantLock(); //

   private Condition fullC;

   private Condition emptyC;

   privateintsize;

   public Queue(intsize) {

      this.size=size;

      //(2)为信号量赋初值

      fullC=lock.newCondition();

      emptyC=lock.newCondition();

   }

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

/**

     * 入队

     * @return

     */

   publicboolean EnQueue(intdata) {

      lock.lock(); //上锁

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

         try {

            fullC.await();

         }catch (InterruptedException e) {

            lock.unlock();

            returnfalse;

         }

      }

      list.addLast(data);

      emptyC.signalAll();

      lock.unlock();

      returntrue;

   }   

/**

     * 出队

     * @return

     */

   publicint DeQueue() {

      lock.lock(); //先上锁

      while(list.size()==0) {  //如果队列为空,则等待生产者唤醒我

         try {

            emptyC.await();

         }catch (InterruptedException e) {

            lock.unlock();

            return -1; //失败返回

         }

      }

      intr=list.removeFirst();   //获取队列头部

      fullC.signalAll(); //唤醒所有的生产者

      lock.unlock(); //解锁

      returnr;

   }

   publicboolean isFull() {

      returnlist.size()>=size;

   }

   publicboolean isEmpty() {

      returnlist.size()==0;

   }

}

  

package org;

 

publicclass TestPC {

   static Queue queue=new Queue(5);

   publicstaticvoid main(String[] args) {      

//创建三个生产者

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

         finalintindex=i;

         new Thread(()->{

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

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

            queue.EnQueue(data);

            System.out.printf("producer thread %d EnQueue %dSuccess\n",index,data);

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

         }).start();

      }  

      //创建消费者

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

         finalintindex=i;

         new Thread(()->{

            while(true) {

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

                intdata=queue.DeQueue();

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

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

            }

         }).start();

      }

   }

   //sleep随机时间

   privatestaticvoid sleep() {

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

      try {

         Thread.sleep(t);

      }catch (InterruptedException e) {

         e.printStackTrace();

      }

   }

   privatestaticvoid sleep2() {

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

      try {

         Thread.sleep(t);

      }catch (InterruptedException e) {

         e.printStackTrace();

     

   }

}


3.4 测试


结果:



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

当生产能力超出消费能力时,即当生产者个数多于消费者个数时,生产速度快,生产者经常等待消费者

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

生产能力弱于消费能力时,当生产者个数少于消费者个数时,生产速度低于消费速度,则消费者经常等待生产者。

4 总结

此次实验主要是围绕线程和同步的问题,有线程的创建和同步问题,还有进程同步的经典问题——生产者-消费者问题。Java程序是建立在线程之上的。线程的创建一般用继承Thread类或是实现Runnable接口。java 1.8+的版本还能用lamda 表达式,大大缩减了代码,更加简单)。线程的同步主要是用synchronize关键字来实现。利用信号量和锁来解决生产者-消费者问题的同步,有较高的效率,并且易于实现。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值