day18

Thread类

/*
进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。
    举例:IDEA, 阿里云盘, wegame, steam
线程:是进程中的单个顺序控制流,是一条执行路径
    一个进程如果只有一条执行路径,则称为单线程程序。
    一个进程如果有多条执行路径,则称为多线程程序。
java提供了一个类用来描述线程:Thread
    线程是程序中执行的线程。
    Java虚拟机允许应用程序同时执行多个执行线程。
    每个线程都有优先权, 具有较高优先级的线程优先于优先级较低的线程执行
    创建线程的方式:实现Runnable接口,借助Thread类创建线程对象和继承Thread类方式
    若将来每一个线程执行的逻辑是一样的话,推荐采用实现Runnable接口方式实现多线程
    若将来每一个线程执行逻辑不一样的话,推荐采用继承Thread类方式实现多线程

 */

代码案例

创建线程的二种方式:

1.继承Thread类通过创建一个新的类继承Thread类,并重写其run方法来创建线程。

若将来每一个线程执行逻辑不一样的话,推荐采用第一种继承Thread类方式实现多线程
class MyThread extends Thread{
public MyThread(String name) {
    super(name);
}

@Override
public void run() {
    //run方法是将来线程对象启动时要执行的计算逻辑
    for(int i=1;i<=200;i++){
        System.out.println(getName()+" - "+i);
      }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        //创建一个线程对象
//        MyThread t1 = new MyThread();
//        MyThread t2 = new MyThread();
        MyThread t1 = new MyThread("李刚");
        MyThread t2 = new MyThread("钱志强");
        //给线程起名字
        // 方式1:调用setName()方法起名字
//        t1.setName("李刚");
//        t2.setName("钱志强");
        // 方式2:使用构造方法起名字

//        t1.run();
//        t2.run();
        t1.start(); // 系统分配资源给线程t1,启动线程,t1线程具备了执行的资格,具体等到抢到cpu执行权的时候才会执行
        t2.start();
    }
}

2.实现Runnable接口,通过创建一个类实现Runnable接口,并实现其run方法。然后可以将Runnable实例传递给Thread的构造方法来创建线程。

若将来每一个线程执行的逻辑是一样的话,推荐采用第二种实现Runnable接口方式实现多线程    
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 200; i++) {
            //可以先通过获取当前执行的线程对象,调用内部getName()方法
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
}

public class RunnableDemo1 {
    public static void main(String[] args) {
        //创建Runnable对象
        MyRunnable r1 = new MyRunnable();
        //创建线程对象
        Thread t1 = new Thread(r1);
        //创建线程对象同时起名字
        Thread t1 = new Thread(r1, "李刚");
       

        //获取线程优先权 默认线程优先级是 5
        System.out.println("t1: "+t1.getPriority());
        //设置线程优先级 线程优先级范围是从 Thread.MIN_PRIORITY (值为1) 到 Thread.MAX_PRIORITY (值为10)。默认情况下,线程的优先级被设置为 Thread.NORM_PRIORITY (值为5)。
        t1.setPriority(1); // 1-10
        t1.start();
    }
}

休眠线程 public static void sleep(long millis)

Thread.sleep(millis) 方法使当前正在执行的线程暂停执行指定的时间(以毫秒为单位),让出CPU给其他线程使用。

public class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程休眠前");
            try {
                Thread.sleep(2000); // 线程休眠2秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程休眠后");
        });

        thread.start();
    }
}

加入线程: public final void join()

join() 方法等待调用该方法的线程终止。这通常用于主线程等待子线程完成。

public class JoinExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            //每过一秒执行一次
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程:" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();
        
        try {
            thread.join(); // 主线程等待子线程结束 如果不使用这个方法 则子线程与主线程谁抢到cpu使用权就会执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("主线程结束");
    }
}

礼让: public static void yield()

Thread.yield() 方法暗示调度器当前线程愿意让出对CPU的使用权,允许相同优先级的其他线程获得CPU时间片。

public class YieldExample {
    public static void main(String[] args) {
        Thread.currentThread().yield(); // 当前线程礼让,让其他线程有机会执行
        System.out.println("当前线程礼让后执行");
    }
}

后台线程:public final void setDaemon(boolean on)

setDaemon(boolean on) 方法将当前线程标记为守护线程。守护线程通常用于为其他线程提供服务,如垃圾回收器。当所有用户线程结束时,守护线程会自动结束。

用户线程:优先级高于守护线程
守护线程【后台线程】:当一个程序没有了用户线程,守护线程也就没有了
中断线程:
        public final void stop() 已弃用
        public void interrupt()
        java所有的线程要想变成运行状态,必须经过抢cpu执行权

public class DaemonExample {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                //每过一秒执行一次
                System.out.println("守护线程运行中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        });

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

        try {
            Thread.sleep(2000); // 主线程休眠2秒后结束 即守护线程执行两次后将自动结束进程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程结束,守护线程将自动结束");
    }
}

线程安全问题

如何判断一个程序是否存在线程安全的问题

三要素,缺一不可:
1、存在多线程环境
2、存在共享数据/共享变量
3、存在多条语句操作着共享数据/共享变量

解决线程安全的方案

  方案1:同步代码块
      synchronized(对象){需要同步的代码;}  这里对象要保证多个线程对象唯一的  传入的是对象的类的class对象
      同步方法:锁对象是this
      同步静态方法:锁对象是当前类的class文件对象   类.class。

