五 异常与多线程——第二节 线程实现方式
多线程
1、 并发和并行
并发:两个或多个事件在同一事件段内发生(交替执行)
并行:两个或多个事件在同一时刻发生(同时发生)
image.png
2、进程概念:进入到内存中的程序
内存中的应用程序。每个进程都有一个独立的内存空间
image.png
3、线程,通往CPU 的执行路径
是进程的一个执行单元
image.png
4、线程调度:
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,java使用的是抢占式调度。
实际上,CPU使用抢占式调度模式在多个线程间进行着高速的切换,对于CPU的一个核而言,某个时刻,只能执行一个线程,而CPU的多个线程间切换速度相对于我们呢感觉要快,看上去在同一时刻执行。
5、 主线程
主线程:执行主方法的线程
单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下依次执行
JVM执行main方法,main 方法会进入栈内存,JVM找操作系统开辟一条main 方法通往cpu 的执行路径,cpu 就可以通过这个路径执行main 方法,这个路径就是main (主)线程
主方法
package con.day13.demo05.Thread;
public class Demo01MainThread {
public static void main(String[] args) {
Person person1 = new Person("jaon");
Person person2 = new Person("sunyy");
person1.run();
System.out.println(0/0);
//弊端:Exception in thread "main" java.lang.ArithmeticException: / by zero, 这时下面的代码不会执行
person2.run();
}
}
package con.day13.demo05.Thread;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
this.name = name;
}
public Person() {
}
public void run(){
//循环20次
for (int i = 0; i < 20; i++) {
System.out.println("运行次数" + name + i) ;
}
}
}
主线程
6、创建多线程程序的第一种方式_创建Thread
实现步骤:
- 1、创建Thread类的子类
- 2、在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程做什么)
- 3、创建一个Thread类的子类对象
- 4、调用Thread类中的start()方法,开启新的线程.执行run()方法
void start()会开启新的线程. java调用该线程的run 方法 ,结果是两个线程并发地执行,当前线程(main线程)和另一个线程(创建的新线程,执行run 方法)
java 程序是抢占式线程,哪个线程优先级高,哪个线程先执行,同一个优先级,随机选择一个执行
java.lang.Thread类代表线程。所有的线程对象都必须是Thread类或其子类的实例。
Thread类
package exception2.thread;
/**
* created by apple on 2020/6/22
* 创建一个Thread类的子类
*/
public class MyThread extends Thread{
// * 2、在Thread类的子类中重写Thread类中的run方法,设置线程任务。
@Override
public void run() {
for (int i = 0; i < 7; i++) {
System.out.println("run:" + i);
}
}
}
测试类
package exception2.thread;
/**
* created by apple on 2020/6/22
* 创建多线程程序的第一种方式。创建Thread类的子类。
* java.lang.Thread是描述线程的类,实现多线程程序,必须继承Thread类,
* 实现步骤:
* 1、创建Thread类的子类
* 2、在Thread类的子类中重写Thread类中的run方法,设置线程任务。
* 3、创建一个Thread类的子类对象
* 4、调用Thread类中的start()方法, 开启新的线程,执行run 方法
void start()会开启新的线程. java调用该线程的run 方法 ,结果是两个线程并发地执行,当前线程(main线程)和另一个线程(创建的新线程,执行run 方法)
*/
public class Demo07thread {
public static void main(String[] args) {
//创建一个Thread类的子类对象
MyThread mt = new MyThread();
// 调用Thread类中的start()方法,是个多线程,开辟一个新的栈空间,执行run方法
mt.start();
for (int i = 0; i < 6; i++) {
System.out.println("main:" + i);
}
}
}
执行结果:多线程,每次执行结果都不一样,随机性的执行结果,两个线程抢占式的执行
run:0
main:0
main:1
main:2
run:1
run:2
run:3
run:4
run:5
run:6
main:3
main:4
main:5
7 多线程原理_随机性打印结果(main 线程和start的线程)
多线程抢占式执行8、多线程原理_多线程内存图解:在多个栈空间,互不影响
多线程内存图解9、Thread类的常用方法_获取线程名称
两种方法:
1、 直接Thread的String getName()方法, 返回该线程的名称
2、获取当前正在执行的线程currentThread().然后再getName(),获取线程名称
static Thread currentThread()。 返回当前正执行的线程对象的引用
线程
package exception2.thread;
/**
* created by apple on 2020/6/22
*/
public class MyThread2 extends Thread{
//重写run 方法,设置线程任务
@Override
public void run() {
//获取线程名称
/*String name = getName();
System.out.println(name);*/
//获取当前正在执行的线程
Thread t = Thread.currentThread();
System.out.println(t);
//Thread[Thread-0,5,main] 获取的执行的线程
//Thread[Thread-1,5,main]
String name = t.getName();
System.out.println(name);
//Thread-0
//Thread-1
//链式变成
System.out.println("======");
System.out.println(Thread.currentThread().getName());
}
}
package exception2.thread;
/**
* created by apple on 2020/6/22
* 获取线程的名称
* 1、使用Thread类中的方法,String getName()
* 2、先获取当前正在执行的线程static Thread currentThread(),再使用线程中的方法,getName()获取线程的名称
线程的名称:
主线程: main
新线程 Thread-01
*/
public class Demo01GetThreadName {
public static void main(String[] args) {
//创建Thread类的子类对象
MyThread2 td2 = new MyThread2();
//调用start()开启新线程,执行run()
td2.start(); //Thread-0
MyThread2 td3 = new MyThread2();
//调用start()开启新线程,执行run()
td3.start(); //Thread-1
}
}
10、Thread类的常用方法_设置线程名称
setName
11、Thread类的常用方法_sleep
package exception2.thread;
/**
* created by apple on 2020/6/22
* sleep, 毫秒数结束后,程序继续执行
*/
public class demo02sleep {
public static void main(String[] args) throws InterruptedException {
//模拟秒表
for (int i = 0; i < 60; i++) {
System.out.print(i);
//使用Thread类的sleep,让程序睡眠一秒钟.静态方法,直接通过类名掉用
Thread.sleep(1000);
}
}
}
12、创建多线程程序的第二种方式_实现Runnable接口
- 实现步骤:
- 1、创建一个Runnable接口的实现类
- 2、在实现类中重写Runnable接口的run方法,设置线程任务
- 3、创建一个Runnable接口的实现类对象
- 4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 5、调用Thread类中的start(),开启新的线程 执行run()
package exception2.Runnable;
/**
* created by apple on 2020/6/22
*/
//创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
//在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
package exception2.Runnable;
/**
* created by apple on 2020/6/22
* 创建多线程程序的第二种方式,实现Runnable接口
* java.lang.Thread类的构造方法:
* Thread(Runnable target)分配新的Thread对象
* Thread(Runnable target,String name)分配新的Thread对象
实现步骤:
* 1、创建一个Runnable接口的实现类
* 2、在实现类中重写Runnable接口的run方法,设置线程任务
* 3、创建一个Runnable接口的实现类对象
* 4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
* 5、调用Thread类中的start(),开启新的线程 执行run()
*/
public class Demo01Runnable {
public static void main(String[] args) {
// 创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
//4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread td = new Thread(run);
//5、调用Thread类中的start(),开启新的线程 执行run()
td.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
//每次执行结果不一样,抢夺Cpu
}
}
13、Thread和Runnable的区别
尽量使用实现Runnable 接口的方式
- 实现Runnable接口创建多线程程序的好处:
1、 避免单线程继承的局限性,
一个类只能继承一个类,类继承了Thread类,不能继承其他类
但是实现Runnable,还可以继承其他类,实现其他接口
2、增强程序扩展性,降低程序耦合型,解耦。
实现Runnable接口的方式,把设置线程任务,开启新线程 进行分离,解耦
实现类中,重写了run():用来设置线程任务
创建Thread类对象,调用start(). 用来开启新的线程,
14、匿名内部类方式实现线程的创建
何谓匿名内部类?
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类的作用:简化代码
把子类继承父类, 重写父类方法,创建子类对象合成一步
把实现类实现接口,重写接口中的方法,创建实现类对象合成一步
匿名内部类最终产物:子类、实现类对象,而这个类没名字
格式:
new 父类/接口(){
重写父类/接口中方法
};
普通实现抽象方法(不使用匿名内部类)
普通实现抽象方法(不使用匿名内部类)
package AnonymousInner;
abstract class Father{
public abstract void speak();
}
class Son extends Father{
@Override
public void speak() {
System.out.println("熊孩子:粑粑,我想哦粑粑");
}
}
public class NIMingDemo {
public static void main(String[] args) {
Father f=new Son();
f.speak();
}
}
运行结果:
熊孩子:粑粑,我想哦粑粑
可以看到,我们用Son继承了Father类,然后实现了Son的一个实例,将其向上转型为Father类的引用。
但是,如果此处的Son类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?这个时候就应该想到匿名内部类了,匿名内部类就此诞生了!所以我们就可以使用匿名内部类来实现抽象方法,具体操作在下面一节详细讲解。
关于匿名内部类必须要知道的两点知识:
1、匿名内部类不能有构造器(构造方法),你想嘛,匿名内部类没有类名,咋定义构造器,构造方法的方法名是要与类名一致,但匿名内部类可以定义实例初始化块。
2、匿名内部类不可以是抽象类,刚说过了匿名内部类不能有构造器,而抽象类可以有构造方法,这是其一。java在创建匿名内部类的时候,会立即创建内部类的对象,而抽象类不能创建实例,这是其二。
匿名内部类实现:
package AnonymousInner;
abstract class Father{
public abstract void speak();
}
public class NIMingDemo {
public static void main(String[] args) {
Father f=new Father() {
@Override
public void speak() {
System.out.println("刘东强东墙东强");
}
};
}
}
OK,回到正题,我们把上面的那个程序的灵魂给抽出来,灵魂代码如下:
abstract class Father(){
....
}
public class NIMingDemo{
Father f = new Father(){ .... }; //{}里就是个匿名内部类
}
一般来说,new 一个对象时()后应该是分号;,也就是new出对象该语句就结束了。但是匿名内部类就不一样,()后跟的是{},{}中是该new 出对象的具体的实现方法,最后还需要在{}后面加个;代表结束。因为楼主在前面也说过,一个抽象类是不能直接new 的,必须先有实现类了我们才能new出它的实现类。上面的灵魂代码就是表示new 的是Father的实现类,只不过这个实现类是个匿名内部类的形式而已。
到这里我们已经讲了继承这一方面的例子了,这个时候肯定会有小白同学说刚才不是一种再讲抽象和匿名内部类咩?好像没有讲到继承,呃呃呃,小白同学啊抽象类也是一个类,我已经是用Son子类继承了Father父类了,只是这个Father父类就是一个抽象类鸭!!
package exception2;
/**
* created by apple on 2020/6/22
*/
public class InnerClassThread {
public static void main(String[] args) {
//1、创建Thread对象
//new MyThread().start()
new Thread(){
//重写run方法。设置线程任务
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName() + "-heima-" );
}
}
}.start();
//2、实现Runnable接口的方式
//RunnableIm ip = new RunnableIm();
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName() + "-Runnnable-" );
}
}
};
new Thread(r).start();
//3、简化接口的方式,。。r用上面的new 出的对象代替
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName() + "-jianhua-" );
}
}
}).start();
}
}