Java 多线程编程与单例模式

1. 线程的创建方式

在 Java 中,创建线程的方式主要有两种:继承 Thread实现 Runnable 接口

1.1 继承 Thread

通过继承 Thread 类并重写 run() 方法来创建线程。run() 方法是线程执行的逻辑体。

java 复制代码
public class MyThread extends Thread {

    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name + "下载了" + i + "%");
        }
    }
}

测试代码:

java 复制代码
public class ThreadTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread("肖申克的救赎");
        mt.start();

        MyThread mt1 = new MyThread("当幸福来敲门");
        mt1.start();
    }
}
1.2 实现 Runnable 接口

通过实现 Runnable 接口并实现 run() 方法来创建线程。这种方式更灵活,因为 Java 不支持多继承,但可以实现多个接口。

java 复制代码
public class DownLoad implements Runnable {

    private String name;

    public DownLoad(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name + "下载了" + i + "%");
        }
    }
}

测试代码:

java 复制代码
public class ThreadTest {
    public static void main(String[] args) {
        Thread t = new Thread(new DownLoad("肖申克的救赎"));
        Thread t1 = new Thread(new DownLoad("当幸福来敲门"));
        t.start();
        t1.start();
    }
}

1.3实现 Callable 接口

java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 使用 Callable 创建线程
 */
public class DownloadCallable implements Callable<String> {

    private String name; // 电影名称

    public DownloadCallable(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        for (int i = 1; i <= 100; i++) {
            System.out.println(name + " 下载进度:" + i + "%");
            Thread.sleep(50); // 模拟下载耗时
        }
        return name + " 下载完成!";
    }
}

/**
 * 测试代码
 */
public class ThreadTest {
    public static void main(String[] args) {
        // 创建 Callable 任务
        DownloadCallable task1 = new DownloadCallable("肖申克的救赎");
        DownloadCallable task2 = new DownloadCallable("当幸福来敲门");

        // 使用 FutureTask 包装 Callable 任务
        FutureTask<String> futureTask1 = new FutureTask<>(task1);
        FutureTask<String> futureTask2 = new FutureTask<>(task2);

        // 创建线程并启动
        Thread thread1 = new Thread(futureTask1);
        Thread thread2 = new Thread(futureTask2);
        thread1.start();
        thread2.start();

        // 获取线程执行结果
        try {
            String result1 = futureTask1.get(); // 阻塞,直到线程1执行完毕
            String result2 = futureTask2.get(); // 阻塞,直到线程2执行完毕
            System.out.println(result1);
            System.out.println(result2);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}
2. 线程的执行原理

线程的并发执行是通过多个线程不断切换 CPU 资源来实现的。由于切换速度非常快,我们感知到的是多个线程在并发执行。


3. 线程的生命周期

线程的生命周期包括以下几个状态:

  1. 新建(New):线程被创建,但尚未启动。

  2. 准备就绪(Runnable) :线程调用了 start() 方法,具备执行的资格,但尚未获得 CPU 资源。

  3. 运行(Running):线程获得 CPU 资源,正在执行。

  4. 阻塞(Blocked):线程因某些原因(如等待锁、I/O 操作)暂时停止执行。

  5. 销毁(Terminated):线程执行完毕或被强制终止。


4. 并发与同步

在多线程编程中,共享资源可能会导致数据不一致的问题。为了解决这个问题,可以使用 synchronized 关键字来实现同步。

1.同步代码块

java 复制代码
public class SaleTicketThread extends Thread {

    private String name;
    static int tickets = 100; // 共享资源
    static Object obj = new Object(); // 锁对象

    public SaleTicketThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    System.out.println(name + "卖出座位是" + (tickets--) + "号");
                } else {
                    break;
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name + "卖票结束");
    }
}

测试代码:

