当前位置: 首页 > news >正文

javaSE学习笔记20-线程(thread)的状态以及控制

线程的状态

在Java中,线程的生命周期可以分为以下几种状态:

  1. 新建(New)

    • 线程对象被创建,但尚未调用start()方法。

    • 控制方式:通过创建Thread对象或其子类对象。

  2. 就绪(Runnable)

    • 线程已经调用了start()方法,等待CPU调度执行。

    • 控制方式:调用start()方法。

  3. 运行(Running)

    • 线程获得CPU资源,正在执行run()方法中的代码。

    • 控制方式:由JVM的线程调度器决定。

  4. 阻塞(Blocked)

    • 线程因为某些原因(如等待锁、I/O操作等)暂时停止执行。

    • 控制方式:通过synchronized关键字、wait()方法、I/O操作等。

  5. 等待(Waiting)

    • 线程无限期等待其他线程执行特定操作(如notify()notifyAll())。

    • 控制方式:调用wait()方法。

  6. 超时等待(Timed Waiting)

    • 线程在指定的时间内等待其他线程执行特定操作。

    • 控制方式:调用sleep(long millis)wait(long timeout)join(long millis)等方法。

  7. 终止(Terminated)

    • 线程执行完run()方法或因为异常退出。

    • 控制方式:run()方法执行完毕或调用stop()方法(不推荐使用)。

控制线程状态的方法

1.新建状态

Thread thread = new Thread(() -> {
    // 线程执行的代码
});

2.就绪状态

thread.start();

3.运行状态

  • 由JVM自动调度,开发者无法直接控制。

4.阻塞状态

  • 使用synchronized关键字:
synchronized (lockObject) {
    // 临界区代码
}
  • 使用wait()方法:
synchronized (lockObject) {
    lockObject.wait();
}

5.等待状态

  • 使用wait()方法:

    synchronized (lockObject) {
        lockObject.wait();
    }

6.超时等待状态

  • 使用sleep()方法:
    Thread.sleep(1000); // 休眠1秒
  • 使用wait(long timeout)方法:
    synchronized (lockObject) {
        lockObject.wait(1000); // 等待1秒
    }
  • 使用join(long millis)方法:
    thread.join(1000); // 等待thread线程1秒

7.终止状态

run()方法执行完毕:
  • 不推荐使用stop()方法,因为它可能导致资源未释放等问题。

 示例代码:

public class ThreadStateExample {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        Thread thread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread is going to wait");
                    lock.wait(); // 进入等待状态
                    System.out.println("Thread is notified and running again");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("Thread state after creation: " + thread.getState()); // NEW

        thread.start();
        System.out.println("Thread state after start: " + thread.getState()); // RUNNABLE

        Thread.sleep(100); // 确保线程进入等待状态
        System.out.println("Thread state after wait: " + thread.getState()); // WAITING

        synchronized (lock) {
            lock.notify(); // 唤醒等待的线程
        }

        Thread.sleep(100); // 确保线程被唤醒
        System.out.println("Thread state after notify: " + thread.getState()); // TERMINATED
    }
}

练习代码

线程停止

(创建一个标志位flag,测试线程停止)

package com.kuang.thread;

/*
线程状态
5个状态
1、new 新生  Thread t = new Thread() 线程对象一旦创建就进入到了新生状态
2、就绪  当调用start()方法,线程立即进入到就绪状态,但不意味着立即调度执行
3、运行状态,调度start完成后,进入运行状态,线程才真正执行线程体的代码块
4、阻塞状态,运行状态时,调用sleep,wait或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待CPU调度执行
5、死亡状态,运行状态时,出现线程中断,或者结束,一旦进入到死亡状态,就不能再次启动

线程相关的方法
setPriority(int newPriority)  更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让正在执行的线程休眠
void join()  等待该线程终止
static void yield()  暂停当前正在执行的线程对象,并执行其他线程
void interrupt()   中断线程,尽量别用这个方式,不建议使用
boolean isAlive  测试线程是否处于活动状态

停止线程
不推荐使用JDK提供的stop()、destroy()方法,已废弃
推荐线程自己停止下来
建议使用一个标志位进行终止变量,当flag = false,则终止线程运行。

测试stop停止线程
1.建议线程正常停止--》利用次数,不建议死循环;
2.建议使用标志位--》设置一个标志位
3.不要使用stop或者destroy等过时或者jdk不建议使用的方法

idea中,alt + enter 快捷重写方法
 */

