首页 > 编程语言 >Java 多线程

Java 多线程

时间:2024-07-21 16:25:58浏览次数:10  
标签:Java 张票 MyThread 线程 窗口 多线程 一在 售卖

文章目录

一、概念

在 Java 中,多线程是指在一个程序中同时运行多个执行线程,例如,在一个服务器程序中,可以使用多线程同时处理多个客户端的请求。


二、实现方式

多线程的实现方式主要有两种。

  • 继承 Thread
  • 实现 Runnable 接口

2.1 Thread 类

public class MyThread extends Thread {

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }


    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread("线程一");
        MyThread t2 = new MyThread("线程二");

        // 开启线程一
        t1.start();
        // 开启线程二
        t2.start();
    }
}

2.2 Runnable 接口

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable(), "线程一");
        Thread t2 = new Thread(new MyRunnable(), "线程二");

        // 开启线程一
        t1.start();
        // 开启线程二
        t2.start();
    }
}

三、常用方法

3.1 基本方法

方法名含义
public final String getName()获取当前线程的名称
void setName(String name)设置线程的名称
static Thread currentThread()获取当前线程的对象
static native void sleep(long millis)让当前线程休眠,时间单位是毫秒
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            // 获取线程名称
            System.out.println(getName() + ":" + i);

            // 获取线程名称
            // System.out.println(Thread.currentThread().getName() + ":" + i);

            // 休眠一秒在打印
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        // 设置线程名字
        t1.setName("线程一");
        t2.setName("线程二");

        // 开启线程一
        t1.start();
        // 开启线程二
        t2.start();
    }
}

运行结果:

线程二:1
线程一:1
线程二:2
线程一:2
线程二:3
线程一:3
线程二:4
线程一:4
线程二:5
线程一:5

3.2 线程优先级

在 Java 中,线程的抢夺方式是 抢占式调度 的,这就意味着线程执行的频率是随机,那如果想要某一线程的代码执行频率相较于其他线程要高的话,就可以设置线程的优先级,另外,每个线程如果不设置优先级的话,默认等级是5,最高是10,最低是1。

方法名含义
public final void setPriority(int newPriority)设置线程的优先级
public final int getPriority()获取当前线程的优先级
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            // 获取线程名称
            System.out.println(getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        System.out.println("===== 默认线程的优先级 ======");
        // 获取线程的优先级
        System.out.println(t1.getName() + ":" + t1.getPriority());
        System.out.println(t2.getName() + ":" + t2.getPriority());


        // 设置线程名字
        t1.setName("线程一");
        t2.setName("线程二");

        // 设置线程的优先级
        t1.setPriority(1);
        t2.setPriority(10);

        // 开启线程一
        t1.start();
        // 开启线程二
        t2.start();
    }
}

运行结果:

===== 默认线程的优先级 ======
Thread-0:5
Thread-1:5
线程二:1
线程二:2
线程一:1
线程二:3
线程一:2
线程二:4
线程一:3
线程二:5
线程一:4
线程一:5

注:你会发现 线程二线程一 先执行完的几率要高一些,因为线程二的的优先级较高。

3.3 守护线程

守护线程的应用场景是当其他线程执行完毕后,有的线程就没有必要存在了,这时候就是将这些进程设置为守护线程,简单理解,守护线程,守护的就是其他线程,当其他线程都执行完,那么自己就没必要存在了。

方法名含义
public final void setDaemon(boolean on)设置线程为守护线程

创建线程类,在后面设置为守护线程

// 守护线程
public class DaemonThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

创建普通线程类,并测试守护线程

public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            // 获取线程名称
            System.out.println(getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        // 创建普通线程
        MyThread Thread1 = new MyThread();
        // 创建普通线程
        DaemonThread daemonThread = new DaemonThread();

        // 设置线程为守护线程
        daemonThread.setDaemon(true);


        // 设置线程名字
        Thread1.setName("线程");
        daemonThread.setName("守护线程");

        // 开启线程一
        Thread1.start();
        // 开启线程二
        daemonThread.start();
    }
}

运行结果:

守护线程:1
线程:3
守护线程:2
线程:4
守护线程:3
守护线程:4
线程:5
守护线程:5
守护线程:6
守护线程:7
守护线程:8
守护线程:9
守护线程:10
守护线程:11
守护线程:12
守护线程:13
守护线程:14
守护线程:15
守护线程:16
守护线程:17

