实践报告

1 线程的创建与启动


1.1 进程与线程

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

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

    区别:1.调度的基本单位:传统OS中,进程是作为独立调度和分派的基本单位,因而进程是能独立运行基本单位;在引入线程的OS中,线程作为调度和分派的基本单位,因而线程是能独立运行基本单位。2.并发性:在引入线程的OS中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,甚至还允许在一个进程中的所有线程都能并发执行。同样,不同进程中的线程也能并发执行。3.拥有资源:进程可以拥有资源,并作为系统中拥有资源的一个基本单位。线程本身并不拥有系统资源,而是仅有一点必不可少的、能保证独立运行的资源。4.独立性:每个进程都拥有一个独立的地址空间和其他资源,而同一进程中的不同线程是共享进程的内存地址空间和资源。5.系统开销:在创建或撤销进程时,系统都要为之分配和回收进程控制块、其他资源等。OS为此所付出的开销,明显大于线程创建或撤销时所付出的开销。6.支持多处理机系统:对于单线程进程,不管有多少处理机,该进程只能运行在一个处理机上。对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使它们并行执行,这将加速进程的完成。

1.2 Java中的ThreadRunnable

    Thread使用方法:

  Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。

      1)start方法

  start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

      2)run方法

  run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

      3)sleep方法

       sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。

  但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

 

      Runnable使用方法:

      步骤一:创建一个实现Runnable接口的类;

      步骤二:创建一个类对象;

      步骤三:由Runnable创建一个Thread对象;

      步骤四:启动线程。

1.3 三种创建线程的办法

  1. 第一种方法(定义一个”MyR”类,创建一个Runnable接口的对象并使用Thread对象启动它)

packageorg.hhh;

 

class MyRimplements Runnable{

privateString msg;

publicMyR(String msg){

    this.msg=msg;

}

    @Override

    publicvoidrun() {

    while(true){

       try {

           Thread.sleep(1000);

           System.out.println(msg);

       }catch(InterruptedException e){

           e.printStackTrace();

           break;

       }

      }

    }

   

}

publicclass whh {

 

    publicstaticvoid main(String[] args) {

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

    thread1.start();

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

    thread2.start();

    }

 

}


2. 第二种方法(定义一个实现Runnable接口的匿名类)

packageorg.hhh;

 

publicclass jjj {

 

    publicstaticvoid main(String[] args) {

     Runnable runnable= new Runnable() {

      

       @Override

       publicvoidrun() {

           while(true){

              try {

                  Thread.sleep(1000);

                  System.out.println("haha");

              }catch(InterruptedException e){

                  e.printStackTrace();

                  break;

              }

             }

       }

      };

      Thread thread = new Thread(runnable);

      thread.start();

     

    }

}


3. 第三种方法(Lambda表达式)

packageorg.hhh;

 

publicclass hh3 {

 

   publicstaticvoid main(String[] args) {

    

     new Thread(()-> {

       System.out.println("haha");

    }).start();

    

  }

}


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

    2.1 同步的概念和必要性
线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。
必要性:当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到

 

 
 
 

2.2 synchronize关键字和同步块

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

     同步块:被Synchronized修饰的代码块称为同步块。

2.3 实例

package org.hhh;

 

importjavax.sql.rowset.Joinable;

 

import com.sun.media.jfxmedia.events.NewFrameEvent;

 

public class www {

     static int c=0;

     static Object Lock = new Object();//(1)建一个变量,作为锁变量

    public static void main(String[] args) {

      Thread[] threads = new Thread[1000];

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

       finalint index=i;//(4)建立一个final变量,放在lamba中使用

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

           synchronized(Lock) {//(2)创建一个同步块,需要一个锁

              System.out.println("thread"+index+"leave");//(5)输出

              inta=c;//获取c的值

              a++;

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

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

              }catch (InterruptedException e) {

                  e.printStackTrace();

              }

              c=a;//将值存回去

              System.out.println("thread"+index+"leave");//(6)输出

           }//(3)这是块的终结

          

       });

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

    }

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

         try {

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

       } catch (InterruptedException e) {

            e.printStackTrace();

       }

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

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

    }

 

}


3 生产者消费者问题

3.1 问题表述

   生产者生产一段数据,只有缓冲区没有满时,生产者才能把信息放到缓冲区中,否则等待;同时,只要缓冲区不空时,消费者才能从中取出消息,否则必须等待。

3.2 实现思路

   要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。

3.3 Java实现该问题的代码

package org.hhh;

 

importjava.util.LinkedList;

importjava.util.concurrent.locks.Condition;

importjava.util.concurrent.locks.Lock;

importjava.util.concurrent.locks.ReentrantLock;

 

publicclass hh5 {//队列

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

    private Lock lock=newReentrantLock();//

    private Condition fullc;//信号量

    private Condition emptyc;//信号量

   

    privateintsize;

    public hh5(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){

           returnfalse;

       }

    }

    list.addLast(data);

    emptyc.signalAll();

    lock.unlock();

    returntrue;

}

/**

 * 出队

 *return

 * @throws

 */

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;

  }

/**

 * 队空

 * @return

 */

publicboolean isFull() {

    returnlist.size()>=size;

}

 

/**

 * 队满

 * @return

 */

publicboolean isEmpty() {

    returnlist.size()==0;

}

}

packageorg.hhh;

 

publicclass TestP {

    static hh5 queue=newhh5(5);

    publicstaticvoid main(String[] args) {

       //创建三个生产者

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

           finalintindex=i;

           newThread(()-> {

              while(true) {

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

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

              queue.EnQueue(data);

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

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

              }

           }).start();

        }

        //创建消费者

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

           finalintindex=i;

           newThread(()-> {

              while(true) {

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

              intdata=queue.DeQueue();

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

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

              }

           }).start();

    }

    }

    //sleep随机时间

    publicstaticvoid sleep() {

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

       try {

           Thread.sleep(t);

       }catch(InterruptedException e){

           e.printStackTrace();

       }

    }

    publicstaticvoid sleep2() {

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

       try {

           Thread.sleep(t);

       }catch(InterruptedException e){

           e.printStackTrace();

       }

    }

}

 

3.4 测试

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

当生产能力超出消费者能力时,生产速度快,生产者经常等待消费者。

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

当生产能力弱于消费者能力时,生产速度慢,消费者经常等待生产者。

4 总结

此次的实验有三个,线程的创建与启动、线程的简单同步以及生产者消费者问题。第一个实验中,重点是用.Thread类和Runnable类来创建并运行线程。第二个实验中重点是使用Synchronized关键字。第三个实验,首先要明确生产者消费者的问题,其次是明白用信号量去解决生产者消费者问题的思路。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值