public class TestStop  implements Runnable{

    //1.设置一个标志位
    private boolean flag = true;

    @Override
    public void run() {
        //2.线程体使用该标识
        int i = 0;
        while (flag){
            System.out.println("run....Thread" + i++);
        }

    }

    //3.设置一个公开的方法停止线程,转换你标志位(对外提供方法改变标识)
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();

        //开启线程
        new Thread(testStop).start();

        for (int i = 0; i < 1000; i++){
            System.out.println("主线程" + i);
            if (i == 900){
                //调用JDK中的stop方法,不是调用上面自己写的stop方法
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

线程休眠-sleep

sleep(时间)指定当前线程阻塞的毫秒数;

sleep存在异常InterruptedException;

sleep时间达到后线程进入就绪状态;

sleep可以模拟网络延时,倒计时等。

每一个对象都有一个锁,sleep不会释放锁;

线程延时(超时等待,运用sleep方法),模拟抢票,上一个人买到票,sleep延时200ms

package com.kuang.thread;

//多个线程同时操作同一个对象。线程不安全
//买火车票的例子,抢票
//模拟网络延时:放大问题的发生性
public class TestSleep implements  Runnable{

    //票数
    private int ticketNums  = 50;
    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                System.out.println("票已经被抢完了,没有票了");
                break;//票已经售完,退出循环
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums + "张票,剩余" + (--ticketNums) + "张票");
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        TestSleep ticket  = new TestSleep();

        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛党").start();
    }
}

线程延时-sleep

(超时等待,运用sleep方法),模拟倒计时,每输出倒计时一次,延时1s

package com.kuang.thread;

//模拟倒计时
public class TestSleep2 {

    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void tenDown() throws InterruptedException {
        int number = 10;

        while (true){
            Thread.sleep(1000);
            System.out.println("倒计时"+ number-- + "秒");
            if (number <= 0){
                break;
            }
        }
    }
}

线程延时(超时等待,运用sleep方法),打印当前时间

package com.kuang.thread;

import java.text.SimpleDateFormat;
import java.util.Date;

//打印时间
public class TestSleep3 {

    public static void main(String[] args) {

        //打印当前系统时间
        Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
        System.out.println("打印时间如下:");

        while (true) {
            try {
                Thread.sleep(1000);//间隔1s
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));//时间格式:HH:mm:ss
                startTime = new Date(System.currentTimeMillis());//更新当前时间
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

线程礼让-yield

礼让线程,让当前正在执行的线程暂停,但不阻塞

将线程从运行状态转为就绪状态

让CPU重新调度,礼让不一定成功!看CPU心情

如何使用Java中的Thread.yield()方法来实现线程的礼让。如下代码:

package com.thread;

//测试礼让线程
//礼让不一定成功,看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();//调用线程的礼让方法
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}
  • main方法中,创建了一个MyYield对象myYield

  • 然后创建了两个线程,分别命名为ab,并将myYield对象作为Runnable任务传递给这两个线程。

  • 最后,启动这两个线程。

  • MyYield类实现了Runnable接口,并重写了run方法。

  • run方法中:

    • 首先打印当前线程的名称,并提示线程开始执行。

    • 然后调用Thread.yield()方法,尝试让当前线程礼让出CPU资源,让其他线程有机会执行。

    • 最后打印当前线程的名称,并提示线程停止执行。

Thread.yield()方法

  • Thread.yield()是一个静态方法,调用它的线程会提示调度器当前线程愿意让出CPU资源,让其他具有相同优先级的线程有机会运行。

