Java基础之多线程

多线程

进程和线程

  • 进程process

    每个程序运行就会创建一个进程

    进程是由操作系统管理

    每个进程独享一段内存空间,进程之间互不干扰

  • 线程Thread

    线程是进程的组成单元

    一个进程可以创建多个线程

    多个线程之间共享同一个进程资源

    例如:一个操作比作一家工厂,进程相当于车间,线程相当于一个车间里多条生产线

并行和并发

  • 并行

    多个线程同时都在运行

  • 并发

    并行的多个线程在同一时间点访问同一个资源(执行同一个方法或访问同一个属性),产生并发可能导致数据不一致(混乱)

    例如:火车票抢票,秒杀,

  • 多线程好处

    加快程序运行速度

    防止程序阻塞

  • 多线程坏处

    产生并发现象

同步和异步

  • 同步(线程安全)

    多个线程排队执行,称为同步

    同一个时间点只有一个程序在执行

    不会产生并发

    执行效率不高(兼顾效率,只对会造成数据混乱的代码同步)

  • 异步(线程不安全)

    多个线程同时执行

    可能会导致并发

    执行效率比较高

    例如:煮饭,如果只有一个锅,只能一个菜一个菜炒,这种方式叫同步

    ​ 两个锅,一个汤锅,一个炒锅,一边炖汤,一边炒菜,这种方式叫异步

同步(线程安全)的类:String、Buffer、ConcurrenHashMap

异步(线程不安全)的类:StringBuilder、HashMap

创建和执行线程

方式一 :继承Thread类
  • 继承Thread类

  • 重写Thread类中的 run() 方法

  • 调用线程类的 start() 方法

    start() 方法是启动线程,并不一定立刻执行,什么时候执行这个线程是由进程调度的

    start() 方法相当于告诉进程,我已经准备好了

  • 不能去调用 run() 方法,如果调 run() 就没有线程效果

  • Thread1线程类

    public class Thread1 extends Thread{
    
        public Thread1(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                try {
                    //Thread.sleep();让线程休眠  单位:毫秒
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.getName() + ":" + i);
            }
        }
    }
    
  • Thread1Test测试类

    public class Thread1Test {
        public static void main(String[] args) {
    
            //创建线程
            Thread1 thread1 = new Thread1("线程1");
            Thread1 thread2 = new Thread1("线程2");
    
            //两个线程同时在执行(并行),打出来的结果 线程一  和 线程二  有交叉
            thread1.start();
            thread2.start();
    
            //不会等待上面的两个执行完之后才会执行而是用start方法启动线程后,就不管了,for循环立刻开始
            for (int i = 0; i < 20; i++) {
                System.out.println("main的主线程在执行" + i);
            }
    
    
        }
    }
    
  • Thread 类常用的方法

    /* Thread 类的方法*/
    thread1.getName();//返回线程的名称
    thread1.setName("线程名称 :");//设置线程名称
    thread1.getPriority();//返回线程的优先级(设置线程被执行的几率)
    thread1.setPriority(6);//改变线程的优先级    范围:1~10
    
方式二:实现Runnable接口
  • 实现Runnable接口

  • 创建一个Thread对象,构造方法的参数是实现了Runnable接口类的对象

  • 调用Thread类的start()方法启动接口

  • Thread2类

    public class Thread2 implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName()获取当前运行线程的名字
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    
  • 测试代码

    public static void main(String[] args) {
    
            //实现runnable接口的线程要创建Thread对象
            Thread2 t1 = new Thread2();
            Thread2 t2 = new Thread2();
            //Thread(t1).start();使用Thread类的start()方法启动线程
            new Thread(t1).start();
            new Thread(t2).start();
            //main方法也是也是一个线程,线程名叫:main
            System.out.println(Thread.currentThread().getName());
        }
    
方式三:实现Callable接口
  • 实现Callable的接口,实现call的方法

  • 创建FutureTask对象,构造方法是实现了Callable接口对象

  • 创建Thread对象,勾构造方法的参数FutureTask对象

  • 调用Thread类的start()方法启动线程

  • Thread3 implements Callable 类 实现接口

public class Thread3 implements Callable <String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
        }
        return Thread.currentThread().getName();
    }
}
  • 测试代码
public class Thread3Test {
    public static void main(String[] args) {
        Thread3 call1 = new Thread3();
        //创建FutureTask对象,构造方法的参数是实现了Callable接口的对象
        FutureTask<String> futureTask1 = new FutureTask<String>(call1);
        //创建Thread对象,勾构造方法的参数FutureTask对象
        Thread t1 = new Thread(futureTask1);
        //启动线程
        t1.start();

        Thread3 call2 = new Thread3();
        FutureTask<String> futureTask2 = new FutureTask<String>(call2);
        Thread t2 = new Thread(futureTask1);
        t2.start();
    }
}

线程的生命周期

线程的生命周期包含5个阶段,包括:新生、就绪、运行、阻塞、终止

