(1)程序:① 是选择一种编程语言,完成一个功能/任务,而编写的一段代码,这段代码最后被编译/解释为指令。
② 程序是一组指令的集合。
③ 程序是静态的。当我们电脑,手机安装了一个程序之后,只是占用硬盘/存储卡的空间。
(2)进程:① 一个程序的一次运行。
② 当程序启动后,操作系统都会给这个程序分配一个进程的ID,并且会给他分配一块独立的内存空间。
③ 如果一个程序被启动了多次,那么会有多个进程。
④ 多个进程之间是无法共享数据。
⑤ 如果两段代码需要进行数据的交互,成本比较高,要么通过一个硬盘的文件,要么通过网络。
⑥ 进程之间的切换成本也比较高。现在的操作系统都支持多任务,操作系统在每个任务之间进行切换,要给整个进程做镜像,要记录当前进程的状态,执行到那个指令的。
⑦ 操作系统分配资源的最小单位是进程。
⑧ 每一个进程至少有一个线程。
(3)线程:① 线程是进程中的其中一条执行路径。多个线程会同属于一个进程。
② 这多个线程会共享同一个进程中的一些资源。比如java中堆内存的数据,方法区的数据。
③ 如果同一个进程的两段代码需要进行数据的交互,非常方便,可以直接在内存中共享。当然,同时要考虑安全问题。
④ 线程之间的切换,需要记录的信息要少很多,因为很多线程之间的数据是共享的,这些数据就不用单独在做镜像了,只需要记录每一个线程要执行的下一条指令。
⑤ CPU调度的最小单位是线程。
Java程序中一个main方法的程序,就是一个进程。但是可以在main中,再开启多个线程。
Java程序后台有几个线程是默默运行的:GC线程,异常的检查和处理线程,类加载器线程。
并行:指两个或多个事件在同一时刻发生(同时发生)。指在同一时刻,有多条指令在多个处理器上同时执行。(并行只能在多核CPU上才能运行)
例子:多项工作一起执行,之后再汇总,如:泡方便面,电水壶烧水,一边撕调料倒入桶中
并发:指两个或多个事件在同一个时间段内发生。指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
例子:同一时刻多个线程在访问同一个资源,多个线程对一个点,如:春运抢票
单核CPU:只能并发
多核CPU:并发+并行
一个进程至少要有一个线程,Java程序至少有一个main线程,是主线程。
Java中开启另一个线程的方式,一共有四种:
① 继承Thread类
② 实现Runnable接口
③ 实现Callable接口
④ 线程池
步骤:
① 编写一个类,让他继承Thread类
② 重写父类的public void run(){}
这个run()方法不是由程序员调用的,而是线程调度时,自动调用。
要让一个线程做什么事,必须把这个代码写到run方法中。
把run()方法的方法体,称为线程体。
③ 创建自定义线程类的对象
④ 启动线程,调用自定义线程类的对象的start()。
代码如下:
public class TestCreateThread {
public static void main(String[] args) {
MyThread my = new MyThread();
my.start();//这个start方法是从Thread类继承
// my.run();//不要手动调用run方法,如果手动调用这个方法,就不是线程了
/* new Thread(){//Thread创建线程的方式二
@Override
public void run() {
//让这个线程打印[1-10]的偶数
for(int i=2; i<=10; i+=2){
System.out.println("自定义线程: " + i);
}
}
}.start();*/
/*
Thread t = new Thread(){//Thread创建线程的方式三
@Override
public void run() {
//让这个线程打印[1-10]的偶数
for(int i=2; i<=10; i+=2){
System.out.println("自定义线程: " + i);
}
}
};
t.start();*/
//在main线程中,打印[1-10]的奇数
for(int i=1; i<=10; i+=2){
System.out.println("main:" + i);
}
}
}
class MyThread extends Thread{//Thread创建线程的方式一
@Override
public void run() {
//让这个线程打印[1-10]的偶数
for(int i=2; i<=10; i+=2){
System.out.println("自定义线程: " + i);
}
}
}
步骤:
① 编写线程类,实现Runnable接口
② 重写接口的抽象方法public void run()
③ 创建自定义线程类的对象
④ 创建一个Thread类的对象,同时让Thread对象代理我们的自定义线程对象,创建它的目的是为了调用start方法
⑤ 启动线程
线程调度器会调用 t 对象的run方法,因为这里启动的是 t 线程。(t.start())
Thread类的run()
@Override
public void run(){
if(target != null){
target.run();
}
}
这里的 target 对象就是创建 Thread 类对象时传入的 Runnable 接口的实现类对象,即被代理对象。
当 my 实参给 target 赋值后,target 就不会为 null,就会执行 my 对象的 run 方法
代码如下:
public class TestCreateThread2 {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
Thread t = new Thread(my);//让t对象代理my对象
t.start();
/* new Thread( new Runnable(){//Runnable创建线程的方式二
@Override
public void run() {
//让这个线程打印[1-10]的偶数
for(int i=2; i<=10; i+=2){
System.out.println("自定义线程: " + i);
}
}
}).start();*/
/* Thread t = new Thread(new Runnable(){//Runnable创建线程的方式三
@Override
public void run() {
//让这个线程打印[1-10]的偶数
for(int i=2; i<=10; i+=2){
System.out.println("自定义线程: " + i);
}
}
});
t.start();*/
//在main线程中,打印[1-10]的奇数
for(int i=1; i<=10; i+=2){
System.out.println("main:" + i);
}
}
}
class MyRunnable implements Runnable{//Runnable创建线程的方式一
@Override
public void run() {
//让这个线程打印[1-10]的偶数
for(int i=2; i<=10; i+=2){
System.out.println("自定义线程: " + i);
}
}
}
① public Thread():分配一个新的线程对象。
子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
class SubThread extends Thread{
//子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
public SubThread() {//默认调用无参构造器
super();
}
}
② public Thread(String name):分配一个指定名字的新的线程对象。
public class ThreadMethod1 {
public static void main(String[] args) {
SubThread s2 = new SubThread("线程2");
s2.start();
}
class SubThread extends Thread{
public SubThread(String name) {
super(name);
}
}
③ public Thread(Runnable target):分配一个带有指定目标新的线程对象
public class ThreadMethod1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
class SubThread extends Thread{
@Override
public void run() {
System.out.println(getName());//getName()方法从父类Thread继承的
}
}
④ public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。
public class ThreadMethod1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},"线程5").start();
}
}
class SubThread extends Thread{
@Override
public void run() {
System.out.println(getName());//getName()方法从父类Thread继承的
}
}
String getName():获取线程的名称
如果没有手动指定线程名称,默认是Thread-编号,从0开始
如果需要手动指定线程名称,可以通过构造器,或者setName(String name)方法设置线程名称。
class SubThread extends Thread{
//子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
public SubThread() {
super();
}
public SubThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(getName());//getName()方法从父类Thread继承的,获取线程名字
}
}
static Thread currentThread():获取执行当前语句的线程对象。
public class ThreadMethod1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//Thread.currentThread().getName()获取线程名字
System.out.println(Thread.currentThread().getName());
}
},"线程5").start();
}
}
整合代码如下:
public class ThreadMethod1 {
public static void main(String[] args) {
SubThread s1 = new SubThread();
SubThread s2 = new SubThread("线程2");
SubThread s3 = new SubThread();
s1.start();
s2.start();
s3.start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},"线程5").start();
}
}
class SubThread extends Thread{
//子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
public SubThread() {
super();
}
public SubThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(getName());//getName()方法从父类Thread继承的
}
}
子类构造器首行一定会调用父类的构造器,默认调用的就是无参构造
方法:
① public final int getPriority():返回线程优先级
② public final void setPriority(int newPriority):改变线程的优先级
线程优先级高的,有更多的机会/概率被优先调用。
线程优先级范围:[MIN_PRIORITY,MAX_PRIORITY],即[1,10]
3个常量值:
MIN_PRIORITY:1
MAX_PRIORITY:10
NORM_PRIORITY:5
代码如下:
public class ThreadMethod2 {
public static void main(String[] args) {
// System.out.println(Thread.MAX_PRIORITY);
// System.out.println(Thread.MIN_PRIORITY);
Thread t = new Thread(){
public void run(){
/*for(int i=2; i<=10; i+=2){
System.out.println("自定义线程:" + i);
}*/
System.out.println(getName() +":" + getPriority());//得到线程优先级
}
};
// t.setPriority(100);//java.lang.IllegalArgumentException 非法参数异常
//当设置的优先级不在 MIN_PRIORITY 到 MAX_PRIORITY 范围内,就会报这个异常。
// t.setPriority(Thread.MAX_PRIORITY);
t.start();
/* for(int i=1; i<=10; i+=2){
System.out.println("主线程:" + i);
}*/
System.out.println(Thread.currentThread().getName() +":" + Thread.currentThread().getPriority());
}
}
public static void sleep(long millis) throws InterruptedException:线程休眠,单位毫秒
public static void yield():让当前线程暂停一下。当前线程暂停下,让出CPU,但是下一次CPU有可能还是调用它。
void join() throws InterruptedException:等到该线程终止。该线程是调用join方法的线程。
void join(long millis):等待该线程终止的时间最长为 millis 毫秒。如果 millis 时间到,将不再等待。
void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
public class ThreadMethod3 {
public static void main(String[] args) {
/*for(int i=10; i>=1; i--){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("新年到!");*/
Thread t = new Thread("偶数线程"){
@Override
public void run(){
for(int i=2; i<=20; i+=2){
System.out.println(getName() +":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
for(int i=1; i<=100; i+=2) {
System.out.println(Thread.currentThread().getName() + ":" + i);
if(i==5) {
//让奇数线程暂停一下
// Thread.yield();
//让奇数线程停止,彻底让出CPU,让偶数线程执行完再继续
/*try {
t.join();//偶数线程加塞,阻塞了奇数线程
} catch (InterruptedException e) {
e.printStackTrace();
}*/
try {
t.join(5000);//偶数线程加塞5秒,阻塞了奇数线程5秒,5秒之后又抢CPU
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
案例:自定义线程类ChatThread:问是否结束(输入Y/y结束),如果输入的不是y,继续问是否结束,直到输入y才结束。
主线程打印[1,10],每隔10毫秒打印一个数字,现在当主线程打印完5之后,
就让自定义线程类加塞,直到自定义线程类结束,主线程再继续。
class ChatThread extends Thread{
public void run(){
//从键盘输入内容
Scanner input = new Scanner(System.in);
while(true){
System.out.print("是否结束(输入Y/y结束)?");
//charAt是将输入内容转换为字符串
/*char confirm = input.next().charAt(0);
if(confirm == 'Y' || confirm == 'y'){
break;
}*/
String answer = input.nextLine();
//因为String是引用数据类型,所以不能用 == 进行比较,== 比较的是地址,所以要用equals
//charAt是转化为字符串,Character.toUpperCase将小写字母转换为大写字母
if(!answer.equals("") && Character.toUpperCase(answer.charAt(0))== 'Y' ){
break;
}
}
input.close();
}
}
public class Exercise2 {
public static void main(String[] args) {
for(int i=1; i<=10; i++){
System.out.println(i);
if(i==5){
ChatThread t = new ChatThread();
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
龟兔赛跑
)案例:编写龟兔赛跑多线程程序,设赛跑长度为30米 ==>从1循环到30
兔子的速度是10米每秒(跑1米100毫秒sleep(100)),兔子每跑完10米休眠的时间10秒
乌龟的速度是1米每秒(跑1米1秒sleep(100)),乌龟每跑完10米的休眠时间是1秒
要求:要等兔子和乌龟的线程结束,主线程(裁判)才能公布最后的结果。
提示:System.currentTimeMillis()方法可以返回当前时间的毫秒值(long类型)
long start = System.currentTimeMillis();
中间代码
long end = System.currentTimeMillis();
System.out.println("中间代码运行耗时:" + (end - start) + "毫秒");
Tu代码:
public class Tu extends Thread {
private long time;
//给线程设置名字
public Tu(String name) {
super(name);
}
public void run(){
long start = System.currentTimeMillis();
for(int i=1; i<=30; i++){
try {
Thread.sleep(100);//休眠100毫秒,模拟每打印1个数字,代表跑了1米,花了100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() +"跑了" + i +"米");
if(i==10 || i==20){
try {
Thread.sleep(10000);//休息10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
time = end-start;
System.out.println(getName()+"耗时:" + time);
}
public long getTime() {
return time;
}
}
Gui代码:
public class Gui extends Thread {
private long time;
//给线程设置名字
public Gui(String name) {
super(name);
}
public void run(){
long start = System.currentTimeMillis();
for(int i=1; i<=30; i++){
try {
Thread.sleep(1000);//休眠1秒,模拟每打印1个数字,代表跑了1米,花了1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() +"跑了" + i +"米");
if(i==10 || i==20){
try {
Thread.sleep(1000);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
time = end-start;
System.out.println(getName()+"耗时:" + time);
}
public long getTime() {
return time;
}
}
执行代码:
public class Exercise3 {
public static void main(String[] args) {
Gui g = new Gui("乌龟");
g.start();
Tu t = new Tu("兔子");
t.start();
try {
g.join();//让乌龟这个线程执行完,才执行if方法,否则会直接输出 平局
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t.join();//让兔子这个线程执行完,才执行if方法,否则会直接输出 平局
} catch (InterruptedException e) {
e.printStackTrace();
}
if(g.getTime() < t.getTime()){
System.out.println("乌龟赢");
}else if(g.getTime() > t.getTime()){
System.out.println("兔子赢");
}else{
System.out.println("平局");
}
}
}
一个线程如何让另一个线程提前结束呢?
线程的死亡有两种:
自然死亡:当一个线程的 run 方法执行完,线程自然会停止
意外死亡:当一个线程遇到未捕获处理的异常,也会挂掉。
案例:声明一个PrintEvenThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的偶数,要求每隔1毫秒打印1个偶数。
声明一个PrintOddThread线程类,继承Thread类,重写run方法,实现打印[1,100]之间的奇数。
在main线程中:
(1)创建两个线程对象,并启动两个线程
(2)当打印奇数的线程结束了,让偶数的线程也停下来,就算偶数线程没有全部打印完[1,100]之间的偶数。
public class ThreadMethod4 {
public static void main(String[] args) {
PrintEvenThread p1 = new PrintEvenThread();
PrintOddThread p2 = new PrintOddThread();
p1.start();
p2.start();
try {
p2.join();//等待p2(打印奇数)线程结束
} catch (InterruptedException e) {
e.printStackTrace();
}
p1.setFlag(false);
}
}
class PrintEvenThread extends Thread{
private boolean flag = true;
public void run(){
for(int i=2; i<=100 && flag; i+=2){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
class PrintOddThread extends Thread{
public void run(){
for(int i=1; i<=100; i+=2){
System.out.println(i);
}
}
}
龟兔赛跑代码改进:
增加一个Sporter类代码:(将Gui和Tu的公共代码复用)
public class Sporter extends Thread {
private long time;
private long runtimePerMeter;//跑1米的时间
private long restTime;//休息时间
private static boolean flag = true;//所有Sporter对象共享
private final int MAX_DISTANCE = 30;//最多跑30米
private int distance;//已经跑了几米
public Sporter(String name, long runtimePerMeter, long restTime) {
super(name);
this.runtimePerMeter = runtimePerMeter;
this.restTime = restTime;
}
public void run(){
long start = System.currentTimeMillis();
while(distance < MAX_DISTANCE && flag){
try {
Thread.sleep(runtimePerMeter);
distance++;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() +"跑了" + distance +"米");
if(distance==10 || distance==20){
try {
Thread.sleep(restTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if(distance == MAX_DISTANCE){
System.out.println(getName()+"已经到达终点");
flag = false;
}
long end = System.currentTimeMillis();
time = end-start;
System.out.println(getName()+"耗时:" + time);
}
public long getTime() {
return time;
}
public int getDistance() {
return distance;
}
}
执行代码:
public class Exercise4 {
public static void main(String[] args) {
Sporter gui = new Sporter("乌龟",1000,1000);
Sporter tu = new Sporter("兔子",100,10000);
gui.start();
tu.start();
try {
gui.join();//这里阻塞的是main线程,和tu(兔子)线程无关。
/*
gui.join();这句代码是 main线程执行的,哪个线程执行,哪个线程就被gui线程给阻塞了。
此时gui和tu线程是并列的关系,还在竞争CPU资源,继续运行。
(1)情况一
如果此时tu(兔子)线程先结束了,main线程还要等gui它,结束才能执行下面的代码。等待gui线程结束,main才往下走。
tu.join(); 因为tu已经结束了,相当于就无法阻塞main线程了,就继续往下走。
(2)情况二
如果此时gui(乌龟)线程先结束了,main线程继续往下走,
tu.join();这句话,因为此时tu没结束,那么main就会被tu加塞了,等到tu线程结束,main才往下走。
(3)结论
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
tu.join();//这里阻塞的是main线程,和gui(乌龟)线程无关。
} catch (InterruptedException e) {
e.printStackTrace();
}
//*****(3)结论上面两个线程都结束才会执行下面的代码
if(tu.getDistance() > gui.getDistance()){
System.out.println("兔子赢");
}else if(tu.getDistance() < gui.getDistance()){
System.out.println("乌龟赢");
}else{
// System.out.println("平局");
// 在严格一点,如果都到达终点,看时间
if(gui.getTime() < tu.getTime()){
System.out.println("乌龟赢");
}else if(gui.getTime() > tu.getTime()){
System.out.println("兔子赢");
}else{
System.out.println("平局");
}
}
}
}