注:守护线程不是立刻就会停止,而是逐渐停止。

3.4 礼让线程

看前面的多线程代码中的运行结果,会发现 线程一线程二 是交替执行的,也有 线程 一次执行多次代码的情况。而礼让线程就是让 线程 执行完后,重新去抢夺CPU执行权,可以让线程之间的执行频率对齐,但这不是一定的,有可能会出现当前 线程 再次抢到了执行权的情况。

方法名含义
public static native void yield()设置线程为礼让线程

创建礼让线程类

public class YieldThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            // 获取线程名称
            System.out.println(getName() + ":" + i);

            // 设置为礼让线程
            Thread.yield();
        }
    }
}

创建普通线程类,并测试礼让线程

public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            // 获取线程名称
            System.out.println(getName() + ":" + i);
        }
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        YieldThread t2 = new YieldThread();

        // 设置线程名字
        t1.setName("线程一");
        t2.setName("礼让线程");

        // 开启线程一
        t1.start();
        // 开启线程二
        t2.start();
    }
}

运行结果:

线程一:1
礼让线程:1
线程一:2
礼让线程:2
线程一:3
线程一:4
线程一:5
礼让线程:3
礼让线程:4
礼让线程:5

3.5 插队线程

插队线程的作用就是,让插入线程先于其他线程先执行完代码。

public class JoinThread extends Thread{

    @Override
    public void run() {
        try {
            // 让当前线程休眠3秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(getName() + "线程执行完毕");
    }

    public static void main(String[] args) throws InterruptedException {

        // 创建对象
        JoinThread t1 = new JoinThread();

        // 设置线程名称
        t1.setName("线程一");

        // 开启线程一
        t1.start();

        // 设置t1线程为插队线程,会优先于其他线程先执行完毕
        t1.join();

        System.out.println("当线程一执行完毕后,Main线程才会去执行");
    }
}

运行结果:

线程一线程执行完毕
当线程一执行完毕后,Main线程才会去执行

四、线程安全问题

在多线程编程中,需要注意线程安全问题。当多个线程同时访问和修改共享资源时,可能会导致数据不一致等问题。可以使用同步机制,如 synchronized 关键字、 等来解决线程安全问题。

需求:某电影院目前正在上映电影,电影票共有50张,分别在3个窗口售卖,请你用程序模拟这个场景。

public class MyThread extends Thread {
    // 电影票
    static int ticket = 0;

    // 具体逻辑
    @Override
    public void run() {
        while (true) {
            // 票没卖完
            if (ticket < 50) {
                ticket++;
                System.out.println(getName() + "在售卖第" + ticket + "张票");
            } else {
                // 票卖完了
                break;
            }

        }
    }

    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 设置线程名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

窗口一在售卖第1张票
窗口二在售卖第2张票
窗口三在售卖第3张票
窗口二在售卖第5张票
窗口一在售卖第4张票
窗口二在售卖第7张票
窗口三在售卖第6张票
窗口二在售卖第9张票
窗口一在售卖第8张票
窗口二在售卖第11张票
窗口三在售卖第10张票
窗口二在售卖第13张票
窗口一在售卖第12张票
窗口二在售卖第15张票
窗口三在售卖第14张票
窗口二在售卖第17张票
窗口一在售卖第16张票
窗口二在售卖第19张票
窗口三在售卖第18张票
窗口二在售卖第21张票
窗口一在售卖第20张票
窗口二在售卖第23张票
窗口三在售卖第22张票
窗口二在售卖第25张票
窗口一在售卖第24张票
窗口二在售卖第27张票
窗口三在售卖第26张票
窗口一在售卖第28张票
窗口二在售卖第29张票
窗口三在售卖第30张票
窗口二在售卖第32张票
窗口一在售卖第31张票
窗口二在售卖第34张票
窗口三在售卖第33张票
窗口二在售卖第36张票
窗口一在售卖第35张票
窗口二在售卖第38张票
窗口一在售卖第39张票
窗口三在售卖第37张票
窗口一在售卖第41张票
窗口二在售卖第40张票
窗口一在售卖第43张票
窗口三在售卖第42张票
窗口一在售卖第45张票
窗口二在售卖第44张票
窗口一在售卖第47张票
窗口三在售卖第46张票
窗口一在售卖第49张票
窗口二在售卖第48张票
窗口三在售卖第50张票

注意点:

  • 你会发现电影票会出现票的顺序不一致、少票、重复票、多票的情况,这些都是因为线程在执行的时候是随机的,打比方,如上述代码,窗口一 在执行完电影票++操作的时候,还没打印,窗口二窗口三就抢到了执行权,分别对车票进行++操作,然后所有窗口一起打印现在是多少张票,这就会造成重复票的安全问题,其他也是一样的逻辑。

4.1 同步代码块

上述问题,主要就是 窗口一 线程在执行方式时,窗口二 线程和 窗口三 线程也进来了,那么有没有一种方法可以让 窗口一 线程在执行方式时,其他线程不能进来,同步代码块就是来解决这个问题的,被同步代码块锁起来的代码,只用里面的代码全部执行完毕,锁才会释放,其他线程才能进来。

public class MyThread extends Thread {
    // 电影票
    static int ticket = 0;

    // 具体逻辑
    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                // 票没卖完
                if (ticket < 50) {
                    ticket++;
                    System.out.println(getName() + "在售卖第" + ticket + "张票");
                } else {
                    // 票卖完了
                    break;
                }
            }

        }
    }

    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 设置线程名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

窗口一在售卖第1张票
窗口一在售卖第2张票
窗口一在售卖第3张票
窗口一在售卖第4张票
窗口一在售卖第5张票
窗口一在售卖第6张票
窗口一在售卖第7张票
窗口一在售卖第8张票
窗口一在售卖第9张票
窗口一在售卖第10张票
窗口一在售卖第11张票
窗口一在售卖第12张票
窗口一在售卖第13张票
窗口一在售卖第14张票
窗口一在售卖第15张票
窗口一在售卖第16张票
窗口一在售卖第17张票
窗口一在售卖第18张票
窗口一在售卖第19张票
窗口一在售卖第20张票
窗口一在售卖第21张票
窗口一在售卖第22张票
窗口一在售卖第23张票
窗口一在售卖第24张票
窗口一在售卖第25张票
窗口一在售卖第26张票
窗口一在售卖第27张票
窗口一在售卖第28张票
窗口一在售卖第29张票
窗口一在售卖第30张票
窗口一在售卖第31张票
窗口一在售卖第32张票
窗口一在售卖第33张票
窗口一在售卖第34张票
窗口一在售卖第35张票
窗口一在售卖第36张票
窗口一在售卖第37张票
窗口一在售卖第38张票
窗口一在售卖第39张票
窗口一在售卖第40张票
窗口一在售卖第41张票
窗口一在售卖第42张票
窗口一在售卖第43张票
窗口一在售卖第44张票
窗口一在售卖第45张票
窗口一在售卖第46张票
窗口一在售卖第47张票
窗口一在售卖第48张票
窗口一在售卖第49张票
窗口一在售卖第50张票

注意点:

  • 第一synchronized() 它小括号传入的是锁对象,且必须是唯一的,不然还是会发生安全问题,synchronized() 就没意义了。

    • 可以传入this,例如 synchronized(this)
    • 可以创建自定义对象,专门表示唯一的锁对象,例如:Object lock = new Object();,然后 synchronized(lock)
    • 可以类的字节码对象,synchronized(类名.class)
  • 第二:注意 synchronized() 书写的位置,如果放在 while 循环的上面,那性质就变了。

4.2 同步方法

同步方法是用来保证方法内代码的安全问题的,锁对象不需要我们指定,如果方法是静态的它是用当前类的 字节码对象 当锁对象,如果是非静态的使用 this 来当锁对象的。

public class MyThread extends Thread {
    // 电影票
    static int ticket = 0;

    // 具体逻辑
    @Override
    public void run() {
        while (true) {
            if (extracted()) break;
        }
    }

