java学习第16天

java学习第16天

多线程

进程和线程

  • 进程Process

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

    进程是由操作系统管理

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

  • 线程Thread

    线程是进程的组成单元

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

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

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

并行和并发

  • 并行

    多个线程是同时都在运行,称为并行

  • 并发

    并行的多个线程在同一时间点访问同一个资源(比如执行同一个方法,访问同一个属性),产生并发。

    并发可能会导致数据不一致(数据混乱)

    例如:火车票抢票;秒杀活动

多线程的好处:

  1. 加快程序运行的速度
  2. 防止程序的阻塞

多线程的坏处:

  1. 产生并发现象

同步和异步

  • 同步(线程安全)

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

    同一个时间点只有一个线程执行

    不会导致并发

    执行的效率不高(为了兼顾效率,一般只对会造成数据不一致的代码(线程)同步)

  • 异步(非线程安全)

    多个线程同时运行

    可能会导致并发

    执行效率比较高

例如:煮饭,如果只有一口锅,一个菜一个菜的炒,这种执行方式是同步

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

同步(线程安全):StringBuffer、ConcurrentHashMap

异步(非线程安全):StringBuilder,HashMap

创建和执行线程

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

  • 重写run方法

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

    start() 方法启动线程,并不一定会立即执行。什么时候执行这个线程,是由进程调度的。start()方法相当于告诉进程,我已经准备好执行了

    千万不要调run()方法,如果调run(),方法就没有多线程的效果。

ThreadDemo1

package com.Hqyj.Thread;

public class ThreadDemo1 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //sleep()让程序暂停一段时间,单位是毫秒
            /*try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            System.out.println("线程"+ getName() +"正在运行"+ i);
        }
    }

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

  • ThreadDemoTest
package com.Hqyj.Thread;

public class ThreadDemo1Test {
    public static void main(String[] args) {
        ThreadDemo1 thread1 = new ThreadDemo1("线程1");
        ThreadDemo1 thread2 = new ThreadDemo1("线程2");
        //两个线程同时执行(并行),所以打出来的结果线程1和线程2有交叉的现象
        thread1.start();
        thread2.start();

        //不会等待上面的两个线程执行万之后在执行,而是用start方法启动线程之后,就不管了,for循环立即执行
        for (int i = 0; i < 200; i++) {
            System.out.println("主线程正在运行"+ i);
        }
    }
}

  • Thread类常用的方法

    /**
             * Thread类的方法
             */
            thread1.getName();//返回线程的名称
            thread1.setName("线程名称");//设置线程的名称
            thread1.getPriority();//返回线程的优先级(设置线程被执行的几率)
            thread1.setPriority(1);//设置线程的优先级,优先级1-10
    
方式二、实现Runnable接口
  • 实现Runnable接口,实现run方法
  • 创建一个Thread对象,构造方法的参数是实现了Runnable接口类的对象
  • 调用Thread对象的start方法启动

Thread2类

package com.Hqyj.Thread;

public class Thread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //Thread.currentThread()  获取到当前线程的对象
            System.out.println("线程"+ Thread.currentThread().getName() +"正在运行"+i);
        }
    }
}

测试代码

package com.Hqyj.Thread;

public class Thread2Test {
    public static void main(String[] args) {

        //实现runnable接口的线程,要创建Thread对象,才能启动线程
        Thread2 t1 = new Thread2();
        Thread2 t2 = new Thread2();
        new Thread(t1).start();//运行方式
        new Thread(t2).start();

        //main方法也是一个线程,线程名称叫:main
        System.out.println(Thread.currentThread().getName());
    }
}

方式三:实现Callable接口
  • 实现Callable接口,实现call方法
  • 创建FutureTask对象,构造方法是实现了Callable接口对象
  • 创建Thread对象,构造方法的参数是FutureTask对象
  • 调用start方法启动线程

Thread3 implement Callable

package com.Hqyj.Thread;

import java.util.concurrent.Callable;

public class Thread3 implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(2);
            System.out.println("线程"+Thread.currentThread().getName()+"正在运行"+i);
        }

        return Thread.currentThread().getName();
    }
}

测试代码

package com.Hqyj.Thread;

import java.util.concurrent.FutureTask;

