1、多线程的第一种实现方式:继承方式
步骤:
1 定义一个类,继承Thread类
2 重写Thread类的run方法
3 创建对象
4 调用启动线程的方法(start方法)
【注意事项】
①当主线程放在自己创建的线程启动之前,就会先顺序执行for循环,直到主线程的for循环执行完毕,才会顺序执行启动mt那个线程;
②当主线程放在mt那个线程启动之后,两个线程就会抢占CPU资源,穿插执行
③mt.run();只是普通对象调用普通方法,并不会启动线程,这样会先将run()方法中的for循环执行完毕再向下执行主线程,也不会出现抢占cpu资源的情况
package com.cc.lianxi;
public class Demo001 {
public static void main(String[] args) {
/* // 主线程
for (int i = 0; i < 100; i++) {
System.out.println("**********main********" + i);
}*/
// 3.创建线程对象
MyThread mt = new MyThread();
// 4启动线程
mt.start();
//调用方法不是启动线程,这样会先把调用方法中的for循环执行完毕再向下执行
//mt.run();
// 主线程
for (int i = 0; i < 100; i++) {
System.out.println("**********main********" + i);
}
}
}
//1.自定义一个类继承Thread
class MyThread extends Thread {
// 2.重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("mythread---------" + i);
}
}
}
练习:模拟火车站窗口售票------>继承(V1.0.0)
【继承方式中,想要给线程设置名字有两种方式】
- 在自定义类中定义带有传入名字的构造方法,直接调用父类的构造方法
- 在自定义类中定义一个名字变量,把构造方法传入的名字通过this关键字直接赋值给当前对象
package com.cc.lianxi;
public class Demo02 {
public static void main(String[] args) {
//3.创建线程对象]
SaleWindow sw1 = new SaleWindow("窗口1");
SaleWindow sw2 = new SaleWindow("窗口2");
SaleWindow sw3 = new SaleWindow("窗口3");
//4.启动线程
sw1.start();
sw2.start();
sw3.start();
}
}
//1.自定义类继续Thread
class SaleWindow extends Thread{
//继承方式,想要多个线程共享同一个变量,必须定义为static类型
static int ticket = 100;
//第一种构造方法,直接调用父类的构造方法
public SaleWindow(String name) {
super(name);
}
/*//第二种构造方法:自定义一个变量,传入的参数赋值给当前对象
String name;
public SaleWindow(String name) {
this.name=name;
}*/
//2.重写run方法
@Override
public void run() {
while(true) {
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"号票");
ticket--;
}else {
break;
}
}
}
}
【运行结果】:可能会出现重复票,0票以及负数票
窗口1卖了第100号票
窗口3卖了第100号票
窗口2卖了第100号票
窗口3卖了第98号票
窗口1卖了第99号票
2、多线程的第二种实现方式:实现接口方式
步骤:
1、定义一个任务类,实现Runnable接口
2、重写任务类中的run方法,用于定义任务的内容
3、创建任务类对象,表示任务
4、创建一个Thread类型的对象,用于执行任务类对象
5、调用线程对象的start方法,开启新线程
【实现接口方式中,想要给线程设置名字】
直接在通过Thread创建任务类对象线程时设置名字
package com.cc.lianxi;
public class Demo02 {
public static void main(String[] args) {
//3创建任务对象
Task t = new Task();
//4.创建线程,并关联任务
Thread th1 = new Thread(t,"子线程1");
Thread th2 = new Thread(t,"子线程2");
Thread th3 = new Thread(t,"子线程3");
//5.启动线程
th1.start();
th2.start();
th3.start();
}
}
//1.自定义Runnable实现类
class Task implements Runnable{
//2.重写Runnable中的run()方法
@Override
public void run() {
for(int i=0;i<100;i++) {
//Thread.currentThread().getName()获取当前正在执行这段代码的线程的名字
System.out.println(Thread.currentThread().getName()+">>>"+i);
}
}
}
练习:模拟火车站窗口售票------>实现接口(1.0.1)
package com.cc.lianxi;
public class Test02 {
public static void main(String[] args) {
SaleWindows sw = new SaleWindows();
Thread t1 = new Thread(sw,"窗口1");
Thread t2 = new Thread(sw,"窗口2");
Thread t3 = new Thread(sw,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SaleWindows implements Runnable {
int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖了第" + (ticket--) + "号票");
} else {
break;
}
}
}
}
为什么继承和实现接口方式模拟火车站售票都可能有重复票、0票以及负数票?
①乱序情况
System.out.println(Thread.currentThread().getName()+"正在卖第" + (tickets--)+"票");
比如说,当线程一执行到"正在卖第"时,CPU被抢占了,那么整句话都不会打印出来.
但是需要打印输出的字符串已经存到一个字符串变量里面,
存储在缓存中,tickets也已经做了--的操作.等线程一获取到CPU,就会打印存储在缓存中的这个字符串!
所以会出现
***窗口二***正在卖第99票
-----窗口三-----正在卖第98票
窗口一正在卖第100票
***窗口二***正在卖第97票
-----窗口三-----正在卖第97票
这些乱序情况
所以需要同步代码块,上锁去解决这些问题
②重复票
线程一刚执行完打印输出语句时,ticket还没来得及减1,CPU就被线程二抢占了,线程二接着执行输出语句,这时的ticket就会和线程一重复
③0票(负数票)
线程1打印输出的票数是1(0),ticket进行减1等于0(-1),之后CPU被线程2抢占,线程二得到CPU直接已经进入循环体内,不对票数进行判断,直接向下执行打印输出
两种方式的比较
1代码复杂程度:
继承Thread方式简单
实现Runnable接口的方式比较复杂
2实现原理:
start方法,调用start0方法,start0是本地方法(native),由虚拟机实现,是C语言实现的方法,所以在java中看不到代码。本地方法start0返回来调用java中的run方法,run方法已经在子类中重写过了,所以最终运行的是子类重写了的run方法
一路init方法的传递,最终,用于给Thread类型中的某个成员变量(target)赋值;调用对象的start方法,最终也是返回来调用Thread类中的run方法,判断当前的成员变量target是否为null,如果不为null,就调用target的run方法,而这个run方法我们已经重写过了,最终运行的是我们重写过的run方法。
3设计:java中只支持单继承、不支持多继承
继承方式:某个类继承了Thread类,那么就无法继承其他业务中需要的类型,就限制了我们的设计。所以扩展性较差。
实现方式:某个类通过实现Runnable的方式完成了多线程的设计,仍然可以继承当前业务中的其他类型,扩展性较强。
4灵活性:
继承方式:将线程对象和任务内容绑定在了一起,耦合性较强、灵活性较差
实现方式:将线程对象和任务对象分离,耦合性就降低,灵活性增强:同一个任务可以被多个线程对象执行,某个线程对象也可以执行其他的任务对象。并且将来还可以将任务类对象,提交到线程池中运行;任务类对象可以被不同线程运行,方便进行线程之间的数据交互。
3、多线程的第三种实现方式:匿名内部类方式
package com.cc.lianxi;
public class Demo03 {
public static void main(String[] args) {
// 继承方式内部类
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("继承内部类====" + i);
}
}
}.start();
// 实现接口方法内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("实现接口内部类%%%%" + i);
}
}
}).start();
}
}