1 线程的创建与启动
主要描述进程线程概念和差别
进程:通常的程序是不能参与并发执行的,为了能使程序并发执行,并且可以对并发执行的程序加以描述和控制,引入了“进程”,由此得出,进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
线程:为了减少程序在并发执行时所付出的时空开销,使OS具有更好的并发性,由此得出,线程是作为调度和分派的基本单位。
进程和线程的差别:从调度性、并发性、系统开销和拥有资源等方面进行比较。
①调度的基本单位:进程是作为独立调度和分派的基本单位,因而进程是能独立运行的基本单位。线程作为调度和分派的基本单位,因而线程是能独立运行的基本单位。
②并发性:在引入线程的OS中,不仅进程间可以并发执行,而且在一个进程间的多个进程之间也可并发执行,甚至允许在一个进程中的所有线程都能并发执行。同样的,不同的进程中的线程也能并发执行。这样使得OS具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量。
③拥有资源:进程可以拥有资源,并作为系统中拥有资源的一个基本单位,线程本身并不拥有系统资源,而是仅有一点必不可少、能保证独立运行的资源。线程除了拥有自己的少量资源外,还允许多个线程共享该进程所拥有的资源。
④独立性:在同一进程中的不同线程之间的独立性要比不同进程之间的独立性低得多。
⑤系统开销:在创建或撤销进程是,系统都要为之分配和回收进程控制块、分配或回收其他资源,OS为之付出的开销,明显大于线程创建或撤销使所付出的开销。在进程切换时,涉及到进程上下文的切换,而线程的切换代价远低于进程的切换代价。
⑥支持多处理机系统:在多处理机系统中,对于传统的进程,即单线程进程,不管有多少处理机,该进程只能运行在一个处理机上。对于多线程进程,就可以将一个进程中的多个线程分配到多个处理机上,使他们并行执行,这将加速进程的完成。
Thread类:
① start方法:start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
②run方法:run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
补充:用start()方法来启动线程,是真正实现了多线程,通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,无需等待run方法执行完毕,即可继续执行下面的代码。
所以说:start()方法是真正实现了多线程run()方法只是一个普通的方法。
③sleep方法:休眠时间由时间t确定
Runnable类:①创建实现Runnable接口的类
②创建一个类对象
③由Runnable创建一个Thread对象
④启动线程
至此,一个线程就创建完了。
①定义一个“MyR”类,并且实现一个Runnable接口的对象
package org.hhh;
/**
* Runnable的实现类
* @author Administrator
*
*/
class MyR implementsRunnable{
privateString msg;
public MyR(String msg) {
this.msg=msg;
}
@Override
publicvoid run() {
while(true) {
try {
Thread.sleep(1000);
System.out.println(msg);
}catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
publicclasswhhh {
publicstaticvoidmain(String[] args){
Thread thread1=new Thread(new MyR("hello"));
thread1.start();
Thread thread2=new Thread(new MyR("wuwu"));
thread2.start();
}
}
②定义一个实现Runnable接口的匿名类
package org.hhh;
publicclassTest {
publicstaticvoidmain(String[] args){
Runnable runnable=new Runnable() {
@Override
publicvoid run() {
while(true) {
try {
Thread.sleep(1000);
System.out.println("haha");
}catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
};
Threadthread= newThread(runnable);
thread.start();
}
}
③使用lambda表达式
package org.hhh;
publicclassTest1 {
publicstaticvoidmain(String[] args){
new Thread(()->{
System.out.println("haha");
}).start();
}
}
2 线程简单同步(同步块)
概念:java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。。
必要性:在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
2.2 synchronize关键字和同步块
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
package org.hhh;
import com.sun.media.jfxmedia.events.NewFrameEvent;
publicclassTestSync {
staticintc=0;
static Object lock=new Object();//(1)随便建立了一个变量,作为锁变量
publicstaticvoid main(String[] args) {
Thread[]threads=new Thread[1000];
for(inti=0;i<1000;i++) {
finalintindex=i;//(4)建立了一个final变量,放在lamba中使用
threads[i]=new Thread(()->{
synchronized (lock) {//(2)创建一个同步块,需要一个锁
System.out.println("thread "+index+" enter");//(5)输出
inta = c;//获取c的值
a++;//将值加1
try {//模拟复杂处理过程
Thread.sleep((long) (Math.random()*1000));
}catch(InterruptedException e) {
e.printStackTrace();
}
c=a;//存回去
System.out.println("thread "+index+" enter");//(6)输出
}//(3)这是块的终结
});
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 生产者消费者问题
有一个队列
生产者负责将元素加入队列中,谓之生产
消费者负责将元素从队列中移出,谓之消费
当队列满的时候,让生产者等待,直到有人唤醒他。唤醒的时机?当有消费者消费了队列中的元素,队列不再满,就可以唤醒生产者让其生产当队列为空时,让消费者等待,当生产者生产了元素,队列不再为空,则唤醒消费者。
①创建一个队列:用来当作生产者和消费者的缓冲区,所以这个缓冲区应该是做到线程内唯一的。
②创建两个线程:一个是生产者线程,一个是消费者线程,做到生产者生产的产品就放到这个缓冲区,而消费者时时刻刻可以从这个缓冲区中去拿物品,只要是有就可以拿,生产者则只是缓冲区不满就可以往里面放,而且队列是一个先对先进的数据结构,同时他维护了枷锁的机制。
package org.hhh;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
publicclassQueue {//队列
//(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=newLinkedList<Integer>();
/**
*
* 入队
* @return
*/
public boolean 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
*/
public int 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;
}
publicbooleanisFull() {
returnlist.size()>=size;
}
publicbooleanisEmpty() {
returnlist.size()==0;
}
}
package org.hhh;
publicclassTestPC {
staticQueue queue=new Queue(5);
publicstaticvoid main(String[] args) {
//创建三个生产者
for(inti=0;i<3;i++) {
finalintindex=i;
new Thread(()->{
while(true) {
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 %d Success\n",index,data);
sleep();//随机休息一段时间
}
}).start();
}
for(inti=0;i<3;i++) {
finalintindex=i;
new Thread(()->{
while(true) {
System.out.printf("customer thread %d want to DeQueue \n",index);
intdata=queue.DeQueue();
System.out.printf("customer thread %d DeQueue %d Success\n",index,data);
sleep2();//随机休息一段时间
}
}).start();
}
}
//sleep随机时间
publicstaticvoidsleep() {
intt=(int)(Math.random()*100);
try {
Thread.sleep(t);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
publicstaticvoidsleep2() {
intt=(int)(Math.random()*1000);
try {
Thread.sleep(t);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
3.4 测试
3.4.1 当生产能力超出消费能力时的表现
当生产者能力超出消费者能力时,生产速度快,生产者经常等待消费者。
当生产者能力弱于消费者能力时,消费速度快,消费者经常等待生产者。
通过这次完成操作系统的生产者与消费者问题的程序实验,让我更深刻的理解了操作系统中生产者与消费者的关系与运行过程,在一定程度上提高了自己理解生产者与消费者问题,通过这次的实验,我使用了多线程的技术,对于的线程的应用方法以及编写方法有了比较深入的了解。