多线程之一

朝花夕拾:多线程

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

多线程的优点

以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

多线程程序的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

  2. 提高计算机系统CPU的利用率

  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

何时需要多线程

  • 程序需要同时执行两个或多个任务。

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

  • 需要一些后台运行的程序时。


多线程的创建

继承于Thread

/**
 * 1.创建一个继承于Thread的类
 * 2.重写run方法-->将此线程的操作写在这里
 * 3.创建thread类的子类对象
 * 4.通过该对象调用start方法
 */
public class ThreadTest{
    public static void main(String[] args){
        Demo01 demo01 = new Demo01();
        //启动当前线程,并调用run()方法
        demo01.start();
        //主线程
        for (int i = 1; i <= 100; i++) {
            if(i%2==0){
                System.out.println(i+"****");
            }
        }
    }
}
/**
 * 这个线程用来遍历1-100的偶数。
 */
class Demo01 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}

实现Runnable接口

  • 创建一个实现了Runnable接口的类
  • 实现类去实现Runnable中的抽象方法:run()
  • 创建实现类的对象
  • 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 通过Thread类的对象调用start()
public class ThreadTest1 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        Demo02 demo02 = new Demo02();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread thread = new Thread(demo02);
        //5.通过Thread类的对象调用start()
        thread.start();
        for (int i = 0; i <=100; i++) {
            if(i%2!=0){
                System.out.println(i+"hello");
            }
        }
    }
}
//1.创建一个实现了Runnable接口的类
class Demo02 implements Runnable{
    //2.实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {
            if(i%2==0){
                System.out.println(i);
            }
        }
    }
}

比较创建线程的两种方式。

开发中:优先选择:实现Runnable接口的方式

原因:

  • 实现的方式没有类的单继承性的局限性

  • 实现的方式更适合来处理多个线程有共享数据的情况。


继承方式和实现方式的联系与区别

public class Thread extends Object implements Runnable

  • 区别
    继承Thread:线程代码存放Thread子类run方法中。
    实现Runnable:线程代码存在接口的子类的run方法。
  • 实现方式的好处
    避免了单继承的局限性
    多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源。

Thread类的特性

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常 把run()方法的主体称为线程体

  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

Thread类

测试Thread中的常用方法:

  • start():启动当前线程;调用当前线程的run()
  • run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
  • currentThread():静态方法,返回执行当前代码的线程
  • getName():获取当前线程的名字
  • setName():设置当前线程的名字
  • yield():释放当前cpu的执行权
  • join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
  • stop():已过时。当执行此方法时,强制结束当前线程。
  • sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
  • isAlive():判断当前线程是否存活

线程的调度

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

线程优先级

  • MAX_PRIORITY:10

  • MIN _PRIORITY:1

  • NORM_PRIORITY:5 -->默认优先级

  • getPriority() :返回线程优先值

  • setPriority(int newPriority) :改变线程的优先级

设置线程优先级

Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

说明:

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

线程的生命周期

JDK中用Thread.State类定义了线程的几种状态

  • 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已
    具备了运行的条件,只是没分配到CPU资源
  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线 程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

image-20220401225329019


线程同步

  • 多个线程执行的不确定性引起执行结果的不稳定
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

image-20220401232141465

多线程出现了安全问题
问题的原因:

  • 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
  • 解决办法:
    对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。

同步代码块

synchronized(同步监视器){
       //需要被同步的代码
}
/*  说明:1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。
 *       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
 *       3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
 *      要求:多个线程必须要共用同一把锁。
 */

同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

在方法上使用关键字:synchronized


死锁问题

死锁

  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

Lock

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。

  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

//空参构造
private Lock lock = new ReentrantLock();
//有参构造
//公平锁
private Lock lock = new ReentrantLock(true);
//非公平锁
private Lock lock = new ReentrantLock(false);
public class ThreadTest2 {
    public static void main(String[] args) {
        Window01 window01 = new Window01();
        Thread t1 = new Thread(window01);
        Thread t2 = new Thread(window01);
        Thread t3 = new Thread(window01);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();

    }
}
class Window01 implements Runnable{
    private ReentrantLock lock = new ReentrantLock(true);
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            try {
                //上锁
                lock.lock();
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":票号为:"+ticket);
                    ticket--;
                }else {
                  break;
                }
            } finally {
                //解锁
                lock.unlock();
            }
        }
    }
}
posted @   Boerk  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示