多线程

多线程

1、多线程的定义

1、什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
2、多线程的作用?
提高效率
3、多线程的应用场景?
只要你想让多个事情同时运行就需要用到多线程
比如: 软件中的耗时操作、所有的聊天软件、所有的服务器
4、并发:在同一时刻,有多个指令在单个CPU上交替执行
5、并行:在同一时刻,有多个指令在多个CPU上同时执行

2、多线程的实现方式

1、继承Thread类的方式进行实现
2、实现Runnable接口的方式进行实现
3、利用Callable接口和Future接口方式实现

2.1 继承Thread类的方式进行实现

public class MyThead extends Thread {
    @Override
    public void run() {
        //线程执行代码
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "Hello");
        }
    }
}
public class Demo01_1 {
    public static void main(String[] args) {
        //创建线程
        MyThead m1 = new MyThead();
        MyThead m2 = new MyThead();
        //设置线程名称
        m1.setName("线程1");
        m2.setName("线程2");
        //启动线程
        m1.start();
        m2.start();
    }
}

2.2 实现Runnable接口的方式进行实现

public class MyRun implements Runnable {
    @Override
    public void run() {
        //获取当前线程对象
        Thread m = Thread.currentThread();
        //线程执行代码
        for (int i = 0; i < 10; i++) {
            System.out.println(m.getName()+"Hello");
        }
    }
}
public class Demo01_1 {
    public static void main(String[] args) {
        //多线程执行任务
        MyRun mr = new MyRun();
        //创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        //设置线程名称
        t1.setName("线程1");
        t2.setName("线程2");
        //启动线程
        t1.start();
        t2.start();
    }
}

2.3 利用Callable接口和Future接口方式实现

特点可以获取多线程运行结果

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <=100; i++) {
            sum += i;
        }
        return sum;
    }
}
public class Demo01_1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //多线程执行任务
        MyCallable mc = new MyCallable();
        //管理多线程运行结果
        FutureTask<Integer> ft = new FutureTask<Integer>(mc);
        //创建线程对象
        Thread t = new Thread(ft);
        //启动线程
        t.start();
        //获取线程结果
        Integer result = ft.get();
        System.out.println(result);
    }
}

3、线程常用方法

略 网上查

4、线程生命周期

略 网上查

5、线程安全问题

5.1 售票问题

public class MyThread extends Thread {
    //票数
    //有多个对象共享一个变量
    static int ticket = 0;

    @Override
    public void run() {
        //执行代码
        while (true) {
            if (ticket < 100) {
                ticket++;
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(getName() + "售出第" + ticket + "票!!");
            } else {
                break;
            }
        }
    }
}
public class Demo01_1 {
    public static void main(String[] args){
        //创建线程对象
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();
        MyThread m3 = new MyThread();
        //设置线程名称
        m1.setName("窗口1");
        m2.setName("窗口2");
        m3.setName("窗口3");
        //启动线程
        m1.start();
        m2.start();
        m3.start();
    }
}

上面的代码执行会出现多个窗口售卖一张票和多票、少票问题。

解决上面问题使用同步代码块 synchronized

5.2 同步代码块 synchronized

1、同步代码块:把操作共享数据的代码锁起来
2、格式:
synchronized (锁对象){
   操作共享数据的代码
}

特点1: 锁默认打开,有一个线程进去了,锁自动关闭
特点2: 里面的代码全部执行完毕,线程出来,锁自动打开
public class MyThread extends Thread {
    //票数
    //有多个对象共享一个变量
    static int ticket = 0;

    @Override
    public void run() {
        //执行代码
        while (true) {
            //同步代码块
            //锁对象 一定是唯一的 通常设置当前类的字节码文件
            synchronized (MyThread.class){
                if (ticket < 100) {
                    ticket++;
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "售出第" + ticket + "票!!");
                } else {
                    break;
                }
            }
        }
    }
}
public class Demo01_1 {
    public static void main(String[] args){
        //创建线程对象
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();
        MyThread m3 = new MyThread();
        //设置线程名称
        m1.setName("窗口1");
        m2.setName("窗口2");
        m3.setName("窗口3");
        //启动线程
        m1.start();
        m2.start();
        m3.start();
    }
}

