Java基础(十七)线程基本概念、创建多线程的方式多线程及并发安全问题的解决方式

本文深入探讨了多线程的基本概念,包括进程与线程的区别,多线程的创建方式,以及多线程在提高CPU利用率方面的作用。文章详细介绍了多线程并发安全问题及其解决方案,如加锁机制,并通过实例演示了生产者-消费者模型,揭示了等待唤醒机制的运用。此外,还讨论了线程状态、优先级、死锁检测、等待与睡眠方法的区别,以及守护线程的概念。

线程

1.概念

进程:操作系统上应用程序拆分成多个小任务
线程:进程拆分成多个小任务

在某个时刻,CPU的某个核中只能执行一个进程,但是进程可以拆分成多个线程,只能执行其中一个线程,CPU只能执行一个线程,CPU会切换执行线程

2.多线程的好处

线程要么和CPU进行交互,要么和硬件进行交互,当线程在和硬件进行交互的时候CPU处于空闲时间,引入多线程提高CPU的利用率(理论上可以提高到100%)

3.创建多线程的方式

1.继承Thread类重写run方法(线程执行任务信息),创建描述线程执行信息的类的对象,调用父类的start方法来开启线程
package cn.tedu.thread;

public class ThreadDemo {
    public static void main(String[] args) {
        //创建描述线程执行信息的类对象
        TDemo t=new TDemo();
        //调用父类的start方法来开启线程
        //线程不会回头执行
        t.start();

        //主线程
        for (int i=20;i>=0;i--){
            System.out.println("main:"+i);

            //线程休眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

//定义类---代表线程的任务信息
//单继承限制不能继承别的类
class TDemo extends Thread{
    //重写run方法---存放线程执行的任务信息
    @Override
    public void run(){
        for (int i=0;i<=20;i++){
            System.out.println("run:"+i);

            //线程休眠
            try {
                //休眠10毫秒
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
2.实现Runnable接口,重写run方法(线程执行任务信息),由Runnable实现类对象来构建Thread类的对象来调用start方法开始线程
package cn.tedu.thread;

public class RunnableDemo {
    public static void main(String[] args) {
        //创建代表Runnable执行信息的任务类对象
        RDemo r=new RDemo();
        //创建Thread对象
        //Thread类和RDemo类都是Runnable的实现类
        //同类对象给本类对象增强功能---装饰者模式
        Thread t=new Thread(r);
        //调用start方法
        t.start();//开启线程
        
        //主线程
        for (int i=20;i>=0;i--){
            System.out.println("main:"+i);

            //休眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//定义类---代表线程执行任务信息
//类与接口是多实现,不影响实现别的接口
class RDemo implements Runnable{
    //重写方法---线程执行任务信息
    @Override
    public void run(){
        for (int i=0;i<=20;i++){
            System.out.println("run:"+i);

            //休眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

多线程之间存在相互抢占(CPU执行-随机抢占),抢占发生在代码的每一步,产生错误的数据问题,导致多线程数据并发安全问题

重复:
重复在这里插入图片描述
跳过:
在这里插入图片描述
负数:
在这里插入图片描述

4.多线程并发安全问题解决方式–加锁

1.同步代码块锁

Synchronized(锁对象){}
根据锁对象共享进来的线程对象保证在执行代码块内容时不会有抢占
锁对象:一个对象,可以把哪些线程对象共享进来
可以把当前参与的线程对象共享进来的对象
方法区资源(把所有的线程对象都共享)
this(当参与进来的线程对象被同一个Runnable实现类对象共享时)

2.同步方法锁

根据锁对象共享进来的线程对象保证在执行方法里内容不会有抢占

锁对象:
如果是非静态方法,默认锁对象就是this
如果是静态方法,默认的锁对象就是当前类.class(方法区资源)

在这里插入图片描述

package cn.tedu.thread;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

public class SellerTicketDemo1 {
    public static void main(String[] args) throws IOException {
        //加载配置文件
        Properties p=new Properties();
        p.load(new FileInputStream("ticket.properties"));
        //根据键来获取值
        String count=p.getProperty("count");
        //创建代表票的类对象
        Ticket t=new Ticket();
        //设置初始票数
        t.setCount(Integer.parseInt(count));
        //创建代表线程执行信息的类对象---四个售票员对象
        Seller s1=new Seller(t);
        Seller s2=new Seller(t);
        Seller s3=new Seller(t);
        Seller s4=new Seller(t);

        //创建四个线程对象
        //指定线程对象名称为A、B、C、D
        Thread t1=new Thread(s1,"A");
        Thread t2=new Thread(s2,"B");
        Thread t3=new Thread(s3,"C");
        Thread t4=new Thread(s4,"D");

        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

}

//定义类---代表线程执行的任务信息(卖票)
//代表售票员的类
class Seller implements Runnable{
    //代表票数---static保证共卖100张票
    //static int count=100;

    //声明代表票的类的对象
    private Ticket t;

    //有参构造---保证创建的对象共享同一个t对象
    public Seller(Ticket t){
        this.t=t;
    }
    //重写run方法---卖票过程
    @Override
    public void run() {
        //循环实现
        while (t.getCount()>0){
            //卖一张票---设置新的剩余票数
            t.setCount(t.getCount()-1);
            //输出
            //Thread.currentThread()---当前正在执行的线程对象
            System.out.println(Thread.currentThread().getName()
                    +"卖了一张票,还剩余"+t.getCount()+"张票");
        }

    }
}

//代表票的类
class Ticket{
    //代表票数
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

同步:在某个时刻只能由一个线程对象拥有资源(没有抢占 )
异步:一个资源会被多个线程来抢占

同步一定是安全的,异步不一定不安全,不安全的一定是异步

5.死锁:由于锁的嵌套导致死锁问题,通过死锁检测来让其中一个线程对象先执行

package cn.tedu.thread;

public class DeadLockDemo {
    //创建打印和扫描的对象
    private static Scann s=new Scann();
    private static Printer p=new Printer();

    public static void main(String[] args) {
        //员工---先打印再扫描
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (p){
                    p.print();
                    //休眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s){
                        s.scan();
                    }
                }

            }
        });
        //员工---先扫描再打印
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                //
                synchronized (s){
                    s.scan();
                    //休眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (p){
                        p.print();
                    }
                }
            }
        });
        
        //开启线程
        t1.start();
        t2.start();
    }
}
//定义代表扫描的类
class Scann{
    public void scan(){
        System.out.println("在呼哧呼哧的扫描。。。");
    }
}
//定义代表打印的类
class Printer{
    public void print(){
        System.out.println("在哼哧哼哧的打印。。。");
    }
}

案例:消费生产模型
每次生产一个随机数量的产品,保证每次生产之后剩余的商品数量不能超过1000.每次消费一个随机数据的产品,消费最大值是把总的剩余商品数量。生产和消费交替出现

234334
30034
900934
834100
package cn.tedu.thread;

public class XiaoFeiText {
    public static void main(String[] args) {
        //商品类对象
        Pruduct p=new Pruduct();
        //创建线程对象
        new Thread(new Pruductor(p)).start();//生产者
        new Thread(new Consumer(p)).start();//消费者
    }

}
//定义类---代表线程执行的任务信息(生产者)
class Pruductor implements  Runnable {
    //声明商品类的对象
    private Pruduct p;

    //有参构造---保证共享同一个商品类对象
    public Pruductor(Pruduct p) {
        this.p = p;
    }

    //生产过程
    @Override
    public void run() {
        //保证一直生产下去
        while (true) {
            synchronized (p) {
                if (p.getFlag()==true)
                //让线程对象进行等待
                try {
                    p.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //可以生产的最大值
                int max = 1000 - p.getCount();
                //随机生产的商品数
                int count = (int) (Math.random() * (max + 1));
                //设置新的剩余的商品数量
                p.setCount(p.getCount() + count);
                //输出
                System.out.println("生产" + count + "个商品,还剩" + p.getCount() + "个商品……");
                //唤醒等待的线程对象
                p.notify();
                //改变布尔值
                p.setFlag(true);
            }
        }
    }
}

//定义类--代表线程执行的任务信息(消费者)
class Consumer implements  Runnable{
    //声明商品类的对象
    private Pruduct p;

    //有参构造---保证共享同一个商品类对象
    public Consumer(Pruduct p){
        this.p=p;
    }
    //指定消费过程
    @Override
    public void run() {
            //保证一直消费下去
            while (true) {
                synchronized (p) {
                if (p.getFlag()==false)
                    //让线程对象进行等待
                    try {
                     p.wait();
                    } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                //随机消费的商品数量
                int count = (int) (Math.random() * (p.getCount() + 1));
                //设置新的剩余的商品数量
                p.setCount(p.getCount() - count);
                //输出
                System.out.println("消费了" + count + "个商品,还剩" + p.getCount() + "个商品……");
                //唤醒等待的线程对象
                p.notify();
                //改变布尔值
                p.setFlag(false);
            }
        }
    }
}

//代表商品的类
class Pruduct{
    //属性---商品数量
    private int count;
    //标志位
    private boolean flag;

    public boolean getFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}

6.等待唤醒机制(结合所来使用)

通过wait()、notify()、notifyAll()以及标志位来控制线程对象执行顺序

package cn.tedu.thread;

public class NotifyAllWaitDemo {
    public static void main(String[] args) {
        //
        //创建代表商品类对象
        Pruduct p=new Pruduct();

        //
        new Thread(new Pruductor1(p)).start();
        new Thread(new Pruductor1(p)).start();
        new Thread(new Pruductor1(p)).start();
        new Thread(new Consumer1(p)).start();
        new Thread(new Consumer1(p)).start();
        new Thread(new Consumer1(p)).start();


    }
}

//代表生产者---描述生产过程
class Pruductor1 implements Runnable{

    //声明商品类的对象
    Pruduct p;

    //通过有参构造传入商品类对象
    public Pruductor1(Pruduct p){
        this.p=p;
    }

    //重写方法---详细生产的过程
    @Override
    public void run() {
        //保证一直生产
        while (true){

            //加锁
            synchronized (p){

                //线程等待
                //保证每次都能进行判断
                while (p.getFalg()==false)
                    try {
                        p.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                //此次最大生产商品数量
                int max=1000-p.getCount();
                //随机生产商品数量
                int count=(int) (Math.random()*(max+1));
                //设置新的剩余商品数量
                p.setCount(p.getCount()+count);
                //输出
                System.out.println("生产了"+count+"个商品,还剩余"
                        +p.getCount()+"个商品。。。");

                //唤醒线程
                //p.notify();
                //唤醒所有线程对象
                p.notifyAll();
                //改变布尔值
                p.setFalg(false);
            }



        }
    }
}




//代表消费者---描述消费过程
class Consumer1 implements Runnable{

    //声明商品类的对象
    Pruduct p;

    //通过有参构造传入商品类对象
    public Consumer1(Pruduct p){
        this.p=p;
    }

    //重写方法---详细消费的过程
    @Override
    public void run() {
        //保证一直消费
        while (true){

            //加锁
            synchronized (p){

                //线程等待
                while (p.getFalg()==true)
                    try {
                        p.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                //随机消费商品数量
                int count=(int) (Math.random()*(p.getCount()+1));
                //设置新的剩余商品数量
                p.setCount(p.getCount()-count);
                //输出
                System.out.println("消费了"+count+"个商品,还剩余"
                        +p.getCount()+"个商品。。。");

                //唤醒等待线程对象
               // p.notify();
                //唤醒所有线程对象
                p.notifyAll();
                //改变布尔值
                p.setFalg(true);
            }



        }
    }
}

7.wait()与sleep()方法的区别:

sleep():使用时一定要给定休眠时间,到点自然醒。在休眠期间会释放线程对象的CPU的执行权(无论有锁没锁都会释放执行权但是如果有锁不会释放锁对象)。定义在Thread类里静态方法。
wait():使用时可以给定等待时间到点自然醒,但是如果没有指定等待时间需要强制进行唤醒。在等待期间释放线程对象的CPU执行权,释放锁对象。定义在Object类里。

8.线程状态

在这里插入图片描述

9.优先级:(一定程度上避免死锁的发生)

从1到10,优先级越大理论上抢占资源概率越高
如果优先级之差大于5抢占概率就会提高一点

package cn.tedu.thread;

public class PriorityDemo {
    public static void main(String[] args) {
        //创建线程对象
        Thread t1=new Thread(new PDemo(),"A");
        Thread t2=new Thread(new PDemo(),"B");

        //设置优先级
        t1.setPriority(1);
        t2.setPriority(10);

        //开启线程
        t1.start();
        t2.start();
    }
}


//代表线程执行信息的类
class PDemo implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<=20;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);

            //休眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

10.守护线程

不是守护线程就是被守护线程,当被守护线程执行完毕时其他守护线程都要随之结束
被守护线程可以有多个,当所有的被守护线程执行完毕其他守护线程随之结束

package cn.tedu.thread;

public class DeamonDemo {
    public static void main(String[] args) throws InterruptedException {
        //创建线程对象
        Thread t1=new Thread(new Soldier(),"宫本");
        Thread t2=new Thread(new Soldier(),"亚索");
        Thread t3=new Thread(new Soldier(),"劫");
        Thread t4=new Thread(new Soldier(),"提莫");

        //设置守护线程
        t1.setDaemon(true);
        t2.setDaemon(true);
        t3.setDaemon(true);
        t4.setDaemon(true);

        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();

        //BOSS---主线程-----被守护线程
        for(int i=20;i>=0;i--){
            System.out.println("BOSS掉了一滴血,还剩"+i+"滴血");
            //休眠
            Thread.sleep(5);
        }
    }
}


//代表小兵的类
class Soldier implements Runnable{
    @Override
    public void run() {
        for (int i=10;i>=0;i--){
            //
            System.out.println(Thread.currentThread().getName()+"掉了一滴血,还剩"+i+"滴血。。。");
            //休眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张艳霞zhangyx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值