  • 需要注意的是,yield()方法并不保证当前线程一定会让出CPU资源,这取决于操作系统的线程调度器。因此,线程礼让不一定成功。

运行结果

由于yield()方法的效果不确定,运行结果可能会有多种情况。以下是可能的输出之一:

a线程开始执行
b线程开始执行
a线程停止执行
b线程停止执行

或者:

a线程开始执行
a线程停止执行
b线程开始执行
b线程停止执行

线程合并-Join

Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

可以想象成插队

练习代码:

package com.thread;

//Join 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
//可以想象成插队
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程vip来了" + i);
        }
    }

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

        //启动我们的线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();//当调用start()方法的时候,vip线程和主线程都开始执行了,此时是并发的

        //主线程
        for (int i = 0; i < 500; i++) {
            if (i == 200){
                thread.join();//vip插队了
            }
            System.out.println("主线程" + i);
        }
    }
}

在代码中,vip线程并不是在main线程执行到i == 200时才开始执行的。实际上,vip线程在thread.start()被调用时就已经开始执行了。thread.join()的作用是让main线程等待vip线程执行完毕后再继续执行,而不是让vip线程在某个特定的时间点才开始执行。

thread.start()被调用时,vip线程就开始执行了。此时,vip线程和main线程是并发执行的。

main线程会继续执行它的循环。当i == 200时,main线程会调用thread.join(),这意味着main线程会暂停执行,直到vip线程执行完毕。

  1. vip线程在start()被调用后就开始执行,它会打印从0到99的数字。由于vip线程和main线程是并发执行的,所以在main线程执行到i == 200之前,vip线程可能已经执行了一部分或全部。

  2. join()的作用
    main线程执行到i == 200时,它会调用thread.join(),此时main线程会等待vip线程执行完毕。如果vip线程已经执行完毕,main线程会立即继续执行。如果vip线程还在执行,main线程会等待直到vip线程完成。

为什么vip线程不是到200才开始执行?

vip线程在thread.start()被调用时就已经开始执行了,而不是在main线程执行到i == 200时才开始。join()的作用是让main线程等待vip线程执行完毕,而不是控制vip线程的启动时间。

要让 vip 线程在 main 线程执行到 i == 200 时才开始执行,可以通过线程同步机制来实现。Java 提供了多种同步工具,比如 CountDownLatchCyclicBarrier 或 wait()/notify() 等。以下是使用 CountDownLatch 的优化代码:

package com.thread;

import java.util.concurrent.CountDownLatch;

public class TestJoin implements Runnable {
    private final CountDownLatch startSignal;

    public TestJoin(CountDownLatch startSignal) {
        this.startSignal = startSignal;
    }

