目录
一、程序、进程和线程
程序
进程
线程
进程与线程的区别
二、线程
线程的状态
线程的创建
进程调度与相关API
多线程案例
一、火车站多窗口卖票
二、家庭消费
一、程序、进程和线程
程序
程序是计算机指令的集合;程序是一组静态的指令集,不占用系统运行资源,不能被系统调度,也不能作为独立运行的单元,程序以文件的形式存储在磁盘上。
进程
进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统),它才能成为一个活动的实体,称其为进程。进程是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所以多道程序设计操作系统都建立在进程的基础上。
- 狭义上,进程是正在运行的程序的实例。
- 广义上,进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。进程是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
通俗地说,进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)。
进程是一个实体。每一个进程都有它自己的地址空间。一般包括:
- 文本区(text region):存储处理器执行的代码;
- 数据区域(date region):存储变量和进程执行期间使用的动态分配的内存;
- 堆栈(stack region):存储活动过程调用的指令和本地变量。
线程
线程与进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其他线程共享一个存储空间,即同一个进程中的多个线程共享一款内存空间和一组系统资源。 线程本身也有一个供程序执行时的堆栈,在线程切换时,负荷小,因此线程也被称为轻量级进程。
线程是进程中所包含的一个或多个执行单元。线程只能归属于一个进程,并且它只能访问该进程所拥有的资源,一个进程可以拥有多个线程。
一个线程就是进程的一个顺序执行流,可以说线程是应用程序中的一个任务执行流。
并发原理:操作系统将时间划分成很多时间片段,尽可能的均匀分配给每一个线程,获取时间片段的线程被CPU运行,而其他线程处于等待状态。所以这种微观上是走走停停,断断续续的,宏观上都在运行的现象叫并发。但不是绝对意义上的“同时发生”。线程的本质是CPU的分片运行。
进程与线程的区别
- 进程是操作系统运行的一个任务,线程是进程中运行的一个任务。
- 进程是资源分配的最小单位(相互独立),线程是程序执行的最小单位(cpu调度的基本单元)。
- 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据 段,这种操作非常昂贵。 进程之间的通信需要以IPC进行;线程是轻量级的进程,同一个进程中可以包含多个线程。 多线程共享进程中的数据,使用相同的地址空间, 因此,线程之间的通信更方便,CPU切换(或创建)一个线程的开销远比进程要小很多。
- 一个进程结束,其内的所有线程都结束,但不会对另外一个进程造成影响。多线程程序,一个线程结束,有可能会造成其他线程结束。
二、线程
线程的状态
- 新建状态:新创建了一个线程对象。
- 就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态:就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。 阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程的创建
1、将类声明为Thread的子类,该子类重写Thread类的run方法
/**任务: 打印1到100万*/
class MyThread extends Thread{
public void run() {
for(int i=0;i<1000000;i++) {
System.out.println(i);
}
}
}
然后在main方法(主线程)创建并启动一个线程:
Thread t1 = new MyThread();
t1.start();
当线程中的run()方法执行完,线程即视为结束
2、声明实现Runnable接口的类,该类实现run方法
/*任务:打印1W行 "Hello" */
class Task implements Runnable{
@Override
public void run() {
for(int i=0;i<10000;i++) {
System.out.println("Hello");
}
}
}
然后创建该类(任务体)的实例,在创建Thread时作为一个参数来传递并启动
Runnable task = new Task();
Thread t1 = new Thread(task);
t1.start();
*方法二明显优于方法一,方法二将线程对象和线程任务对象分离开。降低了耦合性,利于维护实现Runnable接口;
与方法一比较所具有的优势在于:适合多个相同的程序代码的线程去处理同一个资源;可以避免java中的单继承的限制;增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
3、创建FutureTask对象,传入Callable子类对象,重写call方法(相当于run),将FutureTask对象(相当于Runnable)传入Thread对象并启动
优势在于可以获取Callable任务的返回值。
public class ThreadDemo {
public static void main(String[] args) throws Exception, Exception {
/*计算1到100的和*/
FutureTask<Integer> task =
new FutureTask<Integer>(
new Callable<Integer>() {
public Integer call() throws Exception {
int sum = 0;
for(int i=1;i<=100;i++) {
sum+=i;
}
System.out.println("--------");
return sum;
}
});
Thread t1 = new Thread(task);
t1.start();
/* FutureTask的get方法是获取Callable任务的返回值。
* 而get方法是一个阻塞效果。
*/
System.out.println(task.get().intValue());
}
}
进程调度与相关API
1、static void yield():线程让步方法,写在run()里。会暂停当前正在执行的线程对象,使之处于就绪状态,把执行机会让给相同或者更高优先级的线程。让出后,下一个时间片段还有可能被分配给此线程。
2.static void sleep(long millis):线程睡眠方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。要写在try-catch块中,sleep()方法平台移植性好。
3、每个线程都有一个优先级,高优先级线程的执行由于低优先级线程
通过getPriority()方法可以获取线程的优先级。线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了三个常量来表示优先级:
最低(1):Thread.MIN_PRIORITY;最高(10):Thread.MAX_PRIORITY;以及默认优先级(5):Thread.NORM_PRIORITY
通过setPriority()改变线程优先级。尽管优先级设置为最高,但是也不一定会先完成任务,最终还是cpu决定的。
我们可以来测试一下优先级
public class test {
public static void main(String[] args) {
for(int i = 0;i < 10;i++){
new Thread(new SimplePriorty(Thread.MAX_PRIORITY)).start();
new Thread(new SimplePriorty(Thread.MIN_PRIORITY)).start();
}
}
}
class SimplePriorty implements Runnable{
private int priorty;
private volatile double d;
public SimplePriorty(int priorty){
this.priorty = priorty;
}
public void run(){
Thread.currentThread().setPriority(this.priorty);
System.out.println("优先级:"+Thread.currentThread().getPriority()+",线程号"+Thread.currentThread().getId()+"is started");
//做大型浮点运算
for(int i = 0;i < 10000000 ;i++){
d += (Math.PI+Math.E)/(double)i;
if(i%100 == 0){
Thread.yield();
}
}
System.out.println("优先级:"+Thread.currentThread().getPriority()+",线程号"+Thread.currentThread().getId()+"is over");
}
}
结果如下
4、守护进程
线程分两类:前台线程即普通线程;后台线程即守护线程。
当Java虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的main方法);将线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,java虚拟机退出。即当只剩下守护线程后,守护线程会立即结束。通过setDaemon(boolean f)可以设置守护线程。
例:Rose要跳海,每0.5秒喊一次" I'll jump "。Jack来守护她,Rose每喊一次他就喊一次"You jump,I jump"。Rose跳海了之后。Jack也跟着跳了。
public class Titanic {
public static void main(String[] args) {
Thread rose = new Thread() {
public void run() {
for(int i=0;i<10;i++) {
System.out.println("Rose:I'll jump");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("poooom!!!");
}
};
Thread jack = new Thread() {
public void run() {
while(true) {
System.out.println("Jack:You jump,I jump!!!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
/*将 jack设置为守护线程
* setDaemon(boolean f)
* */
jack.setDaemon(true);
rose.start();
jack.start();
}
}
5、void join():在当前线程中A调用另一个线程B的join()方法,则当前线程A转入阻塞状态,直到另一个进程B运行结束,当前线程A再由阻塞转为就绪状态。
例:电影边看边下,下载完成了之后才能播放。
public class WatchingFilm {
public static void main(String[] args) {
Thread download = new Thread() {
public void run() {
for(int i=1;i<=100;i++) {
System.out.println("电影下载完成百分比:"+i+"%");
}
System.out.println("下载成功!!!");
}
};
Thread play = new Thread() {
public void run() {
/*匿名内部类可以直接访问外部类的变量,但是,必须使用final修饰
* jdk1.8以后,不需要使用
* */
try {
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=1;i<=100;i++) {
System.out.println("电影播放完成百分比:"+i+"%");
}
System.out.println("播放完成!!!");
}
};
download.start();
play.start();
}
}
6、void interrupt():当线程睡眠阻塞被打断,会报异常。
例:林永健在睡觉,黄宏每隔0.5秒砸一次墙,10次砸完了吵醒了林永健。
public class ThreadDemo08 {
public static void main(String[] args) {
Thread lin = new Thread() {
public void run() {
try {
Thread.sleep(80000);
System.out.println("林:睡的真香啊,好舒坦!!!!");
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!干嘛呢!!!");
}
}
};
Thread huang = new Thread() {
public void run() {
for(int i=0;i<10;i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("黄说:80!");
}
System.out.println("咣当一声!墙倒了");
System.out.println("黄说:搞定,800到手");
//此时,吵醒了lin
lin.interrupt();
}
};
huang.start();
lin.start();
}
}
7、结束进程
结束进程中stop()方法其实已过时,它会有一些安全隐患,实际上一般都是通过设置一个flag,改变flag的值来结束进程的。
看下面这个例子就懂了,线程在打印20次后结束该进程
public class StopThreadTest implements Runnable{
private boolean stopFlag = true;
public void run() {
int i = 0;
while(stopFlag){
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
i++;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public void stopThread() {
stopFlag = false;
}
public static void main(String[] args) {
int index = 0;
StopThreadTest ts = new StopThreadTest();
new Thread(ts).start();
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
index++;
System.out.println(Thread.currentThread().getName()+":"+index);
if(index == 20){
ts.stopThread();
}
}
}
}
多线程案例
一、火车站多窗口卖票
①
BookingTickets.java
public class BookingTickets implements Runnable{
public static Integer tickets = 100;
public void run() {
while(true){
synchronized (tickets) {
System.out.println("窗口" + Thread.currentThread().getId() + "准备出票");
if(tickets>0){
tickets = tickets-1;
}else{
break;
}
System.out.println("窗口" + Thread.currentThread().getId() + "车票剩余" + tickets);
}
}
}
}
TestTrain.java
public class TestTrain {
public static void main(String[] args) {
TestTrain.book();
}
private static void book() {
BookingTickets bt = new BookingTickets();
new Thread(bt).start();
new Thread(bt).start();
new Thread(bt).start();
new Thread(bt).start();
new Thread(bt).start();
}
}
②
Ticket.java
public class Ticket {
private String seatNo;
public Ticket(String seatNo) {
super();
this.seatNo = seatNo;
}
}
BookingTickets.java
import java.util.LinkedList;
public class BookingTickets extends Thread{
private LinkedList <Ticket> tickets;
public BookingTickets(LinkedList <Ticket> tickets) {
this.tickets = tickets;
}
public void run(){
while(true){
synchronized (tickets) {
if(tickets.size()>0){
System.out.println("准备出票");
tickets.removeLast();
System.out.println("车票剩余"+tickets.size());
}else{
break;
}
}
}
}
}
TestTrain.java
import java.util.LinkedList;
public class TestTrain {
public static void main(String[] args) {
TestTrain.book();
}
private static void book(){
LinkedList<Ticket> tickets = new LinkedList<Ticket>();
for(int i = 0;i<1000;i++){
Ticket t = new Ticket(i+"");
tickets.add(t);
}
Thread t1 = new BookingTickets(tickets);
t1.start();
Thread t2 = new BookingTickets(tickets);
t2.start();
Thread t3 = new BookingTickets(tickets);
t3.start();
}
}
二、家庭消费
家庭成员有父亲、母亲和儿子;家庭共用一个账户;父亲负责挣钱,儿子打游戏花钱,母亲购物花钱;要求不能负债消费
Family.java
public class Family {
private Father father;
private Mother mother;
private Son son;
private double account;
public void setFather(Father father) {
this.father = father;
}
public void setMother(Mother mother) {
this.mother = mother;
}
public void setSon(Son son) {
this.son = son;
}
public double getAccount() {
return account;
}
public void setAccount(double account) {
this.account = account;
}
}
Father.java
public class Father {
private String name;
private Family family;
public Father(String name,Family family){
this.name = name;
this.family = family;
this.family.setFather(this);
}
public String getName() {
return name;
}
public Family getFamily() {
return family;
}
public void earnMoney(double money){
System.out.println("爸爸赚钱前账户剩余:"+this.family.getAccount());
double account = money+this.family.getAccount();
this.family.setAccount(account);
System.out.println("爸爸赚钱后账户剩余:"+account);
}
}
Mother.java
public class Mother {
private String name;
private Family family;
public Mother(String name,Family family){
this.name = name;
this.family = family;
this.family.setMother(this);
}
public String getName() {
return name;
}
public Family getFamily() {
return family;
}
public void shopping(double money){
synchronized (this.family) {
double account = this.family.getAccount();
System.out.println("妈妈消费前剩余:"+account);
if(account < money){
System.out.println("余额不足了妈妈");
}else{
account = this.family.getAccount() - money;
this.family.setAccount(account);
System.out.println("儿子消费前剩余:"+account);
}
}
}
}
Son.java
public class Son {
private String name;
private Family family;
public Son(String name,Family family){
this.name = name;
this.family = family;
this.family.setSon(this);
}
public String getName() {
return name;
}
public Family getFamily() {
return family;
}
public void playGame(double money){
synchronized (this.family) {
double account = this.family.getAccount();
System.out.println("儿子消费前剩余:"+account);
if(account < money){
System.out.println("余额不足了儿子");
}else{
account = this.family.getAccount()-money;
this.family.setAccount(account);
System.out.println("儿子消费后剩余:"+account);
}
}
}
}
Test.java
public class Test {
public static void main(String[] args) {
Family family = new Family();
Father father = new Father("f", family);
Mother mother = new Mother("m", family);
Son son= new Son("s", family);
new Thread(new FatherThread(father)).start();
new Thread(new MotherThread(mother)).start();
new Thread(new SonThread(son)).start();
}
}
class FatherThread implements Runnable{
private Father father;
public FatherThread(Father father){
this.father = father;
}
public void run() {
while(true){
try {
Thread.sleep(1000);
this.father.earnMoney(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MotherThread implements Runnable{
private Mother mother;
public MotherThread(Mother mother) {
this.mother = mother;
}
public void run() {
while(true){
try {
Thread.sleep(800);
mother.shopping(8);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class SonThread implements Runnable{
private Son son;
public SonThread(Son son) {
this.son = son;
}
public void run() {
while(true){
try {
Thread.sleep(500);
son.playGame(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}