public class ThreadTest {
    public static void main(String[] args) {
        Thread3 call1 = new Thread3();
        //创建FutureTask对象,构造方法的参数是实现了Callable接口的对象
        FutureTask<String> ft1 = new FutureTask<>(call1);
        //创建Thread对象,构造方法的参数FutureTask对象
        Thread t1 = new Thread(ft1);
        //启动线程
        t1.start();

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

线程的生命周期

在这里插入图片描述

  • New 新生状态

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

  • Runnable 就绪状态

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

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

  • Running 运行状态

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

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

  • Blocked 阻塞状态

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

    因为sleep()阻塞的线程,sleep()时间到了后,重新回到就绪状态

    因为wait()方法阻塞的线程,就要等待notify()或notifyAll()被执行,才重新回到就绪状态。

  • Terminated 终止状态

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

线程不安全(并发)

假设有一个银行账号,有两个线程同时取钱,有可能出现余额为负的情况。

  • Account 账号类

    账号属性

    余额属性

    相应的get、set方法

    构造方法

    package com.Hqyj.Test;
    
    public class Account {
        private String accountnum;//账号
        private double blance;//余额
    
        public Account() {
        }
    
        public Account(String accountnum, double blance) {
            this.accountnum = accountnum;
            this.blance = blance;
        }
    
        public String getAccountnum() {
            return accountnum;
        }
    
        public void setAccountnum(String accountnum) {
            this.accountnum = accountnum;
        }
    
        public double getBlance() {
            return blance;
        }
    
        public void setBlance(double blance) {
            this.blance = blance;
        }
    }
    
    
  • DrawThread 取钱类

    继承Thread

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

    创建构造方法

    重写run()方法,取钱的逻辑方法run()方法里。

    package com.Hqyj.Test;
    
    public class DrawThread extends Thread{
        private Account account;//账号
        private double drawAmount;//取款金额
        public DrawThread(Account account,double drawAmount){
            this.account = account;
            this.drawAmount = drawAmount;
        }
    
        @Override
        public void run() {
            //取款金额小于等于账号金额
            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("余额不足,取款失败!");
            }
        }
    }
    
    
  • 测试类

    package com.Hqyj.Test;
    
    public class DrawThreadTest {
        public static void main(String[] args) {
            Account account = new Account("88888888", 100000);
            DrawThread t1 = new DrawThread(account,100000);
            DrawThread t2 = new DrawThread(account,100000);
            t1.start();
            t2.start();
    
        }
    }
    
    
  • 测试结果

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

    Thread-0,ATM吐出100000.0Thread-1,ATM吐出100000.0Thread-0,余额是:0.0
    Thread-1,余额是:-100000.0
    
解决线程不安全的问题

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

在这里插入图片描述

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

private synchronized void draw(Account account,Double amount){
        
    }
死锁

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

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

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

会形成死锁的代码

锁定资源的顺序相反,就死锁

package com.Hqyj.Thread;

public class DeadLock {
    public static void main(String[] args) {
        Object knife = new Object();//刀
        Object fork = new Object();//叉
        Thread t1 = new Thread(() ->{
            //t1线程名字
            String name = Thread.currentThread().getName();
            //对刀加锁(同步)
            synchronized (knife){
                System.out.println(name+"获取到了刀");
                System.out.println(name+ "等待叉。。。");
                //对叉加锁(同步)
                synchronized (fork){
                    System.out.println(name+"获取到了叉");
                }
            }
        });
        t1.start();//启动第一个线程
        Thread t2 = new Thread(() ->{
            //t2线程名字
            String name = Thread.currentThread().getName();
            //对叉加锁(同步)
            synchronized (fork){
                System.out.println(name+"获取到了叉");
                System.out.println(name+ "等待刀。。。");
                //对刀加锁(同步)
                synchronized (knife){
                    System.out.println(name+"获取到了刀");
                }
            }
        });
        t2.start();//启动第二个线程
    }
}

结果:

Thread-1获取到了叉
Thread-0获取到了刀
Thread-1等待刀。。。
Thread-0等待叉。。。
避免死锁
  • 以相同的顺序锁定资源
  • 另外建立一个锁的对象,只锁一个对象,不锁多个对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值