    @Override
    public void run() {
        try {
            // 等待主线程的信号
            startSignal.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // VIP 线程开始执行
        for (int i = 0; i < 100; i++) {
            System.out.println("线程vip来了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 创建一个 CountDownLatch,初始值为 1
        CountDownLatch startSignal = new CountDownLatch(1);

        // 启动 VIP 线程
        TestJoin testJoin = new TestJoin(startSignal);
        Thread thread = new Thread(testJoin);
        thread.start();

        // 主线程执行
        for (int i = 0; i < 500; i++) {
            if (i == 200) {
                // 发送信号,让 VIP 线程开始执行
                startSignal.countDown();
            }
            System.out.println("main" + i);
        }
    }
}
  1. CountDownLatch 的作用

    • CountDownLatch 是一个同步工具,允许一个或多个线程等待其他线程完成操作。

    • 在这里,startSignal 的初始值为 1,表示 vip 线程需要等待 main 线程发送信号后才能继续执行。

  2. vip 线程的等待

    • 在 vip 线程的 run() 方法中,调用 startSignal.await(),使 vip 线程等待,直到 main 线程调用 startSignal.countDown()

  3. main 线程的信号发送

    • 当 main 线程执行到 i == 200 时,调用 startSignal.countDown(),释放 vip 线程,使其开始执行。

  4. 线程执行的顺序

    • vip 线程在启动后会立即进入等待状态,直到 main 线程执行到 i == 200 时才会开始执行。

输出结果

  • main 线程会从 0 开始打印,直到 200。

  • 当 i == 200 时,vip 线程开始执行,打印 "线程vip来了"。

  • main 线程和 vip 线程会交替执行(具体顺序取决于线程调度)。

其他实现方式

如果不想使用 CountDownLatch,也可以使用 wait() 和 notify() 来实现类似的功能。以下是使用 wait() 和 notify() 的版本:

package com.thread;

public class TestJoin implements Runnable {
    private final Object lock;

    public TestJoin(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            try {
                // 等待主线程的通知
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // VIP 线程开始执行
        for (int i = 0; i < 100; i++) {
            System.out.println("线程vip来了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();

        // 启动 VIP 线程
        TestJoin testJoin = new TestJoin(lock);
        Thread thread = new Thread(testJoin);
        thread.start();

        // 主线程执行
        for (int i = 0; i < 500; i++) {
            if (i == 200) {
                synchronized (lock) {
                    // 通知 VIP 线程开始执行
                    lock.notify();
                }
            }
            System.out.println("main" + i);
        }
    }
}
  • 使用 CountDownLatch 或 wait()/notify() 可以精确控制线程的执行顺序。

  • CountDownLatch 更适合这种一次性信号通知的场景,而 wait()/notify() 更适合复杂的线程协作场景。

线程的状态观测 

线程状态,线程可以处于以下状态之一
NEW 尚未启动的线程处于此状态
RUNNABLE 在java虚拟机中执行的线程处于此状态
BLOCKED 被阻塞等待监视器锁定的线程处于此状态
WAITING 正在等待另一个线程执行特定动作的线程处于此状态
TIMED WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
TERMINATED 已退出的线程处于此状态
一个线程可以在给定时间点处于一个状态,这些状态是不反映任何操作系统状态的虚拟机状态

练习代码:

package com.thread;

/*
线程状态观测
线程状态,线程可以处于以下状态之一
NEW 尚未启动的线程处于此状态
RUNNABLE 在java虚拟机中执行的线程处于此状态
BLOCKED 被阻塞等待监视器锁定的线程处于此状态
WAITING 正在等待另一个线程执行特定动作的线程处于此状态
TIMED WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
TERMINATED 已退出的线程处于此状态
一个线程可以在给定时间点处于一个状态,这些状态是不反映任何操作系统状态的虚拟机状态
 */
//观察测试线程的状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        //采用Lambda表达式
        Thread thread = new Thread( ()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);//延时1s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("输出线程结束的状态");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);//NEW 状态

        //观察启动后
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);//Run  状态

        while (state != Thread.State.TERMINATED){//只要线程不停止,就一直输出状态
            Thread.sleep(100);//1s钟更新10次
            state = thread.getState();//更新线程状态
            System.out.println(state);//输出更新后的线程状态
        }
    }
}

线程优先级


Java中提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1-10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.MIN_PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority().setPriority(int xxx)
优先级的设定建议在start()调度前
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度

练习代码:

package com.thread;

/*
线程优先级
Java中提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1-10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.MIN_PRIORITY = 5;
使用以下方式改变或获取优先级
getPriority().setPriority(int xxx)
优先级的设定建议在start()调度前
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
 */

//测试线程的优先级
public class TestPriority{

    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread thread1 = new Thread(myPriority);
        Thread thread2 = new Thread(myPriority);
        Thread thread3 = new Thread(myPriority);
        Thread thread4 = new Thread(myPriority);
        Thread thread5 = new Thread(myPriority);
        Thread thread6 = new Thread(myPriority);

        //先设置优先级,再启动
        thread1.start(); //默认优先级

        thread2.setPriority(1);//设置优先级为1
        thread2.start();

        thread3.setPriority(4);//设置优先级为4
        thread3.start();

        thread4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10,最大优先级
        thread4.start();

        thread5.setPriority(Thread.MIN_PRIORITY);//MIN_PRIORITY=1,最小优先级
        thread5.start();

        thread6.setPriority(Thread.NORM_PRIORITY);//NORM_PRIORITY=5,默认优先级
        thread6.start();

    }
}

class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

守护(daemon)线程

线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如,后台记录操作日志,监控内存,垃圾回收等待..

package com.thread;

/*
守护(daemon)线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
如,后台记录操作日志,监控内存,垃圾回收等待..
 */

//测试守护线程
//上帝守护你
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);//默认是false,表示是用户线程,正常的线程都是用户线程

        thread.start();//启动上帝线程

        new Thread(you).start();//你 用户线程启动
    }
}

//上帝,永生的
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你!");
        }
    }
}

//你,一生36500
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着");
        }
        System.out.println("====goodbye world!====");//再见,世界
    }
}

