介绍:进程是正在运行的程序;
单线程:一个进程有一个执行路径;
多线程:一个进程有多条执行路径;
一、实现多线程的方法:
①.继承Thread类;
a.定义类继承Thread类;
b.在类中重写run()方法;
c.创建该类的对象;
d.启动线程;
❤两个小问题:
A.为什么重写run()方法?
因为run()方法是用来封装被线程执行的代码
B.run()方法和start()方法的区别?
run():分装线程执行的代码,直接调用,相当于普通方法
start():启动线程,由JVM调用此线程的run()方法1.a.定义类继承Thread类、b.在类中重写run()方法;
public class duothread01 extends Thread{
@Override
public void run() {
for(int x=0;x<25;x++){
System.out.println(getName() + ":" + x);
}
}
}
2.c.创建该类的对象、d.启动线程;
duothread01 dt01 = new duothread01();
duothread01 dt02 = new duothread01();
//启动线程
dt01.start();
dt02.start();
②.实现runnable接口;
a.定义一个类实现runnable接口;
b.类中重写run方法;
c.创建对象;
d.创建Thread类的对象,把声明的类对象作为参数
e.启动线程;第一步、a.定义一个类实现runnable接口、b.类中重写run方法;
package runnable01;
public class runnable01 implements Runnable {
//重写方法
@Override
public void run() {
for(int i= 0;i<20;i++){
//这里因为与Thread没有直接关系,所以需要获取主线程再获取线程名称
System.out.println(Thread.currentThread().getName()+ "," + i);
}
}
}
第二步、c.创建对象、d.创建Thread类的对象,把声明的类对象作为参数、e.启动线程;
package runnable01;
public class runnableDemo {
public static void main(String[] args) {
//创建实现类对象
runnable01 rb = new runnable01();
//创建线程:创建Thread类的对象,把runnable实现类对象作为参数,设置名称,不设置有默认值;
Thread t1 = new Thread(rb,"火箭");
Thread t2 = new Thread(rb,"飞船");
//启动线程
t1.start();
t2.start();
}
}
❤相比继承Thread类,实现runnable接口实现多线程的好处:
- 避免了Java单继承的局限性;
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序代码、数据有效分离,较好的体现了面向对象设计思想;
二、设置和获取线程名称
setName():
getName():
- 调用setName方法,设置名称,
//调方法给线程赋值
dt01.setName("飞机");
dt02.setName("高铁");
- 在自己定义的Thread的子类中添加无参和带参构造方法,带参方法内部用super访问父类带参方法。
**************************Thread类中***************************
public duothread01() {
}
public duothread01(String name) {
super(name);
}
************************测试类中**************************
//通过带参构造方法
duothread01 dt03 = new duothread01("飞船");
duothread01 dt04 = new duothread01("火箭");
3.获取当前正在执行的线程的名称
Thread currentThread():返回对当前正在执行的线程对象的引用
String name = Thread.currentThread().getName();
System.out.println(name);
线程调度
线程有两种调度模型
- 分时调度模型:所有线程轮流获得CPU的使用权,平均分配每个线程占用CPU的时间片;
- 抢占调度模型(Java使用该模式):优先级高的线程先获得CPU使用权,如果优先级相同则随机选一个;
Thread类中设置和获取线程优先级的方法:
- getPriority():返回此线程的优先级;
- setPriority():修改此线程的优先级;
//获取线程优先级
System.out.println(dt1.getPriority());
System.out.println(dt2.getPriority());
System.out.println(dt3.getPriority());
System.out.println(dt4.getPriority());
//获取优先级参数的取值范围
System.out.println(Thread.MAX_PRIORITY);
System.out.println(Thread.MIN_PRIORITY);
//修改线程优先级
dt1.setPriority(1);
dt2.setPriority(10);
dt3.setPriority(9);
dt4.setPriority(2);
线程默认优先级是5,取值范围是1-10;
线程优先级高仅仅表示线程获取CPU时间片的几率高,该线程不一定会跑到最前面,可能在运行次数多的时候才能看到你想要的效果;
- 线程控制
Sleep(long millis):使当前正在执行的线程停留(暂停)指定的毫秒数;
案例:刘备孙权曹操争天下,势均力敌,不能让一个先跑完;
Join():等待这个线程死亡;如果有一个线程调用了此方法,那么其他线程必须得等到这个线程执行完才能执行;
案例:康熙、四阿哥、八阿哥抢皇位,需等康熙挂掉,两位阿哥开始抢;
setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机即将推出;
案例:刘备关羽张飞三结义,刘备为大哥,刘备挂了,关羽和张飞也会结束,可能不是立即,还会执行一点;
需设置刘备为主线程,关羽张飞为守护线程;
- 线程的生命周期
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csUnVIJWdWNjWp50MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1EDO1QzNwgTMwITMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
案例1:卖票
需求:某电影院目前正在上映国产大片,共有100张,电影院有三个窗口卖票,请设计一个程序模拟该电影院卖票;
思路:
- 定义一个类Move实现runnable接口,设置一个成员变量piao = 100;
- 重写run()方法实现卖票:
- 判断票数大于0,就买票,并告知是哪个窗口卖的
- 卖了票之后总数要减1
- 票没有了也会有人来问,所以用死循环一直卖
- 定义卖票测试类,
- 新建Thread对象
- 将Move对象作为参数传递给三个Thread的对象并给出窗口编号
- 启动线程
代码如下:
实现类:
package Moveticket_anli;
public class Move_ticket implements Runnable {
//定义变量,总票数赋值给它
private int ticket = 20;
//重写run()方法
@Override
public void run() {
//死循环实现一直卖票,即使卖完了也可以访问;
while (true) {
//判断票数大于0,就卖票
if (ticket > 0) {
//拼接:窗口名+第几张
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
//卖出后总数减1
ticket--;
}
}
}
}
测试类:
package Moveticket_anli;
public class movewin {
public static void main(String[] args) {
//新建卖票类的对象
Move_ticket mt = new Move_ticket();
//创建三个线程(窗口)同时卖票
Thread t1 = new Thread(mt,"wein01");
Thread t2 = new Thread(mt,"wein02");
Thread t3 = new Thread(mt,"wein03");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
案例思考1:
现实生活中,票卖出去,出片也需要时间,所以在每卖出一张票,需要一点时间的延迟,所以给卖票动作加一个100毫秒的延迟操作,用sleep()方法实现,实现接口类的if内部开头添加如下代码:
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
案例思考2:
修改代码后发现,同一张票会被出售多次,并且最后出现了第0和第-1张票;
出现此问题的原因是线程的随机性导致的,称为数据安全问题;
数据安全问题:如果符合下面全部三种情况,即可判定程序存在数据安全问题;
- 是否是多线程环境;
- 是否有共享数据;
- 是否有多条语句操作共享数据;
解决方案1:使用同步代码块sychronized(参数)
参数:在外部定义private Object obj = new Object();对象名作为参数;
数据安全问题的存在是因为同时满足了三个条件,那么要解决就得至少改变一条使其不满足,多线程和共享数据是无法避免的,所以只能是改变多条语句操作共享数据的方案,把多条语句操作共享数据的代码给锁起来,让任意时刻都只有一个线程能够执行这段代码;
Java提供了同步代码块的操作:
格式:
sychronized(任意对象){
多条语句操作共享数据的代码
}
sychronized(任意对象):任意对象就可以看作是一把锁;
最终代码如下:
package Moveticket_anli;
public class Move_ticket implements Runnable {
//定义变量,总票数赋值给它
private int ticket = 20;
private Object obj = new Object();
//重写run()方法
@Override
public void run() {
//死循环实现一直卖票,即使卖完了也可以访问;
while (true) {
//把多条语句操作共享数据的代码给锁起来
// 让任意时刻都只有一个线程能够执行这段代码
synchronized (obj) {
//判断票数大于0,就卖票
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拼接:窗口名+第几张
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
//卖出后总数减1
ticket--;
}
}
}
}
}
以下图示是教程中的代码执行流程注释信息,加深理解
使用同步代码块sychronized(参数)的好处与弊端
好处:解决了多线程的数据安全问题;
弊端:当线程很多时,因为每个线程都会去判断线程上的锁,非常耗费资源,无形中降低程序的运行效率;
解决方案2:同步方法
同步方法:将sychronized写到方法的修饰符后面;
Public sychronized void XXX(){};
锁对象是:this
同步静态方法:将sychronized写到方法的静态修饰符后面;
Public static sychronized void XXX(){};
锁对象是:类名.class
用同步方法改进后代码如下:
卖票方法:
package Moveticket_anli;
public class Move_ticket implements Runnable {
private int ticket = 20;
private Object obj = new Object();
@Override
public void run() {
//死循环实现一直卖票,即使卖完了也可以访问;
while (true) {
maipiao();
}
}
//把多条语句操作共享数据的代码给锁起来
// 让任意时刻都只有一个线程能够执行这段代码
private synchronized void maipiao() {
//判断票数大于0,就卖票
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//拼接:窗口名+第几张
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
//卖出后总数减1
ticket--;
}
}
}
卖票窗口:
package Moveticket_anli;
public class wicket {
public static void main(String[] args) {
//新建卖票类的对象
Move_ticket mt = new Move_ticket();
//创建三个线程(窗口)同时卖票
Thread t1 = new Thread(mt,"windows01");
Thread t2 = new Thread(mt,"windows02");
Thread t3 = new Thread(mt,"windows03");
//启动线程
t1.start();
t2.start();
t3.start();
}
}