1 线程的创建与启动
1.1 进程与线程
1.1.1什么是线程
答:线程是独立运行,调度的基本单位。
1.1.2进程与线程的区别
答:进程与线程是相互联系又是有区别的
(1)一个进程包含多个线程,所以说它们之间是相互联系的
(2)调度性:在传统操作系统中,进程是独立运行,调度,资源分配的基本单位,而在线程操作系统中,线程是调度,独立运行的基本单位,而进程是资源分配的基本单位。
(3)并发性:进程和线程都可以并发执行
(4)资源性;进程不管在传统os还是线程的os中都是资源分配的基本单位,线程不能实现资源的分配
1.2 Java中的Thread和Runnable类
1.2.1 Java中创建多线程
答:一个Thread类的对象对应一个线程,创建一个Runnable接口的对象并使用Thread对象启动它,Thread t= new Thread (obj)(obj代表Runnable的对象),用start()函数启动线程。
1.3 三种创建线程的办法
1.3.1实验方法一:
package org.yang;
/**
* Runnable的实现类,是线程执行的主体。
* run函数是入口
*
*/
class MyR implementsRunnable{
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;
}
}
}
}
publicclassTestThread {
publicstaticvoidmain(String[] args){
//创建了线程
Threadthread1= newThread(newMyR("hello"));
thread1.start(); //启动了线程
Threadthread2= newThread(newMyR("wuwu"));
thread2.start(); //启动了线程
}
}
1.3.2实验方法二
package org.yang;
publicclassTestThread2 {
publicstaticvoidmain(String[] args){
TestThread2testThread2=newTestThread2();
//匿名信 匿名类 引用就是指针
Runnablerunnable= newRunnable() {
@Override
publicvoid run() {
while(true) {
try {
System.out.println("haha");
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
break;
}
}
}
};
Threadthread= newThread(runnable);
thread.start();
}
}:
1.3.3实验方法三
package org.yang;
publicclassTestThread3 {
publicstaticvoidmain(String[] args){
new Thread(new Runnable() {
@Override
publicvoid run() {
System.out.println("haha");
}
}).start();
// lamda 表达式 java 1.8+
new Thread(()->{
System.out.println("haha");
}).start();
}
}
2 线程简单同步(同步块)
2.1 同步的概念和必要性
2.1.1同步的定义
答:两个进程使用同一资源时,一个进程必须停下来等待另一个进程完成后才能使用这一资源。
2.1.2必要性
答:保证进程之间互斥的使用资源,没有同步机制会导致资源不合理利用。
2.2 synchronize关键字和同步块
2.2.1synchronize关键字:在多线程环境下的应用synchronized 关键字,它包括两种用法:synchronized 方法和 synchr onized synchronized方法。synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
2.2.2同步块:同步块用来标记方法或者代码块是同步的。Java同步块用来避免竞争。
2.3 实例
关键字实验:
package org.yang;
import java.util.ArrayList;
publicclassTestSync {
staticint c = 0;
static Object lock = new Object(); //(1) 随便建立了一个变量,作为锁变量
publicstaticvoid main(String[] args) {
Thread[]threads= newThread[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++; //将值加一
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 问题表述
生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程--即所谓的"生产者"和"消费者"--在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。
该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
3.2 实现思路
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
3.3 Java实现该问题的代码
3.3.1 Queue队列代码:
packageorg.yang;
importjava.util.LinkedList;
importjava.util.concurrent.locks.Condition;
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
publicclass Queue { //队列
private Lock lock =new ReentrantLook(); //锁
private Condition fullC; //信号量
private Condition emptyC; //信号量
private int size;
public Queue(int size) {
this.size = size;
//(2)为信号量附初值
fullC = lock.newCondition();
emptyC = lock.newCondition();
}
LinkedList<Integer> list = newLinkedList<Integer>();
/**
* 入队
*@return
*/
public boolean EnQueue(int data) {
lock.lock();//上锁
while(list.size()>=size) {
fullC.await();
} catch (InterruptedException e) {
lock.unlock();
return false;
}
}
list.addLast(data);
emptyC.signalAll();
lock.unlock();
return true;
}
/**
* 出队
*@return
*/
public int DeQueue() {
lock.lock();//线上所
while(list.size() == 0) {
try {
emptyC.await();
}catch (InterruptedException e) {
lock.unlock();
return -1;//失败返回
}
}
int r = list.removeFirst();//获取队列头部
fullC.signalAll();//唤醒所有生产者
lock.unlock();//解锁
return r;
}
public boolean isFull() {
return list.size()>=size;
}
public boolean isEmpty() {
return list.size()==0;
}
}
3.3.2 producer实验:
package org.yang;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 生产者
*/
publicclassProducer implementsRunnable {
private Queue q;
private Condition isFull; //信号量,如果满了则等待
private Condition isEmpty; //信号量,如果空了则等待
private Lock lock;
privateintindex; //生产者的编号
public Producer(intindex,Queue q,Lock lock,Condition isFull,Condition isEmpty) {
this.index = index;
this.q= q;
this.isFull = isFull;
this.isEmpty = isEmpty;
this.lock = lock;
}
@Override
publicvoid run() {
lock.lock();
if(q.isFull()) {
try {
isFull.await(); //如果队列为慢,则等待
}catch(InterruptedException e) {
return;
}
}
//生产并入队
inta = (int) (Math.random()*1000);
q.EnQueue(a);
//生产完后
isEmpty.signalAll();//把消费者唤醒。
lock.unlock();
}
}
3.4 测试
3.4.1实验TestPC:
package org.yang;
publicclassTestPC {
static Queue queue = new Queue(5);
publicstaticvoid main(String[] args) {
//创建三个生产者
for(inti=0;i<3;i++) {
finalintindex = i;
new Thread(()->{
intdate =(int)(Math.random()*1000);
System.out.printf("thread %d want to EnQueue %d\n",index,date);
queue.EnQueue(date);
System.out.printf("thread %d EnQueue %d Success\n",index,date);
sleep();//随机休息一段时间
}
}).start();
}
//创建消费者
for(inti = 0;i<3;i++) {
finalintindex = i;
new Thread(()->{
while(true) {
System.out.printf("customer thread %d want to DnQueue %d\n",index);
intdata = queue.DeQueue();
System.out.printf("customer thread %d Finish DnQueue %dSuccess\n",index,date);
sleep2();//随机休息一段时间
}
}).start();
}
}
}
//sleep随机时间
publicstaticvoid sleep() {
int t = (int)(Math.random()*100);
try {
Thread.sleep(t);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
3.4.2 当生产能力超出消费能力时的表现
publicstaticvoidsleep() {
int t = (int)(Math.random()*100);
try {
Thread.sleep1(t);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
3.4.3 当生产能力弱于消费能力时的表现
System.out.printf("customer thread %d want to DnQueue %d\n",index);
intdata = queue.DeQueue();
System.out.printf("customer thread %d Finish DnQueue %dSuccess\n",index,date);
sleep2();//随机休息一段时间
}
}).start();
}
}
4 总结
整个程序的设计过程中出现了很多问题,比如在设计过程中该设计几个类,各个类该完成什 么样的功能,如何实现各个功能,一个类如何调用另一类中的变量、如何处理事件等等许多问题,在代码设计过程中,对JAVA 面向的思想有了更深入的了解,程序中用到了很多操作系统的思想, 操作,信号量的互斥实现,线程的概念,对这些概念理解的更加透彻。
实验过程的同时认识到了自己的不足,在以后的学习中应更加努力。在开始写代码前,一定要有个整体的规划,如设计几个类,每个类完成什么样的功能,应该设计几个变量,几个方法,各个方法能完成的功能等等。在写代码时,遵循 Java 编程规范。