并发、线程同步、队列和锁

并发:多个线程访问同一个对象
某些线程还想修改这个对象,这时候我们就需要线程同步
线程同步其实就是一种等待机制,多个需要同时访问 此对象 的线程进入这个 对象的等待池
等待前面线程使用完毕,下一个线程再使用

队列和锁
每个对象都有一个锁
形成条件  队列+锁  才能保证我们线程同步的安全性

线程同步:
由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题
为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制 synchronized,
当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    1、一个线程持有锁会导致其他所有需要此锁的线程挂起;
    2、在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
    3、如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题

同步方法:
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,
这套机制就是synchronized关键字,它包括两种用法:
synchronized方法和synchronized块
    同步方法:public synchronized void method(int args){}
synchronized方法控制“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得
调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才
释放锁,后面被阻塞的线程才能获得这个锁,继续执行
    缺陷:若将一个大的方法申明为synchronized将会影响效率

同步方法弊端
    只读   方法里面需要修改的内容才需要锁,锁的太多,浪费资源
    A代码

    修改
    B代码

同步块
同步块:synchronized(Obj ){}
Obj称之为同步监视器
    Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]

同步监视器的执行过程
    1.第一个线程访问,锁定同步监视器,执行其中代码,
    2.第二个线程访问,发现同步监视器被锁定,无法访问,
    3.第一个线程访问完毕,解锁同步监视器
    4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问

多线程模拟抢票

实现了一个多线程模拟买票的程序,在多线程环境下,多个线程同时访问和修改共享资源(票数)时,可能出现线程安全问题。

package com.syn;

/*
并发:多个线程访问同一个对象
某些线程还想修改这个对象,这时候我们就需要线程同步
线程同步其实就是一种等待机制,多个需要同时访问 此对象 的线程进入这个 对象的等待池
等待前面线程使用完毕,下一个线程再使用

队列和锁
每个对象都有一个锁
形成条件  队列+锁  才能保证我们线程同步的安全性

线程同步:
由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题
为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制 synchronized,
当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    一个线程持有锁会导致其他所有需要此锁的线程挂起;
    在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
    如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
 */

//不安全的买票
//线程不安全
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"苦逼的我").start();
        new Thread(buyTicket,"牛逼的你们").start();
        new Thread(buyTicket,"可恶的黄牛党").start();
    }

}

//买票类
class BuyTicket implements Runnable{

    //票,定义票数
    private int ticketNumbers = 10;
    boolean flag = true;//定义一个标志位,用来控制循环,外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();//调用买票方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //买票方法
    private void buy() throws InterruptedException {
        //判断是否有票,如果票数小于等于0,就是没票了
        if (ticketNumbers <= 0) {
            flag = false;
            return;
        }

        //模拟延时
        Thread.sleep(100);

        //买票
        System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNumbers-- + "张票");
    }
}
1. 代码功能
  • 模拟买票:程序模拟了多个用户(“苦逼的我”、“牛逼的你们”、“可恶的黄牛党”)同时从一个共享的票池中抢票的过程。

  • 线程安全问题:由于多个线程同时访问和修改共享资源(票数),可能会导致数据不一致的问题。例如,多个线程可能同时读取到相同的票数,导致同一张票被多个线程抢到。


2. 代码结构
  • UnsafeBuyTicket:这是程序的入口类,包含main方法。在main方法中,创建了一个BuyTicket对象,并启动了三个线程来模拟多个用户抢票的过程。

  • BuyTicket:实现了Runnable接口,表示买票操作。该类包含以下属性:

    • ticketNumbers:票的总数,初始值为10。

    • flag:一个布尔标志位,用于控制买票循环的结束。

    BuyTicket类实现了Runnable接口的run方法,并在run方法中调用了buy方法来实现买票逻辑。


3. 代码实现
  • UnsafeBuyTicket类的main方法

    • 创建一个BuyTicket对象。

    • 启动三个线程,分别代表“苦逼的我”、“牛逼的你们”、“可恶的黄牛党”,并传入BuyTicket对象作为Runnable任务。

  • BuyTicket类的run方法

    • 使用一个while循环不断调用buy方法,直到票被抢完(flag变为false)。

  • BuyTicket类的buy方法

    • 检查票数是否大于0。如果票数小于等于0,将flag设置为false,结束循环。

    • 使用Thread.sleep(100)模拟买票操作的延迟,放大线程安全问题。

    • 输出当前线程抢到的票号,并将票数减1。