在这里插入图片描述

  • New 新生状态

    当线程被实例化后new Thread( ), 就进入新生状态

  • Runnable 就绪状态

    当线程对象执行了start( ) 后进入就绪状态

    就绪状态的线程可以执行,但不会执行,等待线程调度触发它执行

    这时候线程处于等待CPU分配资源阶段,谁先抢到CPU资源,谁开始执行

  • Runing 运行状态

    当线程被激活执行,这时候就是运行状态,执行run() 方法

    不一定在一个执行周期能执行完成run( ) 方法,如果cpu时间片消耗完毕或者线程的yield( )方法被调用,线程就会退回就绪状态,等待下一次运行

  • Blocked 阻塞状态

    阻塞状态,暂停运行,正在运行的线程执行了sleep( )方法或wait()方法,就进入到阻塞状态

    • 因为sleep( )阻塞的线程,sleep时间到了后,重写回到就绪状态
    • 因为wait( )方法阻塞的线程,等待notify( )或者notifyAll( )被执行,才重新回到就绪状态
  • Terminated 终止状态

    终止状态,run( ) 正常执行完毕,就进入到终止状态,线程生命周期就结束

线程不安全(并发)

有一个银行账号,两个线程同时取钱,有时候可能出现余额乱套

  • Account账号类
    账号属性
    余额属性
    生成get,set方法
    生成两个参数的构造方法
public class Account {
    private String accountNu;//账号
    private double blance;//余额

    public Account() {
    }

    public Account(String accountNu, double blance) {
        this.accountNu = accountNu;
        this.blance = blance;
    }


    public void setAccountNu(double accountNu) {
        this.accountNu = accountNu;
    }

    public double getBlance() {
        return blance;
    }

    public void setBlance(double blance) {
        this.blance = blance;
    }
}
  • DrawThrerad 取钱类

    继承Thread

    有两个属性,账号和取款金额

    创建构造方法

    重写run方法,取钱逻辑放在run方法里

public class DrawThrerad extends Thread {

    private  Account account; //账号
    public double drawAmount; //取款金额

    public DrawThrerad(Account account, double drawAmount) {
        this.account = account;
        this.drawAmount = drawAmount;
    }

    @Override
    public void run() {
        //先休眠,让线程并发
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //用synchronized关键字给这段代码加锁(让这段代码变为同步的代码),小括号里面的就是加锁对象
        synchronized (account){
            //取钱金额小于等于原来的余额
            if(drawAmount <= account.getBlance()){
                System.out.println(Thread.currentThread().getName() + "ATM吐出:" + drawAmount + "元");
                //重新设置余额为原来的余额减去取款金额
                account.setBlance(account.getBlance() - drawAmount);
                System.out.println(Thread.currentThread().getName() + ", 余额是:" + account.getBlance());
            }else{
                System.out.println(Thread.currentThread().getName() + ", 余额不足,取钱失败");
            }
        }

    }
}
  • 测试类
public static void main(String[] args) {
    Account account = new Account("88888888",1000);
    DrawThrerad t1 = new DrawThrerad(account,800);
    DrawThrerad t2 = new DrawThrerad(account,600);
    t1.start();
    t2.start();
}
  • 测试结果

可能会出现(并发时产生)余额为负数的情况

在这里插入图片描述

解决线程不安全问题

把并发代码(或者是方法)添加一个同步的关键字:synchronized

在这里插入图片描述

synchronized关键字还可以用于修饰方法,如果一个方法加同步,这个方法不会出现并发调用情况

在这里插入图片描述

死锁

两个线程要相互的等到对方锁定的资源,两个线程都没有办法继续执行,程序就一直处于等待状态,不会结束,也不能成功运行,这种现象就是死锁

例如:两个人吃牛排,只有一套刀叉。A拿到叉子,B拿到刀,两个人都没有办法吃的到牛排,只能无限的等下去

  • 死锁形成的条件
    1. **互斥使用调用:**当资源被一个线程使用时,另一个线程不能使用
    2. **不可抢占:**资源请求者不能强制从资源的占用者手中夺取资源,只能由资源的占用着主动释放
    3. **请求和保持:**当资源的请求者在请求资源的时候,已经占用的资源不会释放
    4. **循环等待:**A线程持有资源1要等待资源2,B线程持有资源2要等待资源1

会死锁的代码

锁定资源的顺序相反,造成循环等待

public class DeadLock {
    public static void main(String[] args) {
        Object knife = new Object(); //刀
        Object fork = new Object(); //叉
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();//t1线程名字
//对刀加锁(同步)
                synchronized (knife){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name + ", 获取到了刀");
                    System.out.println(name + ", 等待叉子。。。");
//对叉子加锁
                    synchronized (fork){
                        System.out.println(name + ", 获得了叉");
                    }
                }
            }
        });
        t1.start();//启动第一个线程
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();//t2线程的名字
//对叉子加锁
                synchronized (fork){
                    System.out.println(name + ", 获得了叉子");
                    System.out.println(name + ", 等待刀。。。");
//对刀加锁
                    synchronized (knife){
                        System.out.println(name + ", 获得了刀");
                    }
                }
            }
        });
        t2.start();//启动第二个线程
    }
}

结果

在这里插入图片描述

避免死锁

  • 以相同的舒徐去锁定资源
  • 可以建立一个锁的对象,之只锁一个对象,不锁多个对象
    叉子加锁
                synchronized (fork){
                    System.out.println(name + ", 获得了叉子");
                    System.out.println(name + ", 等待刀。。。");
//对刀加锁
                    synchronized (knife){
                        System.out.println(name + ", 获得了刀");
                    }
                }
            }
        });
        t2.start();//启动第二个线程
    }
}

结果

在这里插入图片描述

避免死锁

  • 以相同的舒徐去锁定资源
  • 可以建立一个锁的对象,之只锁一个对象,不锁多个对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值