概念:
进程是并发执行的程序在执行过程中分配和管理资源的基本单位。进程有5种基本状态,分别是初始态、执行态、等待状态、就绪状态、终止状态。
线程是进程的一部分,如果一个进程没有线程则可以被看作单线程。
差别:
进程的执行过程是线性的,尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。这是进程宏观上的执行过程。
线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。除了 CPU 之外,计算机内的软硬件资源的分配与线程无关,
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,创建名为MyR的类,作为Runnable接口的实现类并复写run()方法,用while循环控制输出,就可以启动新线程并执行自己定义的run()方法
1、继承Thread类创建线程类(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。(2)创建Thread子类的实例,即创建了线程对象。(3)调用线程对象的start()方法来启动该线程。
2、通过Runnable接口创建线程类(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。(2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。(3)调用线程对象的start()方法来启动该线程。
3、通过Callable和Future创建线程(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
代码1
/*Runnable的实现类,是线程执行的主体。
* run函数是入口
*
*/
classMyR implements Runnable{
private String msg;
public MyR(String msg) {
this.msg = msg;
}
//线程的入口
@Override
public void run() {
while(true) {
try {
System.out.println(msg);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
publicclass TestThread {
public static void main(String[] args) {
//创建了线程
Thread thread1 = new Thread(newMyR("hello"));
thread1.start(); //启动了线程
Thread thread2 = new Thread(newMyR("wuwu"));
thread2.start(); //启动了线程
}
}
代码二
public class TestThread2 {
publicstatic void main(String[] args) {
TestThread2testThread2 =new TestThread2();
//匿名信 匿名类 引用就是指针
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();
}
}
代码三
publicclass TestThread3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("haha");
}
}).start();
// lamda 表达式 java 1.8+
new Thread(()->{
System.out.println("haha");
}).start();
}
}
同步的概念:同步就是协同步调,按预定的先后次序进行运行。进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
同步的必要性:同步可以保证在同一时刻,只有一个线程可以进入临界区执行代码,因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。
1、如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,则会提示存钱成功,取钱不成功。
2、当同学们在网络教学平台选课时。若当时已经没有课,一人选课一人退课,则退课的同学能成功退课,选课的同学则无课可选。
关键字的作用域有两种:
(1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法
(2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。
2.3 实例
TestSync.java
package org.yang;
import java.util.ArrayList;
public class TestSync {
staticint c = 0;
staticObject lock = new Object(); //(1) 随便建立了一个变量,作为锁变量
publicstatic void main(String[] args) {
Thread[]threads = new Thread[1000];
for(inti=0;i<1000;i++) {
finalint index = 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的结果
}
}
创建一个队列,用来当做生产者和消费者的缓冲区,这个缓冲区应该是线程内唯一的。创建两个线程,一个是生产者线程一个是消费者线程,当生产者生产了物品就放到这个缓冲区中,而消费者时时刻刻可以从这个缓冲区中去拿物品,只要是有就可以拿,生产者只要是缓冲区不满就可以往里面放。
package ly;
importjavax.net.ssl.SSLException;
public classTestPC {
static Queue queue=new Queue(5);
public static void main(String[] args) {
for(int i=0;i<3;i++) {
final int index=i;
new Thread(()-> {
intdata=(int)(Math.random()*1000);
System.out.printf("thread%d want to EnQueue %d\n"+data);
queue.EnQueue(data);
System.out.printf("thread%d EnQueue %d Success\n"+data);
sleep();
}).start();
}
for(int i=0;i<3;i++) {
final int index=i;
new Thread(()-> {
while(true) {
System.out.printf("customerthread %d want to DnQueue %d\n",index);
int data=queue.DeQueue();
System.out.printf("customerthread %d DnQueue %dSuccess\n",index,data);
sleep();
}
}).start();
}
}
public static void sleep() {
int t=(int)(Math.random()*1000);
try {
Thread.sleep(t);
} catch (InterruptedException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}
3.4.2 当生产能力弱于消费能力时的表现
生产能力弱于消费能力时,消费者线程消费物品,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产者线程生产出来
通过今天慢慢一天的实践课程,让我对计算机操作系统有了一个不一样的认识。对路程和线程也有了比较深刻的认识。学会了用synchronized解决线程同步问题,认识到路程和线程的区别以及他们之间的联系,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。通过对生产者与消费者的问题,让我体会到同步对于生产者消费者的重要意义。虽然一天的学习比较辛苦,在实验过程中遇到了很多困难,但是我们迎难而上,克服了困难,挑战了自己。