多线程
线程
进程中的最小执行单位就是线程,并且一个进程中至少有一个线程,进程中的实际运作单位。
进程
进程是程序的基本执行体
并发
在同一时刻 , 有多个指令在单个CPU上交替执行
并行
在同一时刻 , 有多个指令在多个CPU上同时执行
实现线程的两种方式
继承Thread类
-
自己定义一个类继承Thread
-
重写run方法
-
创建子类的对象,并启动线程
public class ThreadDemo { public static void main(String[] args) { //建子类的对象 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); //设置线程名 t1.setName("线程1"); t2.setName("线程2"); //启动线程 t1.start(); t2.start(); } } public class MyThread extends Thread{ @Override public void run() { //线程中要实现的代码 for (int i = 0; i < 100; i++) { System.out.println(getName() + ":" + "hello hngy"); } } }
实现Runnable接口
- 自己定义一个类实现Runnable接口
- 重写里面的run方法
- 创建自己的类的对象
- 创建一个Thread类的对象,并开启线程
public class ThreadDemo {
public static void main(String[] args) {
//创建MyRun对象
//表示多线程要执行的任务
MyRun r = new MyRun();
//创建线程对象
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
//设置线程名
t1.setName("线程1");
t2.setName("线程2");
//启动线程
t1.start();
t2.start();
}
}
public class MyRun implements Runnable{
@Override
public void run() {
//线程中要实现的代码
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "hello hngy");
}
}
}
线程的生命周期
新建
创建线程对象
就绪
有执行资格,没有执行权
运行
有执行资格,有执行权
阻塞
没有执行资格,没有执行权
死亡
线程死亡,变成垃圾
操作线程的方法
方法名称 | 说明 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字 (构造方法也可以设置名字) |
staticc Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
线程的优先级
抢占式调度(JAVA中的机制)
抢占式 调度是CPU调度的一种类型,其中资源(CPU周期)被分配给一个进程的时间是有限的。在这种类型的调度中,一个进程在被执行时可以被打断。
主要体现出随机性,优先级越大越有可能抢到CPU
非抢占式调度
非抢占 式调度是指一旦资源(CPU周期)被分配给一个进程,该进程就会保持它,直到它完成它的突发时间或切换到 “等待 “状态。
一个接着一个
线程的优先级是指线程在竞争CPU资源时被调度的优先级。线程的优先级用整数表示,通常范围在1到10之间,其中1是最低优先级,10是最高优先级。线程的优先级可以影响线程被调度的顺序,但并不是绝对的,具体调度取决于操作系统的实现。
注意:
- 不同操作系统对线程优先级的支持可能有所差异,不能完全依赖线程优先级来控制线程的调度。
- 过度依赖线程优先级可能导致线程饥饿或优先级反转等问题,影响程序的性能和稳定性。
- 在实际应用中,应该避免过多地依赖线程优先级,而是通过合适的同步机制和线程调度算法来确保线程的正确执行顺序。
守护线程
-
守护线程是用来为用户线程服务的,当一个程序中的所有用户线程都结束之后,无论守护线程是否在工作都会跟随用户线程一起结束;
-
守护线程的子线程也是守护线程;
-
守护线程的优先级和用户线程优先级一致;
-
守护线程setDaemon(true) 如果设置在 start() 之后,程序执行会报错,守护线程也不会生效。
出让线程
出让线程是指在多线程编程中,一个线程主动让出自己的执行权给其他线程,让其他线程有机会执行。这样可以提高系统的并发性能,避免某个线程长时间占用CPU资源导致其他线程无法执行的情况。
出让线程的方式通常有两种:一种是通过调用线程调度器提供的yield()方法来让出执行权;另一种是通过调用sleep()方法来让当前线程暂停一段时间,让其他线程有机会执行。
插入线程
在多线程编程中,通常情况下,我们会在程序开始时创建线程并让它们开始执行任务。但有时候,我们可能需要在程序运行过程中动态地创建并启动新的线程。
插入线程是指在程序的执行过程中,动态地创建一个新的线程并将其插入到已有的线程执行流中。这种方式可以用于在程序运行时动态地增加并发性,灵活地处理任务和提高系统的并发性能。
线程安全
问题 :现在需要售卖火车票 , 一共有三个窗口 ,三个窗口同时售卖100张票,请使用多线程实现这个问题。
public class ThreadDemo {
public static void main(String[] args) {
//建子类的对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
//设置线程名
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
public class MyThread extends Thread{
//共享的数据
public static int ticket = 100;
@Override
public void run() {
while (ticket > 0){
try {
sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket--;
System.out.println(getName() + "窗口正在卖第: " + ticket + "张票");
}
}
}
以上程序的运行结果会出现一下问题
- 三条语句的ticket都相同
- 票卖超过规定数量
这种延迟卖票的问题被称为问题,要发生线程安全问题需要满足三个条件(任何一共条件不满足都不会造成线程安全问题):
- 是否存在多线程环境
- 是否存在共享数据/共享变量
- 是否有多条语句操作着共享数据/共享变量
解决方案
synchronized —— 自动锁
使用synchronized的条件:
-
必须有两个或两个以上的线程
-
同一时间只有一个线程能够执行同步代码块
-
多个线程想要同步时,必须共用同一把锁
-
synchronized(对象)括号里面的对象就是一把锁
使用synchronized的过程:
- 只有抢到锁的线程才可以执行同步代码块,其余的线程即使抢到了CPU执行权,也只能等待,等待锁的释放。
- 代码执行完毕或者程序抛出异常都会释放锁,然后还未执行同步代码块的线程争抢锁,谁抢到谁就能运行同步代码块。
public class MyThread extends Thread{
public static int ticket = 100;
@Override
public void run() {
synchronized(MyThread.class){
while (ticket > 0){
try {
sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + "窗口正在卖第: " + ticket-- + "张票");
}
}
}
}