  方案2lock// 加锁
        lock.lock();{进程语句}
      //释放锁
       lock.unlock();

synchronized代码案例

同步代码块(synchronized block)允许你指定一个对象作为锁,来保护对共享资源的访问。当你想要同步一个静态方法时,实际上你是想要同步对一个类级别资源的访问,而不是一个实例资源。
如果静态方法内部需要同步访问类的共享资源,你可以使用这个类的Class对象或者任何与类关联的静态对象作为锁。通常,使用类的Class对象作为锁是最常见的做法,因为它是与类直接关联的
public class MyClass {
    public static void staticMethod() {
        synchronized(MyClass.class) { // 使用类的Class对象作为锁
            // 需要同步的代码
            System.out.println("访问类的共享资源");
        }
    }
    
    public void instanceMethod() {
        synchronized(this) { // 使用当前实例作为锁
            // 实例方法中的同步代码
            System.out.println("访问实例的共享资源");
        }
    }
}
在这个例子中,staticMethod 是一个静态方法,它使用 MyClass.class 作为同步锁来同步对类级别资源的访问。而 instanceMethod 是一个非静态方法,它使用 this(当前实例的引用)作为锁来同步对实例资源的访问。

使用类的 Class 对象作为锁是线程安全的,因为 .class 表达式会返回类的 Class 对象的引用,而这个引用在Java虚拟机中是唯一的。这意味着无论你在哪里使用 MyClass.class,它都指向同一个 Class 对象,因此所有使用这个对象作为锁的线程都将协作以确保同步。

lock锁代码案例

package com.shujia.day18;

import java.util.concurrent.locks.ReentrantLock;

/*
    使用lock锁来解决线程安全的问题
 */
class Window4 implements Runnable {
    int tickets = 200;
    Object obj = new Object();
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 加锁
            lock.lock();
            if (tickets > 0) { // 1
                try {
                    // t1
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
            }
            //释放锁
            lock.unlock();
        }
    }
}


public class SellTicketsDemo4 {
    public static void main(String[] args) {
        Window4 window4 = new Window4();

        Thread t1 = new Thread(window4, "窗口1");
        Thread t2 = new Thread(window4, "窗口2");
        Thread t3 = new Thread(window4, "窗口3");

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

死锁

public class Locks {
    public static final ReentrantLock LOCK1 = new ReentrantLock();
    public static final ReentrantLock LOCK2 = new ReentrantLock();

    private Locks(){}

}
package com.shujia.day18;

/*
    死锁:线程之间存在相互等待的现象

    案例:中国人和外国人
        前提:中国人吃饭必须要两支筷子,外国人吃饭必须一把刀和一把叉
        现在:
            中国人:一支筷子和一把刀
            外国人:一支筷子和一把叉

 */

class Person extends Thread{
    boolean flag;

    public Person(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (Locks.LOCK1){
                System.out.println("if lock1");
                // p1
                synchronized (Locks.LOCK2){
                    System.out.println("if lock2");
                }
            }
        }else {
            synchronized (Locks.LOCK2){
                System.out.println("else lock2");
                // p2
                synchronized (Locks.LOCK1){
                    System.out.println("else lock1");
                }
            }
        }
    }
}

public class DieLock {
    public static void main(String[] args) {
        Person p1 = new Person(true);
        Person p2 = new Person(false);

        p1.start();
        p2.start();
    }
}

等待唤醒

/*
    等待唤醒机制:
        生产者
        消费者
        共享数据
        测试类

   等待唤醒机制:前提是要保证程序是线程安全的
 */
public class WaitNotifyDemo1 {
    public static void main(String[] args) {
        Student s = new Student();

        //创建生产者线程对象
        Product product = new Product(s);
        //创建消费者线程对象
        Consumer consumer = new Consumer(s);

        product.start();
        consumer.start();
    }
}
package com.shujia.day18.bao1;

public class Product extends Thread{

    Student s;
    int i =0;

    public Product(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
//        Student s = new Student();
        while (true){
            synchronized (s){
                //作为生产者,在生产数据之前,应该先检查一下数据有没有被消费
                //如果没有被消费,就等待消费者消费
                if(s.flag){
                    //等待 锁对象调用方法等待
                    try {
                        s.wait(); // 程序走到这一步,发生阻塞,直到锁对象再次在程序中被调用了notify()方法
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }


                if(i%2==0){
                    s.setName("李刚");
                    s.setAge(18);
                }else {
                    s.setName("钱志强");
                    s.setAge(10);
                }

                // 生产完数据后,通知消费者来消费数据
                // 由锁对象来通知
                s.notify();
                s.setFlag(true);

                i++;
            }
        }
    }
}
package com.shujia.day18.bao1;

public class Consumer extends Thread{
    Student s;

    public Consumer(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
//        Student s = new Student();
        while (true){
            synchronized (s){
                //消费者在消费数据之前,应该先看一看数据有没有产生【flag是否是true】
                //若没有数据产生,等待生产者生产数据
                if(!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.getName()+"-"+s.getAge());

                //消费者消费完数据后,通知生产者生产数据
                s.notify();
                s.setFlag(false);
            }

        }
    }
}
posted @   先瞄准再开枪  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示