文章目录
概念
1、程序:由若干条具有一定功能的指令所组成的解题顺序和步骤
2、进程:程序的一次执行,是资源分配的最小单元
3、 线程:CPU的基本调度单位
抛开各种技术细节,从应用程序角度讲:
==
1、在单核计算机里,有一个资源是无法被多个程序并行使用的:cpu。
没有操作系统的情况下,一个程序一直独占着全都cpu。
如果要有两个任务来共享同一个CPU,程序员就需要仔细地为程序安排好运行计划–某时刻cpu和由程序A来独享,下一时刻cpu由程序B来独享
而这种安排计划后来成为OS的核心组件,被单独名命为“scheduler”,即“调度器”,它关心的只是怎样把单个cpu的运行拆分成一段一段的“运行片”,轮流分给不同的程序去使用,而在宏观上,因为分配切换的速度极快,就制造出多程序并行在一个cpu上的假象。
2、在单核计算机里,有一个资源可以被多个程序共用,然而会引出麻烦:内存。
在一个只有调度器,没有内存管理组件的操作系统上,程序员需要手工为每个程序安排运行的空间 – 程序A使用物理地址0x00-0xff,程序B使用物理地址0x100-0x1ff,等等。
然而这样做有个很大的问题:每个程序都要协调商量好怎样使用同一个内存上的不同空间,软件系统和硬件系统千差万别,使这种定制的方案没有可行性。
为了解决这个麻烦,计算机系统引入了“虚拟地址”的概念,从三方面入手来做:
2.1、硬件上,CPU增加了一个专门的模块叫MMU,负责转换虚拟地址和物理地址。
2.2、操作系统上,操作系统增加了另一个核心组件:memory management,即内存管理模块,它管理物理内存、虚拟内存相关的一系列事务。
2.3、应用程序上,发明了一个叫做【进程】的模型,(注意==)每个进程都用【完全一样的】虚拟地址空间,然而经由操作系统和硬件MMU协作,映射到不同的物理地址空间上==。不同的【进程】,都有各自独立的物理内存空间,不用一些特殊手段,是无法访问别的进程的物理内存的。
3、现在,不同的应用程序,可以不关心底层的物理内存分配,也不关心CPU的协调共享了。然而还有一个问题存在:有一些程序,想要共享CPU,【并且还要共享同样的物理内存】,这时候,一个叫【线程】的模型就出现了,它们被包裹在进程里面,在调度器的管理下共享CPu,拥有同样的虚拟地址空间,同时也共享同一个物理地址空间,然而,它们无法越过包裹自己的进程,去访问别一个进程的物理地址空间。
4、进程之间怎样共享同一个物理地址空间呢?不同的系统方法各异,符合posix规范的操作系统都提供了一个接口,叫mmap,可以把一个物理地址空间映射到不同的进程中,由不同的进程来共享。
5、PS:在有的操作系统里,进程不是调度单位(即不能被调度器使用),线程是最基本的调度单位,调度器只调度线程,不调度进程,比如VxWorks
一个线程的生命周期
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的优先级
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
线程的优先级只是理论上的优先级,它可以让线程优先级高的可能概率稍微大的先执行,但是具体执行哪一个线程却是由电脑控制的。众所周知,计算机对于线程的执行是抢占式策略,谁能抢到谁先执行,因此这个方案并不能完全控制顺序。
来自一些问答:
1、优先级高不代表说一定是优先级最高的运行完了再让优先级地的运行。否则那样多线程就没意义了。
优先级高就是说同等条件下我运行的概率会比优先级低的高,至于具体怎么分配,这样看虚拟机的策略。但是肯定不会是像你想的那样,优先级高的运行完才运行优先级低的
2、首先说 线程优先级,并不能保证优先级高的先运行,也不保证优先级高的更多的分配CPU时间,只是对系统的建议而已,到底运行哪个,是操作系统决定的,都不是java说了算的。
另外java只能保证在线程内部看起来是顺序执行你的代码的,并不能保证从其他线程看来这个是按照你编码顺序执行的。
3、根据java语言规范,程序的正确性和性能保障,不能依靠优先级。
要靠程序自己控制。
优先级这个东西一般在系统资源紧缺的情况下可能会比较明显。
你这个经过运行时优化之后,几乎没有什么了。
所以体现不出来。
创建一个线程
Java 提供了三种创建线程的方法:
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
通过实现 Runnable 接口来创建线程
class RunableDemo implements Runnable{
private Thread t;
private String name;
RunableDemo(String name){
this.name = name;
System.out.println("create:"+this.name);
}
public void run(){
int i;
try{
for(i=4;i>0;i--){
System.out.println("run Thread:"+this.name+","+i);
System.out.println("sleep:"+this.name+","+i);
Thread.sleep(50);
}
}catch(InterruptedException e){
System.out.println("interrupted:"+name);
}
System.out.println("exiting:"+this.name);
}
public void start(){
System.out.println("--------");
System.out.println("Thread start:"+this.name);
if(t==null){
t = new Thread(this,name);//创建名为name的thread
t.start();//不是上面的start();
}
}
}
public class ThreadTest{
public static void main(String[] args){
RunableDemo r1 = new RunableDemo("gyy1");
//r1.t = new Thread("gyy1");//错误,不能访问private
r1.start();
RunableDemo r2 = new RunableDemo("gyy2");
r2.start();
}
}
结果:有可能gyy1先结束,又可能gyy2先结束。
E:\java_code\code>java ThreadTest
create:gyy1
--------
Thread start:gyy1
create:gyy2
--------
run Thread:gyy1,4
Thread start:gyy2
sleep:gyy1,4
run Thread:gyy2,4
sleep:gyy2,4
run Thread:gyy1,3
run Thread:gyy2,3
sleep:gyy1,3
sleep:gyy2,3
run Thread:gyy1,2
sleep:gyy1,2
run Thread:gyy2,2
sleep:gyy2,2
run Thread:gyy2,1
run Thread:gyy1,1
sleep:gyy2,1
sleep:gyy1,1
exiting:gyy2
exiting:gyy1
通过继承Thread来创建线程
//class RunableDemo implements Runnable{
class RunableDemo extends Thread{
private Thread t;
private String name;
RunableDemo(String name){
this.name = name;
System.out.println("create:"+this.name);
}
public void run(){
int i;
try{
for(i=4;i>0;i--){
System.out.println("run Thread:"+this.name+","+i);
System.out.println("sleep:"+this.name+","+i);
Thread.sleep(50);
}
}catch(InterruptedException e){
System.out.println("interrupted:"+name);
}
System.out.println("exiting:"+this.name);
}
public void start(){
System.out.println("--------");
System.out.println("Thread start:"+this.name);
if(t==null){
t = new Thread(this,name);//创建名为name的thread
t.start();//不是上面的start();
}
}
}
public class ThreadTest{
public static void main(String[] args){
RunableDemo r1 = new RunableDemo("gyy1");
//r1.t = new Thread("gyy1");//错误,不能访问private
r1.start();
RunableDemo r2 = new RunableDemo("gyy2");
r2.start();
}
}
程序
1、通过键盘输入一个数字,计算机循环随机得到一个数字看是否与输入的数字相等,如果不相等继续循环,反之停止。
import java.util.Scanner;
class GuessTest extends Thread{
private int number;
GuessTest(int number){
this.number = number;
System.out.println("number:"+this.number+",yes or not?");
}
public void run(){
int guess;
int count=0;
do{
guess =(int)(Math.random()*100+1);
System.out.println(this.getName()+" guess:"+guess);
count++;
}while(guess!=this.number);
System.out.println("okok!yes:"+this.getName()+":guess:"+guess+",count:"+count);;
}
}
public class GuessNumber{
public static void main(String[] args){
int i=0;
Scanner s = new Scanner(System.in);
System.out.println("请输入你的预测数字(整数):");
if(s.hasNextInt()){
i = s.nextInt();
System.out.println("输入整数:"+i);
}else{
System.out.println("输入的不是整数。");
}
GuessTest g = new GuessTest(i);
g.start();
}
}
2、多线程方法的使用:包括各个方法及线程按顺序执行
//需要Thread与start
//采用Runnable接口
public class DisplayMessage implements Runnable{
private String mge;
DisplayMessage(String mge){
this.mge = mge;
}
public void run(){
System.out.println(mge);
System.out.println(mge);
// while(true){
// System.out.println(mge);
// }
}
}
//采用Thread类
class GuessANumber extends Thread{
private int number;
GuessANumber(int number){
this.number = number;
System.out.println("number:"+this.number+",yes or not?");
}
public void run(){
int guess;
int count=0;
do{
guess =(int)(Math.random()*100+1);
System.out.println(this.getName()+" guess:"+guess);
count++;
}while(guess!=this.number);
System.out.println("okok!yes:"+this.getName()+":guess:"+guess+",count:"+count);;
}
}
public class ThreadClassDemo{
public static void main(String[] args){
// //Runnable
// Runnable hello = new DisplayMessage("Hello");
// Thread thread1 = new Thread(hello);
// thread1.setDaemon(true);
// thread1.setName("hello");
// System.out.println("Starting hello thread...");
// thread1.start();
DisplayMessage r1 = new DisplayMessage("gyy1");
Thread th1 = new Thread(r1);
th1.setDaemon(true);
System.out.println("start "+th1.getName()+" Thread……");
th1.setName("ggyy1");
System.out.println("start "+th1.getName()+" Thread……");
th1.start();//打印的是mge不是name
Runnable r2 = new DisplayMessage("gyy2");
Thread th2 = new Thread(r2);
th2.setPriority(Thread.MIN_PRIORITY);
th2.setDaemon(true);
System.out.println("start "+th2.getName()+" Thread……");
th2.start();
//Thread
GuessANumber g1 = new GuessANumber(9);
System.out.println("Starting "+g1.getName()+"...");
g1.start();
try{//去掉会使得可能g2先执行。
g1.join();
}catch(InterruptedException e){
System.out.println("Thread interrupted.");
}
GuessANumber g2 = new GuessANumber(17);
System.out.println("Starting "+g2.getName()+"...");
g2.start();
System.out.println("main() is ending...");
}
}
3、让线程按顺序执行
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo3{
public static void main(String[] args){
method3();
}
private static void method3(){
final Thread t1 = new Thread(new Runnable(){
@Override
public void run(){
System.out.println("111");
}
});
final Thread t2 = new Thread(new Runnable(){
@Override
public void run(){
System.out.println("222");
}
});
final Thread t3 = new Thread(new Runnable(){
@Override
public void run(){
System.out.println("333");
}
});
//创建一个newSingleThreadExecutor的线程池
ExecutorService ss = Executors.newSingleThreadExecutor();
//队列,先进先出
ss.submit(t1);
ss.submit(t2);
ss.submit(t3);
ss.shutdown();
}
}
通过 Callable 和 Future 创建线程
创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
具体:
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
//采用Callable和Future创建线程
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable<Integer>{//创建Callable接口的实现类
public static void main(String[] args){
int i;
CallableDemo cc = new CallableDemo();//创建Callable接口的实现类的对象
FutureTask<Integer> ff = new FutureTask<Integer>(cc);//用FutureTask来包装Callable对象
for(i=0;i<100;i++){
System.out.println("线程:"+Thread.currentThread().getName()+"i的值:"+i);
if(i==20){
Thread tt = new Thread(ff,"gyy");//Future对象创建线程
tt.start();
}
}
System.out.println("循环结束");
try{
System.out.println("线程返回值:"+ff.get());//返回call里的返回值
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}
}
//call返回值
@Override
public Integer call() throws Exception{
int j;
for(j=0;j<100;j++){
System.out.println("线程:"+Thread.currentThread().getName()+"j值:"+j);
}
return j;
}
}
出错:
E:\java_code\code>javac CallableDemo.java
CallableDemo.java:31: 错误: 找不到符号
}catch(ExecutionException e){
^
符号: 类 ExecutionException
位置: 类 CallableDemo
1 个错误
解决:
将 ExecutionException 改成 Exception
创建线程的三种方式的对比
- 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
- 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
线程的几个主要概念
在多线程编程时,你需要了解以下几个概念:
线程同步
线程间通信
线程死锁
线程控制:挂起、停止和恢复
多线程的使用
有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!