多线程的实现与安全问题

线程与进程

线程是什么?

  1. 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
  2. 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程

进程是什么?

​ 指一个内存中运行的应用程序,每个进程都有一个独立的内存空间

线程调度

分时调度

​ 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

抢占式调度

  1. 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java中默认使用的为抢占式调度
  2. CPU使用抢占式调度模式在多个线程间进行着告诉的切换。对于CPU的一个核心而言,只能够执行一个线程,而CPU的在多个线程间切换速度相对于我们的感觉要快很多,看上去就是在同一时刻运行。实际上,多线程程序并不能提高程序的运行速度,但是能够提高程序运行效率,让CPU的使用率更高。

同步与异步

同步:排队执行,效率低但安全。

异步:同时执行,效率高但不安全。

并发与并行

并发:指两个或多个事件在同一个时间段内发生

​ 比如:一秒内的完成了5000次下单

并行:指两个或多个事件在同一个时刻发生(同时发生)

​ 比如:在同一时刻打印了两句话

线程的创建方式

  1. 继承Thread类
  2. 实现Runnable类

继承Thread类方式

实现步骤:

  1. 创建一个类
  2. 继承Thread类
  3. 重写run()方法

代码实现

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

注意点:

​ 开启线程是调用start()方法,而不是直接调用run方法

实现Runnable接口方式

实现步骤:

  1. 创建一个类实现Runnable接口
  2. 实现run()方法

代码

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class Test {

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

Thread类常用方法

  • public static Thread currentThread()

    返回当前正在执行的线程对象的引用,返回的是一个Thread类型

  • public String getName()

    返回当前线程的名称

  • public void interrupt()

    中断线程:如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,并抛出 nterruptedException异常。

  • public static void sleep(long millis)

    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。

  • publilc void setDaemon(boolean on)

    将该线程标记为守护线程或用户线程。

  • public void setName(String name)

    改变线程名称,使之与参数 name 相同。

  • public void setPriority(int newPriority)

    更改线程的优先级。

    优先级在1-10之间 MIN_PRIORITY:1 MAX_PRIORITY:10

线程安全问题

当两个或两个以上的线程去操作同一个变量时,就会出现不安全的问题

问题描述:

​ 现在有A、B两个线程、变量num=100,当A线程拿到了num,并且此时num=100,但是这个时候,B线程来了,同时也拿到了num,此时num=100,但是B线程抢先一步A线程把num变成了99,而A拿到的num仍然是100,这个时候A拿这个值为100的num去操作就会出现数据不安全的问题,因为这个时候num应该为99,A线程应该拿num=99这个值取进行别的操作。

  • 演示问题
public class SynchronizedDemo {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();

    }
}
public class Ticket implements Runnable{

    public int num = 100;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (num>0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
                System.out.println("剩余票数:"+num);
            }
        }
    }
}
  • 运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GxEOZzrq-1665656449414)(assets/image-20211120163717655.png)]

结果:出现重复值,理论上每个值都只出现一次,并且当票数等于0时,就不再继续打印

解决方法

  1. 同步代码块
  2. 同步方法
  3. 同步锁Lock
同步代码块

代码实现

public class Ticket implements Runnable{

    /**
     * 现在有一百张车票
     */
    public int num = 100;

    @Override
    public void run() {
            for (int i = 0; i < 100; i++) {
                //在此添加
                synchronized (this) {
                    if (num>0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                    System.out.println(Thread.currentThread().getName()+"线程卖出一张票,剩余票数:"+num);
                }
            }
        }
    }
}
  • 运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qw2WoO1f-1665656449415)(assets/image-20211120164234845.png)]

发现没有重复被消费的问题

同步方法

在需要的方法上加synchronized修饰符即可

此处需要新建一个sale()方法,在此方法上加synchronized

如果在run在加那么整个卖票操作就会被一个线程执行完,无法起到多条线程同时出售的效果

public class Ticket implements Runnable{

    public int num = 100;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            sale();
        }
    }

    public synchronized void sale(){
        if (num>0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"线程正在卖票,剩余票数:"+num);
        }
    }
}
同步锁Lock

此处为了规范,加入了try catch代码块,lock.lock()方法之后紧跟try代码块,lock.unlock()在finally中释放

获取Lock的方法,Lock lock = new ReentrantLock();

lock.lock()表示上锁

lock.unlock()表示释放锁

