多线程基础

基础知识

线程

线程是操作系统能够进行运算调度的最小单位.它被包含到进程中,是进程中的实际运作单位.

多线程用来提高效率.让程序同时做多件事.

并发

在同一时刻,有多个指令在单个CPU上交替执行.

并行

在同一时刻,有多个指令在多个CPU上同时执行

多线程实现方式

1.实现runnable接口

2.继承Thread类,重写run方法.

3.实现Callable接口

package com.lanqiao.study;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 多线程的第三种实现方式
         * 1.创建一个类MyCallable实现Callable接口
         * 2.重写call(有返回值,表示多线程运行的结果)
         * 3.创建MyCallable对象(表示多线程要执行的任务)
         * 4.创建FutureTask的对象(作用管理多线程运行的结果)
         * 5.创建Thread类的对象,并启动(表示线程)
         */
    //3.创建MyCallable对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //4.创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程对象
        Thread t1 = new Thread(ft);
        t1.start();
        //获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }
}

常见的成员方法

package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        //1.String getName()返回该线程名字
        //2.void setName(String name)设置线程的名字
        //要设置名字 可以用set方法设置 也可以构造方法设置
        //3.static Thread currentThread()获取当前线程的对象
        //细节:当JVM启动后 会自动启动多条线程
        //其中有一条main线程 它的作用是去调用Main方法 并执行里面的代码
        //在以前 我们写的所有代码 起始都是运行在Main线程当中的
        //4.static void sleep(long time)让线程休眠指定的时间,单位为ms
        // 哪条线程执行到这个方法 就会停留对应时间,时间到后自动醒来 自动执行其他代码
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread("飞机");
        t1.start();
        t2.start();
    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class MyThread extends Thread {
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            try{
                Thread.sleep(1000);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(getName()+"@"+i);
        }
    }

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

    public MyThread() {
    }
}

线程的优先级

JAVA属于抢占式调度 充满随机性

优先级分为10档 最小1 最大10 默认5

优先级不是绝对的 只是一个概率

package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        /*
            setPriority(int newPriority) 设置线程的优先级
            final int getPriority()     获取线程的优先级
         */
        MyRuunable mr = new MyRuunable();
        Thread t1 = new Thread(mr,"飞机");
        Thread t2 = new Thread(mr,"坦克");
        t1.setPriority(1);
        t2.setPriority(9);