5.3 同步方法

1、同步方法
就是把synchronized关键字加到方法上

2、格式:
修饰符 synchronized 返回值类型 方法名(方法参) {...}

特点1: 同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定

非静态: this
静态:当前类的字节码文件对象
public class MyRunner implements Runnable {

    //多个对象共享票数 加上static
    static int ticket = 0;

    @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) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票!!");
        }
        return false;
    }
}
public class Demo01_2 {
    public static void main(String[] args) {
        MyRunner my = new MyRunner();
        //创建线程
        Thread t1 = new Thread(my);
        Thread t2 = new Thread(my);
        Thread t3 = new Thread(my);
        //设置线程名称
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        //线程启动
        t1.start();
        t2.start();
        t3.start();
    }
}

5.4 Lock锁

1、虽然我们可以理解同步代码块和同步方法的锁对象问题
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

2、Lock实现提供比使用synchronized方法和语句
可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法
void lock(): 获得锁
void unlock():释放锁

3、Lock是接口不能直接实例化,这里采用它的
实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock():创建一个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 == 100) {
                    break;
                } else {
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "售出第" + ticket + "票!!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁
                lock.unlock();
            }

        }
    }
}
public class Demo01_1 {
    public static void main(String[] args){
        //创建线程对象
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();
        MyThread m3 = new MyThread();
        //设置线程名称
        m1.setName("窗口1");
        m2.setName("窗口2");
        m3.setName("窗口3");
        //启动线程
        m1.start();
        m2.start();
        m3.start();
    }
}

5.5 死锁

不要嵌套两个锁

6、生产者和消费者

6.1 常见方法

方法名称
void wait() 当前线程等待,直到被其他线程唤醒
void notify() 随机唤醒单个线程
void notifyAll() 唤醒所有线程
/**
 * 控制生产者和消费者的执行
 */
public class Desk {
    //0:有面条 1:没有面条
    public static int foodFlag = 0;
    //总个数
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}
/**
 * 消费者
 */
public class Foodie extends Thread {
    @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) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        //数量减一
                        Desk.count--;
                        //有就开吃
                        System.out.println("吃货在吃面条,还能吃" + Desk.count + "碗!");
                        //吃完唤醒厨师
                        Desk.lock.notifyAll();
                        //修改桌子状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
/**
 * 生产者
 */
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) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        //制作食物
                        System.out.println("厨师做了一碗面条!");
                        //修改桌子状态
                        Desk.foodFlag = 1;
                        //做完唤醒吃货
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
public class Demo01_3 {
    public static void main(String[] args) {
        //创建线程对象
        Cook cook = new Cook();
        Foodie foodie = new Foodie();
        //设置线程名称
        cook.setName("厨师");
        foodie.setName("吃货");
        //启动线程
        cook.start();
        foodie.start();
    }
}

6.2 线程状态

线程的状态
新建状态 (NEW )----创建线程对象
就绪状态 (RUNNABLE)----start方法
阻塞状态 (BLOCKED)----无法获得锁对象
等待状态( WAITING)----wait方法
计时等待 ( TIMED WAITING )----sleep方法
结束状态 (TERMINATED)----全部代码运行完毕

7、线程池

7.1 创建线程池

创建线程池 无上限

public class Demo01_4 {
    public static void main(String[] args) {
        //创建线程池对象 无上限
        ExecutorService pool= Executors.newCachedThreadPool();
        //提交任务
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        //销毁线程池
        pool.shutdown();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("线程池");
    }
}

创建3个线程池

public class Demo01_4 {
    public static void main(String[] args) {
        //创建3个线程池
        ExecutorService pool= Executors.newFixedThreadPool(3);
        //提交任务
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        //销毁线程池
        pool.shutdown();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("线程池");
    }
}

时间:2023-06-25 晚上

posted @   有何和不可  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示