    // 同步方法
    private static synchronized boolean extracted() {
        // 票没卖完
        if (ticket < 50) {
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在售卖第" + ticket + "张票");
        } else {
            // 票卖完了
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 设置线程名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

窗口一在售卖第1张票
窗口一在售卖第2张票
窗口一在售卖第3张票
窗口一在售卖第4张票
窗口一在售卖第5张票
窗口一在售卖第6张票
窗口一在售卖第7张票
窗口一在售卖第8张票
窗口一在售卖第9张票
窗口一在售卖第10张票
窗口一在售卖第11张票
窗口一在售卖第12张票
窗口一在售卖第13张票
窗口一在售卖第14张票
窗口一在售卖第15张票
窗口一在售卖第16张票
窗口一在售卖第17张票
窗口一在售卖第18张票
窗口一在售卖第19张票
窗口一在售卖第20张票
窗口一在售卖第21张票
窗口一在售卖第22张票
窗口一在售卖第23张票
窗口一在售卖第24张票
窗口一在售卖第25张票
窗口一在售卖第26张票
窗口一在售卖第27张票
窗口一在售卖第28张票
窗口一在售卖第29张票
窗口一在售卖第30张票
窗口一在售卖第31张票
窗口一在售卖第32张票
窗口一在售卖第33张票
窗口一在售卖第34张票
窗口一在售卖第35张票
窗口一在售卖第36张票
窗口一在售卖第37张票
窗口一在售卖第38张票
窗口一在售卖第39张票
窗口一在售卖第40张票
窗口一在售卖第41张票
窗口一在售卖第42张票
窗口一在售卖第43张票
窗口一在售卖第44张票
窗口一在售卖第45张票
窗口一在售卖第46张票
窗口一在售卖第47张票
窗口一在售卖第48张票
窗口一在售卖第49张票
窗口一在售卖第50张票

4.3 lock()

方法名含义
void lock()上锁
void unlock()解锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread extends Thread {
    // 电影票
    static int ticket = 0;

    // 创建锁对象
    static Lock lock = new ReentrantLock();

    // 具体逻辑
    @Override
    public void run() {
        while (true) {
            // 上锁
            lock.lock();
            try {
                // 票卖完了
                if (ticket >= 50) {
                    break;
                } else {
                    ticket++;
                    System.out.println(getName() + "在卖第" + ticket + "张票!!!");
                }
            } finally {
                // 解锁
                lock.unlock();
            }
            
        }
    }

    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 设置线程名
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

窗口二在卖第1张票!!!
窗口二在卖第2张票!!!
窗口二在卖第3张票!!!
窗口二在卖第4张票!!!
窗口二在卖第5张票!!!
窗口二在卖第6张票!!!
窗口二在卖第7张票!!!
窗口二在卖第8张票!!!
窗口二在卖第9张票!!!
窗口二在卖第10张票!!!
窗口二在卖第11张票!!!
窗口一在卖第12张票!!!
窗口一在卖第13张票!!!
窗口一在卖第14张票!!!
窗口一在卖第15张票!!!
窗口一在卖第16张票!!!
窗口一在卖第17张票!!!
窗口一在卖第18张票!!!
窗口三在卖第19张票!!!
窗口三在卖第20张票!!!
窗口三在卖第21张票!!!
窗口三在卖第22张票!!!
窗口三在卖第23张票!!!
窗口三在卖第24张票!!!
窗口三在卖第25张票!!!
窗口三在卖第26张票!!!
窗口二在卖第27张票!!!
窗口二在卖第28张票!!!
窗口二在卖第29张票!!!
窗口二在卖第30张票!!!
窗口二在卖第31张票!!!
窗口二在卖第32张票!!!
窗口二在卖第33张票!!!
窗口二在卖第34张票!!!
窗口二在卖第35张票!!!
窗口一在卖第36张票!!!
窗口一在卖第37张票!!!
窗口一在卖第38张票!!!
窗口一在卖第39张票!!!
窗口一在卖第40张票!!!
窗口一在卖第41张票!!!
窗口一在卖第42张票!!!
窗口一在卖第43张票!!!
窗口一在卖第44张票!!!
窗口一在卖第45张票!!!
窗口一在卖第46张票!!!
窗口三在卖第47张票!!!
窗口三在卖第48张票!!!
窗口三在卖第49张票!!!
窗口三在卖第50张票!!!

注意点:

-Lock 它不像 synchronized ,它可以手动的上锁和解锁,比 synchronized 有更好的操作性;

  • Lock 是一个接口,可以通过 ReentrantLock 来创建对象调用方法。

五、线程生命周期

在这里插入图片描述


标签:Java,张票,MyThread,线程,窗口,多线程,一在,售卖
From: https://blog.csdn.net/m0_62854966/article/details/140558350

相关文章

  • Java 网络编程
    文章目录一、概念二、网络编程三要素三、UDP通信3.1发送端3.2接收端3.3运行结果四、TCP通信4.1发送端4.2接收端4.3运行结果五、三次握手、四次挥手5.1三次挥手(建立连接)5.5四次挥手(数据完整)一、概念在Java中,网络编程指的是计算机之间通过网络来进行通......
  • JAVA:异常
      一.异常概述1.异常体系根类:子类描述java.lang.Throwabljava.lang.ErrorError:严重错误Error,无法通过处理的错误,只能事先避免,例如:栈内存溢出错误,服务器宕机,数据库崩溃...好比绝症。java.lang.Exception(常用)Exception:表示异常,异常产生后程序员可以通过代码的方式纠正......
  • Java 随笔记: 集合与泛型
    文章目录1.集合框架概述2.集合接口2.1Collection接口2.2List接口2.3Set接口2.4Map接口3.集合的常用操作3.1添加元素3.2删除元素3.3遍历元素3.4判断大小3.5判断是否为空4.迭代器4.1迭代器的作用4.2迭代器的使用4.3迭代器与增强for循环4.4迭代器......
  • 一文搞懂Java中的双亲委派
    一天正在宿舍里忙着写代码。突然,老师给我布置了一项新任务:优化他正在开发的项目中的类加载机制。我对类加载器了解不多,开始翻阅各种资料,逐渐了解了Java中的类加载器机制。尤其是当读到双亲委派模型时,脑海中豁然开朗。仿佛看到了类加载请求在层层递进、逐步传递的画面,像极了树状......
  • Java语言概述
    1.常用的DOS命令进入DOS操作窗口:按下Windows+R键盘,打开运行窗口,输入cmd回车,进入到DOS的操作窗口。常用指令:操作说明盘符名称:盘符切换,如:E:表示切换到E盘dir列出当前目录下的文件及文件夹cd目录进入指定的单级目录cd目录1\目录2\...进入指......
  • JavaEE初阶(1)—— 计算机理论常识
    目录一.JavaEE发展历程二.计算机相关知识2.1计算机发展史2.2 冯诺依曼结构(VonNeumannArchitecture)2.3CPU1.cpu做得好的公司2.cpu架构3.cpu的核心参数4.cpu的寄存器(Register)2.4指令 1.概念 2.指令表3.指令格式4.指令执行阶段 2.5操作系统概述一.J......
  • Java基础之异常
    异常1.概述​代码出现了不正常的现象,在Java中每一个异常都是java一个一个的类,或者叫做异常对象2.异常体系说明Throwable:Error:错误类似于人得了癌症,不能通过处理让代码变正常,必须得重新写Exception:异常(所有异常的父类)类似于人得了感冒可以治疗,可以通过......
  • Java 基础学习第一节:初始 Java 及其安装
    第一节001.大项目贯穿学习能力动手能力耐心和毅力表达能力002.学习中碰到问题怎么办找同桌找同学找项目经理找讲师003.学习Java的窍门多想公司需要什么?熟练掌握这个东西的人.如何才能熟练呢?多练练学Java就像学车,天天看别人开车,自己无论怎么看都不会,必须亲自......
  • ChatGPT:Java的双冒号运算符(::)
    ChatGPT:Java的双冒号运算符(::)为什么说双冒号运算符(::)通过引用现有的方法或构造器,简化了Lambda表达式的定义在Java中,双冒号运算符(::)是用于方法引用的符号。方法引用是一种更简洁、更直观的方式来表示Lambda表达式。它通过引用现有的方法或构造器,简化了Lambda表达式的定义......
  • 计算机课设——基于Java web的超市管理系统
    smbms_java_web基于Javaweb的超市管理系统,数据库课程设计1.引言是一个基于JavaWeb连接MySQL的小项目。超市管理系统(smbms)作为每个计算机专业的大学生都是一个很好的练手项目,逻辑层次分明,基础功能包括用户的登录和注销,用户和供应商以及订单信息的增删查改的基础功能......