//        System.out.println(t1.getPriority());
//        System.out.println(t2.getPriority());
        t1.start();
        t2.start();

    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class MyRuunable implements Runnable {
    @Override
    public void run() {
        for(int i=1;i<=100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

守护线程

final void setDaemon(boolean on)

当其他的非守护线程执行完毕后,守护线程会陆续结束

可以用于这样的场景 聊天:线程1 传输文件:线程2

聊天结束了传输文件就不必要了(特殊情境)

礼让线程

public static void yield()

通过Thread.yield()表示出让当前CPU的执行权,尽可能让线程执行均匀

插入线程/插队线程

public static void join()

t.join(); 把t线程插入到当前线程之前 会先执行t线程的代码

线程的生命周期

线程安全的问题

package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class MyThread extends Thread {
    int ticket = 0;
    @Override
    public void run() {
//        for(int i=0;i<100;i++){
//            try{
//                Thread.sleep(1000);
//            }catch(Exception e){
//                e.printStackTrace();
//            }
//            System.out.println(getName()+"@"+i);
//        }
        while (true) {
            if(ticket<100){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName()+"正在卖第"+ticket+"张票");
            }else{
                break;
            }
        }
    }

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

    public MyThread() {
    }
}

如本题中会出现问题:

1.相同的票卖了多次

2.出现了超出范围的票

线程执行时,有随机性

同步代码块

把操作共享数据的代码锁起来

synchronized(锁){
操作共享数据的代码

}

特点1:锁默认打开,有一个线程进行去了,锁自动关上

特点2:里面的代码全部执行完毕,线程出来,锁自动打开

package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/1
 * @name: 刘宇
 */
public class MyThread extends Thread {
    static int ticket = 0;
    //锁对象一定是唯一的
    static Object obj = new Object();
    @Override
    public void run() { 
        while (true) {
            synchronized(obj){
                if(ticket<100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName()+"正在卖第"+ticket+"张票");
                }else{
                    break;
                }
            }
        }
    }

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

    public MyThread() {
    }
}

注意锁放的位置

锁对象一定要是唯一的

synchronized(MyThread.class)这种写法保险

同步方法

StringBuffer是线程安全的 因为是同步方法

package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/2
 * @name: 刘宇
 */
public class MyThread1 extends Thread {
    static int ticket=0;
    //同步代码块的方式
//    @Override
//    public void run() {
//        while(true){
//            synchronized(MyThread1.class){
//                if(ticket==100){
//                    break;
//                }else{
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    ticket++;
//                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!!");
//                }
//            }
//        }
//    }
    //同步方法
    @Override
    public void run() {
        while (true) {
            if(method())break;
        }
    }
    private synchronized boolean method(){
        if(ticket==100){
                    return true;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!!");
                }
        return false;
    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/2
 * @name: 刘宇
 */
public class ThreadDemo4 {
    public static void main(String[] args) {

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

lock锁

即可手动开关锁

public class MyThread1 extends Thread {
    static int ticket=0;
    //同步代码块的方式
    //加static使多个对象共享同一把锁
    static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
//            synchronized(MyThread1.class){
            lock.lock();
            try {
                if (ticket == 100) {
                    break;
                } else {
                    Thread.sleep(100);
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!!!");
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }finally {//保证释放锁
                lock.unlock();
            }
        }
    }
  }
}

死锁

避免锁嵌套

生产者和消费者(等待唤醒机制)

生产者消费者模式是一个十分经典的多线程协作的模式.notify wait

类似于ABABABABABABABABABABA...

void wait() 当前线程等待,直到被其他线程唤醒

void notify() 随机唤醒单个线程

void notifyAll()唤醒所有线程

public class ThreadDemo5 {
    public static void main(String[] args) {
        cook c = new cook();
        foodie f = new foodie();
        c.setName("厨师");
        f.setName("吃货");
        c.start();
        f.start();
    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/2
 * @name: 刘宇
 */
public class cook extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized(desk.lock){
                if(desk.count==0){
                    break;
                }else{
                    if(desk.foodFlag==1){
                        //判断桌子上是否有食物
                        try {
                            //有就等待
                            desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //没有面条就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上食物状态
                        desk.foodFlag=1;
                        //叫醒等待的消费者
                        desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/2
 * @name: 刘宇
 */
public class foodie extends Thread{
    /*
    1.循环
    2.同步代码块
    3.判断共享数据是否到了末尾 到了
    4.没到末尾
     */
    @Override
    public void run() {
        while(true){
            synchronized(desk.lock){
                if(desk.count==0){
                    break;
                }else{
                    //判断是否有面条
                    if(desk.foodFlag==0){
                        //没等待
                        try {
                            desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //把吃的总数-1
                        desk.count--;
                        //有开吃
                        System.out.println("在吃面条,还能吃"+desk.count+"碗");
                        //吃完后唤醒厨师继续做
                        desk.lock.notifyAll();
                        //修改桌子的状态
                        desk.foodFlag=0;
                    }
                }
            }
        }
    }
}
package com.lanqiao.study;

/**
 * @author: lans
 * @date: 2025/4/2
 * @name: 刘宇
 */
public class desk {
    //0是没有面条 1有
    public static int foodFlag=0;
    //总个数
    public static int count=10;
    //锁对象
    public static Object lock=new Object();
}

等待唤醒机制(阻塞队列方式实现)

public class ThredDemo {
    public static void main(String[] args) {
        //需要指定上限
        //1.创建阻塞队列对象 将对象传递给cook foodie实现共用同一个对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        //2.创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);
        //3.开启线程
        c.start();
        f.start();
    }
}
public class Cook extends Thread {
    ArrayBlockingQueue<String> queue;
    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while(true){
            //不断的把面条放到阻塞队列中
                try {
                    queue.put("面条");
                    //锁在阻塞队列里面 打印语句在外导致输出结果看的不方便但执行是合理的
                    System.out.println("厨师放了一碗面条");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
}

public class Foodie extends Thread {
    ArrayBlockingQueue<String> queue;
    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            String food = null;
            try {
                food = queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

多线程的6种状态

练习1.抢红包

100元三个包 五个人抢

package com.lanqiao.homework;

/**
 * @author: lans
 * @date: 2025/4/3
 * @name: 刘宇
 * 抢红包 100块分成了3个包 现在有5个人去抢 红包是共享数据
 */
public class thread1 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread1 t1 = new MyThread1();
        MyThread1 t2 = new MyThread1();
        MyThread1 t3 = new MyThread1();
        MyThread1 t4 = new MyThread1();
        MyThread1 t5 = new MyThread1();
        t1.setName("ly");
        t2.setName("鸡哥");
        t3.setName("31");
        t4.setName("sm");
        t5.setName("jm");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

}

package com.lanqiao.homework;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: lans
 * @date: 2025/4/3
 * @name: 刘宇
 */
public class MyThread1 extends Thread {
    static BigDecimal money = BigDecimal.valueOf(100);
    //100块分3个包
    static int count = 3;
    //红包最小金额
    static final BigDecimal MIN = BigDecimal.valueOf(0.01);

    private static final Lock lock = new ReentrantLock(true); // 启用公平锁

    @Override
    public void run() {
        //同步代码块
        lock.lock();
        try {
            if (count == 0) {
                System.out.println(getName() + "没有抢到红包");
            } else {
                //判断是否到末尾
                BigDecimal price;
                if (count == 1) {
                    //此时是最后一个红包 无需随机 即剩下的所有金额
                    price = money;
                } else {
                    //表示是第一二次
                    Random r = new Random();
                    //第一个红包最大99.98
                    double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
                    //范围0.01-bounds
                    price = BigDecimal.valueOf(r.nextDouble(bounds));
                }
                price = price.setScale(2, RoundingMode.HALF_UP);
                money = money.subtract(price);
                count--;
                System.out.println(getName() + "抢到了" + price + "元");
            }
        } finally {
            lock.unlock();
        }
    }
}

练习2.抽奖箱抽奖

package com.lanqiao.homework;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;

/**
 * @author: lans
 * @date: 2025/4/3
 * @name: 刘宇
 */
public class thread2 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        Collections.addAll(list,10,5,20,50,100,200,500);
        MyThread2 t1 = new MyThread2(list);
        MyThread2 t2 = new MyThread2(list);
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");
        t1.start();
        t2.start();
    }
}
package com.lanqiao.homework;

import java.util.ArrayList;
import java.util.Collections;

/**
 * @author: lans
 * @date: 2025/4/3
 * @name: 刘宇
 */
public class MyThread2 extends Thread {
    //用集合来存放奖品,在构造方法中赋值集合
    ArrayList<Integer> list;
    public MyThread2(ArrayList<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        //1.循环
        //2.同步代码块
        //3.判断
        while(true){
            synchronized (MyThread2.class){
                if(list.size()==0){
                    break;
                }else{
                    //shuffle方法打乱集合然后抽取
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    System.out.println(getName()+"又产生了一个"+price+"元大奖");
                }
            }
            //让线程休眠 结果尽可能随机
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程池

以前写多线程的弊端 用到线程就创建 用完后消失 这样是浪费资源的

创建一个线程池可以解决这个问题.

通过Executors:线程池的工具类,调用方法返回不同类型的线程池对象

public static ExecutorService newCachedThreadPool(int nThreads);

线程池核心原理
  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,线程池中没有空闲线程,也无法创建新的线程,任务就会排队等待.
package com.lanqiao.homework;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: lans
 * @date: 2025/4/3
 * @name: 刘宇
 */
public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
//        public static ExecutorService newCachedThreadPool(int nThreads);
        //1.获取线程池对象
//        ExecutorService pool1 = Executors.newCachedThreadPool();
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
        //2.提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        //3.销毁线程池
//        pool1.shutdown();
    }
}
package com.lanqiao.homework;

/**
 * @author: lans
 * @date: 2025/4/3
 * @name: 刘宇
 */
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+i);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值