4. 线程安全问题
  • 问题描述

    • 由于buy方法没有使用任何同步机制,多个线程可能会同时访问和修改ticketNumbers,导致数据不一致。例如,多个线程可能同时读取到相同的票数,导致同一张票被多个线程抢到。

    • 输出结果可能会出现重复的票号,或者票号顺序混乱。

  • 如何解决

    • 可以通过使用synchronized关键字或ReentrantLock等同步机制来确保同一时间只有一个线程可以访问和修改共享资源。

采用线程同步synchronized关键字优化后代码:

package com.kuang.syn;


//买票
//synchronized关键字实现线程安全
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"苦逼的我").start();
        new Thread(buyTicket,"牛逼的你们").start();
        new Thread(buyTicket,"可恶的黄牛党").start();
    }

}

//买票类
class BuyTicket implements Runnable{

    //票,定义票数
    private int ticketNumbers = 10;
    boolean flag = true;//定义一个标志位,用来控制循环,外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();//调用买票方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //买票方法,synchronized 同步方法,锁的是this
    private synchronized void buy() throws InterruptedException {
        //判断是否有票,如果票数小于等于0,就是没票了
        if (ticketNumbers <= 0) {
            flag = false;
            return;
        }

        //模拟延时
        Thread.sleep(1000);

        //买票
        System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNumbers-- + "张票");
    }
}

模拟银行取款

采用多线程的方法实现了一个简单的银行取款模拟程序:

package com.syn;

//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {

    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.start();
    }
}

//账户
class Account{
    int money;//金额
    String name;

    public Account(int money, String name){
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Drawing extends Thread{

    Account account;//账户
    //取了多少钱
    int drawingMoney;
    //现在手里有多少钱
    int nowMoney;

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        //判断有没有钱
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName() + "银行卡里钱不够,取不了");
            return;
        }

        //sleep可以放大问题的发生性,延时1s
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡内余额 = 余额 - 你取的钱
        account.money = account.money - drawingMoney;
        //你手里的钱
        nowMoney = nowMoney + drawingMoney;

        System.out.println(account.name + "余额为:" + account.money);
        //Thread.currentThread().getName = this.getName()
        System.out.println(this.getName() + "手里的钱:" + nowMoney);
    }
}

在多线程环境下,多个线程同时访问和修改共享资源(账户余额)时可能出现的线程安全问题。

1. 代码功能
  • 模拟银行取款:程序模拟了两个用户(“你”和“girlFriend”)同时从一个共享账户中取款的过程。

  • 线程安全问题:由于多个线程同时访问和修改共享资源(账户余额),可能会导致数据不一致的问题。虽然代码中没有显式的线程同步机制,但通过Thread.sleep(1000)放大了问题的发生性,使得线程安全问题更容易被观察到。

2. 代码结构
  • UnsafeBank:这是程序的入口类,包含main方法。在main方法中,创建了一个Account对象(账户),并启动了两个Drawing线程(“你”和“girlFriend”)来模拟取款操作。

  • Account:表示银行账户,包含两个属性:

    • money:账户余额。

    • name:账户名称。

    该类有一个构造方法,用于初始化账户的余额和名称。

  • Drawing:继承自Thread类,表示取款操作。每个Drawing对象代表一个取款线程。该类包含以下属性:

    • account:要取款的账户。

    • drawingMoney:要取款的金额。

    • nowMoney:当前线程(用户)手中的钱。

    Drawing类重写了Thread类的run方法,在run方法中实现了取款逻辑。

3. 代码实现
  • UnsafeBank类的main方法

    • 创建一个Account对象,初始余额为100,名称为“结婚基金”。

    • 创建两个Drawing线程对象,分别代表“你”和“girlFriend”,并启动这两个线程。

  • Drawing类的run方法

    • 首先检查账户余额是否足够取款。如果余额不足,输出提示信息并返回。

    • 使用Thread.sleep(1000)模拟取款操作的延迟,放大线程安全问题。

    • 更新账户余额和当前线程手中的钱。

    • 输出账户余额和当前线程手中的钱。

