1. 线程相关概念 579
1.1 程序(program) 579
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码
1.2 进程 579
1.进程是指运行中的程序,比如我们使用QQ,就启动了一一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
2.进程是程序的一次执行过程,或是正在运行的一个程序。 是动态过程:有它自身的产生、存在和消亡的过程
2. 什么是线程 579
1.线程由进程创建的,是进程的一个实体(例如你在迅雷下载一个东西,而这个东西就是一个线程)
2.一个进程可以拥有多个线程,如下图
坦克大战[后面会把多线程加入到坦克大战中,学以致用]
2.1 其他相关概念 580
1.单线程:同个时刻,只允许执行一个线程
2.多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
3.并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发。
4.并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。
2.2 例 查看当前电脑有多少cpu 580
代码在com.stulzl.cpunum_.
CupNum
package com.stulzl.cpunum_;
//查看当前电脑有多少cpu 580
public class CupNum {
public static void main(String[] args) {
//都是一些方法不必纠结
Runtime runtime = Runtime.getRuntime();
//获取当前电脑cpu数量
int cpuNums = runtime.availableProcessors();
System.out.println("当前cpu数量="+cpuNums);
}
}
3. 线程基本使用 581
3.1 创建线程的两种方式 581
在java中线程来使用有两种方法。
1.继承Thread类 ,重写 run方法
2.实现Runnable接口, 重写run方法
3.2 线程应用案例 1-继承 Thread 类 581
1)请编写程序,开启一个线程, 该线程每隔1秒。在控制台输出"喵喵, 我是小猫咪”
2)对上题改进:当输出80次喵喵,我是小猫咪,结束该线程
3)使用JConsole监控线程执行情况,并画出程序示意图
代码在com.stulzl.thread_use01.包中
ThreadUse01
package com.stulzl.thread_use01;
import java.util.TreeMap;
//线程应用案例 1-继承 Thread 类 581
//1)请编写程序,开启一个线程, 该线程每隔1秒。在控制台输出"喵喵, 我是小猫咪”
public class ThreadUse01 {
public static void main(String[] args) throws InterruptedException {
//创建Cat对象,可以当作线程使用
Cat cat = new Cat();
cat.start();//启动线程->最终会执行cat的run方法
//读源码
/*
(1)
public synchronized void start() {
start0();
}
(2)
//start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
//真正实现多线程的效果, 是 start0(), 而不是 run
private native void start0();
*/
//之所以不直接调用 是因为run 方法就是一个普通的方法, 没有真正的启动一个线程
// ,就会把 run 方法执行完毕,才向下执行
//cat.run();
//说明: 当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
System.out.println("主线程继续执行"+Thread.currentThread().getName());//名字main
for (int i = 0; i <60; i++) {
System.out.println("主线程 i="+i);
//让主线程休息1秒
Thread.sleep(1000);//这里偷个懒,就不捕获异常了,我们直接抛出异常
}
}
}
//说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run 方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的 run 方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread{//继承Thread类,说明Cat就可以当作一个线程使用
int count = 0;
//重写润方法,写上自己的业务逻辑
@Override
public void run() {
while(true){
//该线程每隔1秒。在控制台输出"喵喵, 我是小猫咪”
System.out.println("喵喵, 我是小猫咪"+(++count)+"线程名="+Thread.currentThread().getName());
//让该线程休眠1秒
try {
Thread.sleep(1000);//1000毫秒时1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count==80){//如果输出80次就退出循环,也就等于退出了线程
break;
}
}
}
}
3.3 解析为什么不直接调用run()方法而是用start() 583
之所以不直接调用 是因为run 方法就是一个普通的方法, 没有真正的启动一个线程 ,就会把 run 方法执行完毕,才向下执行,这样就会产生阻塞
start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现,真正实现多线程的效果, 是 start0(), 而不是 run
在底层
start()方法调用start0()方法后(start0()方法真实是由JVM调用的,start0()方法才是真正调用run()方法的),该线程并不一定会立马执行,只是将线程变成了可运行状态。具
体什么时候执行,取决于CPU,由CPU统一调度。
4. 线程应用案例 2-实现 Runnable 接口 584
➢说明
1. java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程 显然不可能了。
2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
4.1 应用案例 584
请编写程序,该程序可以每隔1秒。在控制台输出"hi!" ,当输出10次后,自动退出。请使用实现Runnable接口的方式实现。
这里底层使用了设计模式[代理模式] =>代码模拟实现Runnable接口开发线程的机制
代码在com.stulzl.runnable_use.包中
Thread02
package com.stulzl.runnable_use;
// 线程应用案例 2-实现 Runnable 接口 584
//请编写程序,该程序可以每隔1秒。在控制台输出"hi!" ,当输出10次后,自动退出。
// 请使用实现Runnable接口的方式实现。
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog.start(); 这里不能调用 start
//创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread
// 这里底层使用了设计模式[代理模式] =>代码模拟实现Runnable接口开发线程的机制
//大白话解释,因为Dog是实现了Runnable接口 可是我们的Runnable接口中没有start()方法,
// 但是呢,我们模拟的Thead类有start()方法,而我还想用start实现线程,
// 就必须通过Thead类区去调用它的start()方法,巧了Thead类里有 有参构造器,
// 这就意味着我们可以把dog类扔进这个有参构造器,就相当于我们的Dog类进去了(为了后面动态绑定),
// 然后在调用 Thead类的start()方法即可,后涉及动态绑定等操作就实现了线程
//这个有参构造器接受的还是Runnable类型的,因为Dog实现了Runnable接口所以可以扔进有参构造器去
Thread thread = new Thread(dog);
thread.start();
//这里是我们模拟实现的代理模式
//大白话解释,因为tiger是实现了Runnable接口 可是我们的Runnable接口中没有start()方法,
// 但是呢,我们模拟的theadProxy类有start()方法,我还想用start实现线程,
// 就必须通过theadProxy类区去调用它的start()方法,巧了theadProxy类里有构造器 还是有参构造器,
// 这就意味着我们可以把tiger类扔进这个有参构造器,就相当于我们的Tiger类进去了,然后在调用
//theadProxy类的start()方法即可,后涉及动态绑定等操作就实现了线程
// Tiger tiger = new Tiger();
// TheadProxy theadProxy = new TheadProxy(tiger);//因为tiger也实现了Runnable接口所以可以扔进去
// theadProxy.start();
}
}
class Dog implements Runnable{//通过实现 Runnable 接口,开发线程
int count = 0;
@Override
public void run() {
while(true){
System.out.println("小狗汪汪叫...hi"+(++count)+Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count==10){
break;
}
}
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫……");
}
}
//这里底层使用了设计模式[代理模式] =>代码模拟实现Runnable接口开发线程的机制
//线程代理
//线程代理类 , 模拟了一个极简的 Thread 类
class TheadProxy implements Runnable{//TheadProxy是我们模拟的,可以看作系统的Thead
private Runnable target = null;//属性,类型是Runnable
//构造器
public TheadProxy(Runnable target){
this.target = target;
}
public void start(){
start0();
}
public void start0(){
run();
}
@Override
public void run() {
if(target!=null){
target.run();//动态绑定
}
}
}
5. 线程使用应用案例-多线程执行 585
请编写一个程序,创建两个线程,一个线程每隔1秒输出"hello,world" ,输出10次,退出,一个线程每隔1秒输出"hi" ,输出5次退出
代码在com.stulzl.thread03.包中
Thread03
package com.stulzl.thread03;
//线程使用应用案例-多线程执行 585
//请编写一个程序,创建两个线程,一个线程每隔1秒输出"hello,world" ,输出10次,退出,
// 一个线程每隔1秒输出"hi" ,输出5次退出
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
Thread thread1 = new Thread(t1);
thread1.start();
T2 t2 = new T2();
Thread thread2 = new Thread(t2);
thread2.start();
}
}
class T1 implements Runnable{
private int count = 0;
@Override
public void run() {
while(true){
//一个线程每隔1秒输出"hello,world" ,输出10次,退出
System.out.println("hello,world"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count==10){
break;
}
}
}
}
class T2 implements Runnable{
private int count = 0;
@Override
public void run() {
while(true){
//一个线程每隔1秒输出"hi" ,输出5次退出
System.out.println("hi"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count==5){
break;
}
}
}
}
6. 线程如何理解 585
7. 继承 Thread vs 实现 Runnable 的区别
1.从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
2.实现Runnable接口方式更加适合多个线程共享一 个资源的情况,并且避免了单继承的限制,建议使用Runnable
8. 多线程练习 586
[售票系统] ,编程模拟三个售票窗口售票100,分别使用继承Thread和实现Runnable方式,并分析有什么问题?
提示会出现超卖现象,等后面我们再把这个坑上
代码在com.stulzl.ticket.包中
SellTicket
package com.stulzl.ticket;
//多线程练习 586
// [售票系统] ,编程模拟三个售票窗口售票100张,分别使用继承Thread和实现Runnable方式,并分析有什么问题?
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// //这里会出现超卖现象,原因是比如 当我们线程1进去后还没来的及--ticketNum,线程2线程3也进来了
// //这样就会出现超卖现象
// sellTicket01.start();//启动售票,开启线程
// sellTicket02.start();
// sellTicket03.start();
//这样也会出现超卖现象,原因如上
System.out.println("===使用实现接口的方式===");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第一个线程窗口
new Thread(sellTicket02).start();//第二个线程窗口
new Thread(sellTicket02).start();//第三个线程窗口
}
}
//使用继承Thread类
class SellTicket01 extends Thread{
//让多个线程共享ticketNum,因为静态的会随着类的加载而创建的嘛,而且只会被创建一次,因为我们后三个线程
//都要多ticketNum进行--,但是呢又不能让ticketNum重置,所以用static
private static int ticketNum = 100;
@Override
public void run() {
while(true){
if(ticketNum<=0){
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+"售出一张票"+
"剩余票数="+(--ticketNum));
}
}
}
//实现接口Runnable接口
class SellTicket02 implements Runnable{
private int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while(true){
if(ticketNum<=0){
break;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+"售出一张票"+
"剩余票数="+(--ticketNum));
}
}
}
标签:Runnable,run,Thread,介绍,start,线程,使用,public
From: https://blog.51cto.com/u_15784725/6309683