java 复制代码
public class ThreadTest {
    public static void main(String[] args) {
        SaleTicketThread t1 = new SaleTicketThread("窗口1");
        SaleTicketThread t2 = new SaleTicketThread("窗口2");
        SaleTicketThread t3 = new SaleTicketThread("窗口3");
        SaleTicketThread t4 = new SaleTicketThread("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

2.同步方法对象上

java 复制代码
public class SaleTicket implements Runnable {
    /**
     * 多个线程共享的100张票
     */
    int tickets = 100;

    //创建一个锁对象,这个对象是多个线程对象共享的数据
    Object obj = new Object();

    @Override
    public void run() {
        //卖票是持续的
        while (true){
            if(saleTickets()){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println(Thread.currentThread().getName()+"卖票结束");
    }


    /*public boolean saleTickets(){
        synchronized (obj){
            boolean isFinish = false;
            if(tickets > 0){
                System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
            }else{
                isFinish = true;
            }
            return isFinish;
        }
    }*/

    /**
     *
     * @return     如果一个对象方法上有synchronized的话那么锁的对象就是this
     */
    public synchronized boolean saleTickets(){
        //synchronized (obj){
        boolean isFinish = false;
        if(tickets > 0){
            System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
        }else{
            isFinish = true;
        }
        return isFinish;
        //}
    }
}

3.同步类方法上,那么锁对象就是类的类对象

java 复制代码
public class SaleTicketThread extends Thread {

    private String name;

    /**
     * 定义共享的数据100张票
     */
    static int tickets = 100;

    //创建一个锁对象,这个对象是多个线程对象共享的数据
    static Object obj = new Object();


    public SaleTicketThread(String name) {
        super(name);
        this.name = name;


    }

    @Override
    public void run() {

        //卖票是持续的
        while (true){
            if(saleTickets()){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println(name+"卖票结束");
    }


    public static synchronized boolean saleTickets(){

        boolean isFinish = false;
        if(tickets > 0){
            System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");
        }else{
            isFinish = true;
        }
        return isFinish;

    }
}


//测试代码:
public class ThreadTest {

    public static void main(String[] args) {
        SaleTicketThread t1 = new SaleTicketThread("窗口1");
        SaleTicketThread t2 = new SaleTicketThread("窗口2");
        SaleTicketThread t3 = new SaleTicketThread("窗口3");
        SaleTicketThread t4 = new SaleTicketThread("窗口4");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
5. 单例模式

单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。

单例模式的好处:

  1. 确保系统中只有一个实例,避免重复创建对象。

  2. 提供对唯一实例的受控访问。

  3. 节约系统资源,提高性能。

6 休眠

复制代码
在做服务器端的程序的时候都需要给一个休眠的时间,在没有synchronized代码块里面会让出cpu的资源。

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| public static void main(String[] args) { while (true ){ System.out .println(new Date()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } |

休眠在同步代码块内不会让出cpu

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| synchronized (ojb ){ try { // 我们休眠如果在 synchronized 内部就不会让出 cpu 的资源 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } |

7.使用继承Thread,实现Runnable,实现Callable接口3种方法创建线程的区别

继承性

  • 继承 Thread 类:Java 是单继承语言,一个类继承了 Thread 类就无法再继承其他类,限制了类的扩展性。
  • 实现 Runnable 接口:实现 Runnable 接口的类还可以继承其他类,更符合面向对象中的 "聚合" 思想,能让多个线程共享一个 Runnable 实例,资源共享性更好。
  • 实现 Callable 接口:同样实现 Callable 接口的类也可以继承其他类,在实现多任务处理的同时保持了类的继承灵活性。

任务执行结果返回

  • 继承 Thread 类和实现 Runnable 接口:这两种方式中的 run () 方法没有返回值,无法直接获取线程执行后的结果,如果需要获取结果,需通过共享变量等间接方式实现。
  • 实现 Callable 接口:其 call () 方法允许有返回值,能更方便地获取线程执行的结果,可用于需要获取线程计算结果并进行后续处理的场景。

异常处理

  • 继承 Thread 类和实现 Runnable 接口:在 run () 方法中只能通过 try-catch 捕获处理内部异常,无法向外抛出。
  • 实现 Callable 接口:call () 方法可以声明抛出异常,由调用者进行处理,让异常处理更灵活、更可控。

启动方式

  • 继承 Thread 类:直接创建子类实例后调用 start () 方法启动线程。
  • 实现 Runnable 接口和 Callable 接口:需要将实现类的实例作为参数传入 Thread 类的构造函数,再调用 start () 方法启动线程。
相关推荐
侠客行03177 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪7 小时前
深入浅出LangChain4J
java·langchain·llm
灰子学技术9 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
老毛肚9 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎9 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
二十雨辰9 小时前
[python]-AI大模型
开发语言·人工智能·python
Yvonne爱编码9 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚9 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言
你这个代码我看不懂10 小时前
@ConditionalOnProperty不直接使用松绑定规则
java·开发语言
pas13610 小时前
41-parse的实现原理&有限状态机
开发语言·前端·javascript