多线程的实现方式

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)

【继承方式中,想要给线程设置名字有两种方式】

  1. 在自定义类中定义带有传入名字的构造方法,直接调用父类的构造方法
  2. 在自定义类中定义一个名字变量,把构造方法传入的名字通过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方法

  实现方式:构造方法中,将Runnable的实现类对象传入构造方法中,经过一路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();
	}

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值