4. superthis关键字的作用
  • super关键字

    • Drawing类的构造方法中,super(name)调用了父类Thread的构造方法,并将线程名称传递给父类。这样,每个Drawing线程对象都会有一个名称,可以通过this.getName()获取。

  • this关键字

    • Drawing类的构造方法中,this.account = accountthis.drawingMoney = drawingMoney用于将传入的参数赋值给当前对象的属性。

    • run方法中,this.getName()用于获取当前线程的名称。

5. 线程安全问题
  • 问题描述:由于Drawing类的run方法中没有使用任何同步机制,两个线程可能会同时访问和修改account.money,导致数据不一致。例如,两个线程可能同时读取到相同的余额,然后各自减去取款金额,导致最终的余额计算错误。

  • 如何解决:可以通过使用synchronized关键字或ReentrantLock等同步机制来确保同一时间只有一个线程可以访问和修改共享资源。

采用synchronized同步块优化后代码: 

package com.kuang.syn;

//使用synchronized同步代码块安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {

    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.start();
    }
}

//账户
class Account{
    int money;//金额
    String name;

    public Account(int money, String name){
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Drawing extends Thread{

    Account account;//账户
    //取了多少钱
    int drawingMoney;
    //现在手里有多少钱
    int nowMoney;

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    //synchronized默认锁的是this(这个类),synchronized同步块可以锁任何东西
    @Override
    public void run() {

        //锁的对象就是变化的量,需要增删改查的对象
        synchronized (account){
            //判断有没有钱
            if (account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName() + "银行卡里钱不够,取不了");
                return;
            }

            //sleep可以放大问题的发生性,延时1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //卡内余额 = 余额 - 你取的钱
            account.money = account.money - drawingMoney;
            //你手里的钱
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name + "余额为:" + account.money);
            //Thread.currentThread().getName = this.getName()
            System.out.println(this.getName() + "手里的钱:" + nowMoney);
        }
    }
}

线程不安全的集合代码:

package com.syn;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread( ()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(50);
        System.out.println(list.size());
    }
}

使用synchronized同步块优化后代码:

package com.kuang.syn;

import java.util.ArrayList;
import java.util.List;

//线程安全的集合
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread( ()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(50);
        System.out.println(list.size());
    }
}

测试JUC安全类型的集合

package com.kuang.syn;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();

        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
               copyOnWriteArrayList.add(Thread.currentThread().getName());
            }).start();

        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(copyOnWriteArrayList.size());
    }
}

相关文章:

  • Day6 25/2/19 WED
  • Win10下安装 Redis
  • ssh免密登录配置
  • 【前端】使用WebStorm创建第一个项目
  • 【笑着写算法系列】位运算
  • Python常见面试题的详解13
  • Linux | 进程控制(进程终止与进程等待)
  • 鱼皮面试鸭30天后端面试营
  • MySQL 如何使用EXPLAIN工具优化SQL
  • 知识拓扑-xmind
  • 第四章:高级特性与最佳实践 - 第三节 - Tailwind CSS 性能优化策略
  • QSNCTF-WEB做题记录
  • Android Hal AIDL 简介 (一)
  • Android 应用 A 通过 aidl 主动向应用 B 发送数据示例
  • 学习next.js的同时的一些英语单词记录
  • Rust编程语言入门教程(一)安装Rust
  • 啥是CTF?新手如何入门CTF?网络安全零基础入门到精通实战教程!
  • 大数据 高并发 解决方案 Moebius
  • [c语言日寄]指针进阶:函数指针数组——转换表
  • FFmpeg 源码编译安装
  • 紧盯大V、网红带货肉制品,整治制售假劣肉制品专项行动开展
  • 现场|万米云端,遇见上博
  • A股三大股指收涨:军工股掀涨停潮,两市成交近1.5万亿元
  • 短剧剧组在贵州拍戏突遇极端天气,演员背部、手臂被冰雹砸伤
  • 罗马尼亚临时总统博洛让任命普雷多尤为看守政府总理
  • 今晚上海地铁多条线路加开定点加班车,2号线运营至次日2时