public class Ticket implements Runnable{

    public int num = 100;

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            sale();
        }
    }

    public void sale(){
        lock.lock();
        try {
            if (num>0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
                System.out.println(Thread.currentThread().getName()+"线程正在卖票,剩余票数:"+num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

公平锁与非公平锁

  • 公平锁

    依次排队执行,先到先得

    实现方式:创建Lock时添加参数true

    ​ Lock lock = new ReentrantLock(true);

  • 非公平锁

    谁抢到谁就先执行,以上的三种解决安全问题的方法都是非公平锁

线程死锁

假如有A、B两个线程,线程A需要等待线程B回应才可执行,线程B也要等线程A回应才可执行,此时就僵持住了,也就造成了线程死锁

解决方式:不要使用可以带锁的线程,去调用另一个会带锁的线程

多线程通信问题

生产者与消费者案例

需求:生产者生产一份食物,消费者就消费一份食物。生产者生产时,消费者不能消费。消费者需要等生产者生产完毕之后才能消费。

有问题的代码

public class Demo {
    public static void main(String[] args) {
        Food food = new Food();
        new Consumer(food).start();
        new Product(food).start();
    }
    static class Consumer extends Thread{
        private Food food;
        public Consumer(Food food) {
            this.food=food;
        }
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                food.get();
            }
        }
    }
    static class Product extends Thread{
        private Food food;
        public Product(Food food) {
            this.food=food;
        }
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i%2==0) {
                    food.setNameAndTaste("小鸡炖蘑菇","甜甜");
                } else {
                    food.setNameAndTaste("镇江牛肉面","香辣");
                }
            }
        }
    }
    static class Food{
        private String name;
        private String taste;
        public void setNameAndTaste(String name,String taste){
            this.name=name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste=taste;
        }
        public void get(){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费者消费了:"+name+",味道为:"+taste);
        }
    }
}

  • 运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5bX1Mbkm-1665656449416)(assets/image-20211120172029382.png)]

问题描述:

​ 这种方式导致了生产时没生产完成,消费者就消费了。

​ 即生产小鸡炖蘑菇时,上一份的镇江牛肉面还没生产完成,此时菜名name=“小鸡炖蘑菇”,味道taste=“香辣”,在这个瞬间,去调用get方法,就会出现以上运行的情况

解决了问题的代码

public class Demo {
    public static void main(String[] args) {
        Food food = new Food();
        new Consumer(food).start();
        new Product(food).start();
    }
    static class Consumer extends Thread{
        private Food food;
        public Consumer(Food food) {
            this.food=food;
        }
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                food.get();
            }
        }
    }
    static class Product extends Thread{
        private Food food;
        public Product(Food food) {
            this.food=food;
        }
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i%2==0) {
                    food.setNameAndTaste("小鸡炖蘑菇","甜甜");
                } else {
                    food.setNameAndTaste("镇江牛肉面","香辣");
                }
            }
        }
    }
    static class Food{
        private String name;
        private String taste;
        //表示此时生产者可以生产
        private boolean flag=true;
        public synchronized void setNameAndTaste(String name,String taste) {
            //判断是否可生产
            if (flag) {
                this.name=name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste=taste;
                //此时已有产品,生产者不可生产
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            //判断是否可消费,生产者不可生产时消费则就可以消费
            if (!flag) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者消费了:"+name+",味道为:"+taste);
                //此时产品已被消费,生产者可生产
                flag=true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-milHTayt-1665656449417)(assets/image-20211120173647695.png)]

线程池

缓存线程池

使用 Executors.newCachedThreadPool() 创建一个线程池对象

使用execute方法添加任务并自动执行

public class ExecutorDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了");
            }
        });

    }
}

定长线程池

使用Executors.newFixedThreadPool()创建一个线程池对象

使用execute方法添加任务并自动执行

public class ExecutorDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了");
            }
        });
    }
}

单线程线程池

线程池中只有一个线程,每次执行都是相同的线程

Executors.newSingleThreadExecutor();

public class ExecutorDemo {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"执行了");
            }
        });
    }
}

周期定长线程池

public class ExecutorDemo {

    public static void main(String[] args) {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
        //定时执行一次
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行了 只执行一次");
            }
        },1, TimeUnit.SECONDS);

        //定时多久执行一次
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行了 每秒一次");
            }
        },2,1, TimeUnit.SECONDS);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值