课堂总结
1 线程的创建与启动
1.1进程与线程
进程表示一个逻辑控制流,就是一种计算过程,它造成一个假象,好像这个进程一直在独占CPU资源,进程拥有一个独立的虚拟内存地址空间,它造成一个假象,好像这个进程一致在独占存储器资源
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)CPU分给线程,即真正在CPU上运行的是线程。
1.2 Java中的Thread和Runnable类
Java中线程的创建有两种方式:
1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中
2. 通过实现Runnable接口,实例化Thread类
1.3 三种创建线程的办法
1.继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。
2.实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。
3.创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。创建Thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方式可以获得线程执行完之后的返回值。
package org.yang;
/**
* Runnable的实现类,是线程执行的主体,
*
* @author Administrator
*
*/
class MyR implements Runnable{
private Stringmsg;
publicMyR(String msg) {
this.msg= msg;
}
@Override
public voidrun() {
while(true) {
try {
System.out.println(msg);
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public class TestThread {
public staticvoid main(String[] args) {
Thread thread1= new Thread(new MyR("hello"));
thread1.start();//启动了线程
Thread thread2= new Thread(new MyR("wuwu"));
thread2.start();//启动了线程
}
}
2、package org.yang;
public class TestThread2 {
public static void main(String[] args) {
//创建匿名类,引用就是指针
Runnable runnable = new Runnable() {//Runnable是一个接口
@Override
public void run() {
while(true) {
try {
System.out.println("haha");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
3、package org.yang;
import javax.swing.event.TreeWillExpandListener;
public class TestTread3 {
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();
}
}
、
4、队列package org.yang;
import java.util.LinkedList;
public class Queue { //队列
private int size;
public Queue(int size) {
this.size = size;
}
LinkedList<Integer>list = new LinkedList<Integer>();
/**
* 入队
* @return
*/
public boolean EnQueue(int data) {
if(list.size()>=size) return false;
list.addLast(data);
return true;
}
/**
* 出队
* @return
*/
public int DeQueue() {
if(list.size() == 0) return -1; //返回-1表示失败
return list.removeFirst();
}
}
2 线程简单同步(同步块)
2.1 同步的概念和必要性
线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。
2.2 synchronize关键字和同步块
synchronized 关键字包括synchronized 块。
synchronized 方法:通过在方法声明中加入synchronized关键字来声明 synchronized方法。控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
Synchronize关键字有以下三种使用方式:
1、同步普通方法,锁的是当前对象
2、 同步静态方法,锁的是当前Class对象
3、 同步块,锁的是{}中的对象
其本质就是对一个对象监视器( Monitor
)进行获取,而这个获取过程具有排他性从而达到了同一时刻只能一个线程访问的目的。
2.3 实例
TestSync.java
packageorg.yang;
importjava.util.ArrayList;
public classTestSync {
staticint c = 0;
staticObjectlock = 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++;//将值加一
try{//模拟复杂处理过程
Thread.sleep((long)(Math.random()*10));
}catch(InterruptedException e) {
//TODOAuto-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) {
//TODOAuto-generated catch block
e.printStackTrace();
}
}//循环后,所有的线程都完成了
System.out.print("c="+c);//输出c的结果
}
}
3 生产者消费者问题
3.1 问题表述
该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
3.2 实现思路
让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题
3.3 Java实现该问题的代码
package ly;
importjavax.net.ssl.SSLException;
public classTestPC {
static Queue queue=newQueue(5);
public static voidmain(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 测试
3.4.1 当生产能力超出消费能力时的表现
当生产能力超出消费能力时,生产者进程等待,可以调大生产者的sleep(),调小消费者的sleep(),从而加快消费者进程,将生产者进程唤醒。
3.4.2 当生产能力弱于消费能力时的表现
当生产能力弱于消费能力时,消费者进程等待,可以调小生产者的sleep(),调大消费者的sleep(),从而加快生产者进程,将消费者进程唤醒。
4总结
Java中万物皆对象,线程也被描述成了一个对象就是Thread对象 进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位