课程总结
1.1进程与线程的介绍与差别:
进程的介绍:
进程:指在系统中能独立运行并作为资源分配的基本单位,它是由一组机器指令、数据和堆栈等组成的,是一个能独立运行的活动实体。
进程有三个状态:就绪状态、执行状态、等待状态【或称阻塞状态】;进程由父进程建立,系统中所有的进程形成一种进程树的层次体系;挂起命令可由进程自己和其他进程发出,但是解除挂起命令只能由其他进程发出。
线程的介绍:
线程:线程是进程中的一个实体,作为系统调度和分派的基本单位。Linux下的线程看作轻量级进程。
进程的特征:
1.动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
2.并发性:任何进程都可以同其他进程一起并发执行。
3.独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
4.异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
线程和进程的区别:
1.线程是进程内的一个相对独立的可执行的单元。若把进程称为任务的话,那么线程则是应用中的一个子任务的执行。
2.由于线程是被调度的基本单元,而进程不是调度单元。所以,每个进程在创建时,至少需要同时为该进程创建一个线程。即进程中至少要有一个或一个以上的线程,否则该进程无法被调度执行。
3.进程是被分给并拥有资源的基本单元。同一进程内的多个线程共享该进程的资源,但线程并不拥有资源,只是使用他们。
4.线程是操作系统中基本调度单元,因此线程中应包含有调度所需要的必要信息,且在生命周期中有状态的变化。
5.由于共享资源【包括数据和文件】,所以线程间需要通信和同步机制,且需要时线程可以创建其他线程,但线程间不存在父子关系。
1.2 Java中的Thread和Runnable类
Java中线程的创建有两种方式:
1、 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中。
2、 通过实现Runnable接口,实例化Thread类。
Runnable 接口只有一个方法run(),我们声明自己的类实现 Runnable 接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。但是 Runnable 接口并没有任何对线程的支持,我们还必须创建 Thread 类的实例,这一点通过 Thread 类的构造函数public Thread(Runnable target);来实现。
3、继承Thread实现的模式是定义多个线程,各自完成各自的任务.
4、实现Runnable实现的模式是定义多个线程,实现一个任务。
Thread类与Runnable接口的关系
public interfaceRunnable {
public abstractvoid run();
}
public classThread implements Runnable {
/* What will berun. */
private Runnabletarget;
/**
* Causes thisthread to begin execution; the Java Virtual Machine
* calls the<code>run</code> method of this thread.
*/
publicsynchronized void start() {......}
@Override
public void run(){
if (target !=null) {
target.run();
}
}
}
Thread类与Runnable接口都位于java.lang包中。从上面我们可以看出,Runnable接口中只定义了run()方法,Thread类实现了Runnable 接口并重写了run()方法。当调用Thread 类的start()方法时,实际上Java虚拟机就去调用Thread类的run()方法,而Thread 类的run()方法中最终调用的是Runnable类型对象的run()方法。
1.3三种创建线程的方法
代码一:
package T1;
/**
*javadoc 将文档嵌入到程序中
* @author Administrator
*
*/
class MyR implements Runnable{
private String 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;
}
}
}
}
publicclass test_1 {
publicstaticvoid main(String[] args) {
//创建线程
Thread thread1 = new Thread(new MyR("hello"));
thread1.start();
Thread thread2 = new Thread(new MyR("66666"));
thread2.start();
}
}
代码二:
package T1;
import java.text.BreakIterator;
publicclass TestThread2 {
publicstaticvoid main(String[] args) {
TestThread2 testThread2 =new TestThread2();
//匿名类
Runnable runnable = new Runnable()
@Override
publicvoid run() {
while(true) {
try {
Thread.sleep(1000);
System.out.println("hehe");
} catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
代码三:
package T1;
import javax.xml.stream.events.StartDocument;
publicclass TestThread3 {
publicstaticvoid main(String[] args) {
new Thread (new Runnable() {
@Override
publicvoid run() {
while(true) {
try {
Thread.sleep(1000);
System.out.println("haha");
} catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}).start();
//lamda表达式 java 1.8+
new Thread(()->{ System.out.println("abc");
}).start();
}
}
2 线程简单同步(同步块)
2.1 同步的概念和必要性
同步:
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
/**
* 同步
*
* @author XIEHEJUN
*
*/
public class SynchronizedThread {
class Bank {
private int account = 100;
public int getAccount() {
return account;
}
/**
* 同步方法
*/
public synchronized void save(int money) {
account += money;
}
/**
* 同步代码块
*
* @param money
*/
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//bank.save1(10);
bank.save(10);
System.out.println(i + "账户余额为:" + bank.getAccount());
}
}
}
/**
*
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("线程1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("线程2");
Thread thread2 = new Thread(new_thread);
thread2.start();
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
2.2synchronize关键字和同步块
synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
例如:
publicsynchronized void save(){}
同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
例如:
synchronized(object){
}
2.3代码实例
//
public classSynchronizedThread {
class Bank {
private int account = 100;
public int getAccount() {
return account;
}
public synchronized void save(intmoney) {
account += money;
}
/public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable{
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// bank.save1(10);
bank.save(10);
System.out.println(i + "账户余额为:" + bank.getAccount());
}
}
}
3 生产者消费者问题
3.1 问题表述
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程--即所谓的"生产者"和"消费者"--在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
3.2 实现思路
1)每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的当前全部内容、当前指针位置和生产者/消费者线程的自定义标识符。
2)为每个生产者/消费者产生一个线程,设计正确的同步算法每个生产者和消费者对有界缓冲区进行操作后,即时显示有界缓冲区的当前全部内容、当前指针位置和生产者/消费者线程的自定义标识符。
3)生产者和消费者都有30个;
4)多个生产者或多个消费者之间须共享对缓冲区进行操作的函数代码。
3.3 Java实现该问题的代码
生产者进程:
repeat
produce an item in nextp
wait(empty);
wait(mutex);
buffer(in):=nextp;
in:=(in+1)mod n;
signal(mutex);
signal(full);
until false;
消费者进程:
repeat
wait(full);
wait(mutex);
nextc:=buffer(out);
out:=(out+1)mod n;
signal(mutex);
signal(empty);
consumethe item in nextc
wait(S)操作:
S=S-1;
若S>=0,则进程继续运行。
若S<0,则该进程被阻塞,并将它插入该信号量的等待队列中。
Signal(S)操作:
S=S+1;
若S>0,则进程继续执行;
若S<=0,则从信号量等待队列中移处第一个进程,使其变为就绪状态,然后再返回原进程继续执行。
当生产者能力超出消费者能力:
package T1;
import java.util.concurrent.ThreadLocalRandom;
publicclass Testpc {
static Que queue = new Que(5);
publicstaticvoid main(String[] args) {
//创建三个生产者
for(inti=0;i<3;i++) {
finalintindex = i;
new Thread(()->{
intdata =(int)(Math.random()*1000);
System.out.printf("produce thread %d want to EnQueue %d\n",index,data);
queue.EnQueue(data);
System.out.printf("produce thread %d EnQueue %d Success\n",index,data);
sleep();
}).start();
}
for(inti=0;i<3;i++) {
finalintindex=i;
new Thread(()->{
System.out.printf("customer thread %d want to EnQueue\n",index);
intdata = queue.DeQueue();
System.out.printf("customer thread %d EnQueue %d Success\n",index,data);
}).start();
}
}
publicstaticvoid sleep() {
intt =(int)(Math.random()*10);
try {
Thread.sleep(t);
} catch (Exception e) {
e.printStackTrace();
}
}
}
customer thread 0 want to EnQueue
customer thread 0 EnQueue -1 Success
produce thread 2 want to EnQueue 459
produce thread 1 want to EnQueue 750
produce thread 0 want to EnQueue 946
produce thread 0 EnQueue 946 Success
customer thread 1 want to EnQueue
customer thread 2 want to EnQueue
customer thread 1 EnQueue 459Success
produce thread 1 EnQueue 750 Success
produce thread 2 EnQueue 459 Success
customer thread 2 EnQueue 750Success
当消费者能力超过生产者:
package T1;
import java.util.concurrent.ThreadLocalRandom;
publicclass Testpc {
static Que queue = new Que(5);
publicstaticvoid main(String[] args) {
//创建三个生产者
for(inti=0;i<3;i++) {
finalintindex = i;
new Thread(()->{
intdata =(int)(Math.random()*10);
System.out.printf("produce thread %d want to EnQueue %d\n",index,data);
queue.EnQueue(data);
System.out.printf("produce thread %d EnQueue %d Success\n",index,data);
sleep();
}).start();
}
for(inti=0;i<3;i++) {
finalintindex=i;
new Thread(()->{
System.out.printf("customer thread %d want to EnQueue\n",index);
intdata = queue.DeQueue();
System.out.printf("customer thread %d EnQueue %d Success\n",index,data);
}).start();
}
}
publicstaticvoid sleep() {
intt =(int)(Math.random()*1000);
try {
Thread.sleep(t);
} catch (Exception e) {
e.printStackTrace();
}
}
produce thread 0 want to EnQueue 5
produce thread 0 EnQueue 5 Success
customer thread 1 want to EnQueue
customer thread 1 EnQueue 5 Success
produce thread 2 want to EnQueue 7
produce thread 2 EnQueue 7 Success
customer thread 2 want to EnQueue
customer thread 2 EnQueue 7 Success
customer thread 0 want to EnQueue
customer thread 0 EnQueue -1 Success
produce thread 1 want to EnQueue 5
produce thread 1 EnQueue 5 Success
4总结
本次实践让我知道了什么是线程和进程,一个进程可以有多个线程,知道了什么是javadoc,它是一个可以将文本嵌入到程序中三种创建线程的方法:普通的创建方法,匿名类和lamda表达式。消费者与生产者问题的解决方法,建立队列Queue和producer,创建三个生产者和消费者,通过运行结果观察生产者能力大于消费者和消费者能力大于生产者的现象。知道了什么是信号量,什么是互斥量。
互斥量:
互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,
但是更节省资源,更有效率。
信号量:
信号量就是具有原子性的计数器,就相当于一把锁,在每个进程要访问临界资源时,必须要向信号量拿个锁”,它才能进去临界资源这个“房间”,并锁上门,不让其他进程进来,此时信号量执行P()操作,锁的数目减少了一个,所以计数器减1,;当它访问完成时,它出来,将锁还给信号量,执行V()操作,计数器加1;然后是下面的进程继续。这也体现了各个进程访问临时资源是互斥的