【无标题】Java多线程

程序

是为完成特定任务、用某种语言编写好的代码。

我们电脑上会安装很多的应用程序比如Notepad++,微信,QQ,Word,PPT,Excel等。

进程

是一个正在运行的程序

线程的概念

线程是进程的执行单元 就是用来执行代码的

  • 一个进程最少有一个线程
  • 一个进程也可有多个线程,称为多线程程序

了解并发和并行

并行:

多个事件在同一时刻,同时执行

并发:

多个事件在同一时间段,交替执行

计算机中的单核CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码。

多线程运行原理

CPU在多个线程间快速切换, 造成"同时"运行的假象

多线程的创建

多线程的创建方式一:继承Thread

定义一个子类MyThread继承线程类java.lang.Thread,重写run()

创建MyThread类的对象

调用线程对象的start()方法启动线程(启动后还是执行run方法的)

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("正在执行子线程run方法" + i);

        }
    }
}
MyThread myThread = new MyThread();
myThread.start();

方式一优缺点

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展,没有返回值。

多线程的注意事项

1、启动线程必须是调用start方法,不是调用run方法。

2、不要把主线程任务放在启动子线程之前。

直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。

只有调用start方法才是启动一个新的线程执行。

这样主线程一直是先跑完的,相当于是一个单线程的效果了。

多线程的创建方式二:实现Runnable接口

1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

2、创建MyRunnable任务对象

3、把MyRunnable任务对象交给Thread处理。

 4、调用线程对象的start()方法启动线程

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("正在执行Runnable的Run方法");
    }
}
public static void main(String[] args) {
   //写法一
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();

    //写法二
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("666");
        }
    }).start();

    //写法三
    new Thread(() -> {
        System.out.println("777");
    }).start();
}

方式二的优缺点

优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。

缺点:需要多一个Runnable对象,没有返回值。

多线程的第三种创建方式:利用Callable接口、FutureTask类来实现。

1、 创建任务对象

        定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。

        把Callable类型的对象封装成FutureTask(线程任务对象)。

2、把线程任务对象交给Thread对象。

3、调用Thread对象的start方法启动线程。

4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("正在执行Callable的call方法");
        return "666";
    }
}
public static void main(String[] args) throws Exception {
    MyCallable myCallable = new MyCallable();
    FutureTask<String> futureTask = new FutureTask<>(myCallable);
    Thread thread = new Thread(futureTask);
    thread.start();
    Object o =  futureTask.get();
    System.out.println(o);
}

线程创建方式三的优缺点

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。

缺点:编码复杂一点。

线程操作相关方法

常用方法补充:

wait() sleep() 的区别?
1. 来自不同的类
wait(): 来自 Object 类;
sleep(): 来自 Thread 类;
2. 关于锁的释放:
wait(): 在等待的过程中会释放锁
sleep(): 在等待的过程中不会释放锁
3. 使用的范围:
wait(): 必须在同步代码块中使用;
sleep(): 可以在任何地方使用;
4. 是否需要捕获异常
wait(): 不需要捕获异常;
sleep(): 需要捕获异常;

线程安全

什么是线程安全问题?

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题

例如:

取钱的线程安全问题

场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?

public class Account { // 账户
    private String id; // 卡号
    private double balance; // 余额

    // 取钱的方法
    public void drawMoney(double money) {
            // 1. 先判断取的钱够不够
            if (balance >= money) {
                String name = Thread.currentThread().getName();
                // 2. 取钱
                System.out.println(name + "取出了" + money + "元");
                // 3. 更新余额
                balance -= money;
                System.out.println("当前余额为:" + balance);
            } else {
                System.out.println("您的余额不足");
            }
        }
 }
public class UserThread extends Thread{

    private Account account;
    public UserThread(Account account ){ // 用于接收一个账户信息
        this.account = account;
    }


    @Override
    public void run() {
        account.drawMoney(100000);
    }
}
public static void main(String[] args) {
    Account account = new Account("vip0001" , 100000);

    UserThread xiaoMing = new UserThread(account);
    xiaoMing.setName("小明");
    UserThread xiaoHong = new UserThread(account);
    xiaoHong.setName("小红");
    xiaoMing.start();
    xiaoHong.start(); //balance = -100000.0

}

线程同步

让多个线程实现先后依次访问共享资源,这样就解决了安全问题

线程同步的常见方案

加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能

再加锁进来。

同步代码块

作用:访问共享资源的核心代码给上锁,以此保证线程安全。

synchronized(同步锁) {
    访问共享资源的核心代码
}

public void drawMoney(double money) {
    synchronized (this) { // 一般,对于实例方法用 this   静态方法用  类名.class
        // 1. 先判断取的钱够不够
        if (balance >= money) {
            String name = Thread.currentThread().getName();
            // 2. 取钱
            System.out.println(name + "取出了" + money + "元");
            // 3. 更新余额
            balance -= money;
            System.out.println("当前余额为:" + balance);
        } else {
            System.out.println("您的余额不足");
        }
    }
}

原理每次只允许一个线程加锁后进入执行完毕后自动解锁,其他线程才可以进来执行。

 同步锁的注意事项

对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug

同步方法

把访问共享资源的核心方法给上锁,以此保证线程安全

修饰符 synchronized 返回值类型 方法名称(形参列表) {
  操作共享资源的代码
}

public synchronized void drawMoney(double money) { //默认 锁对象 实例方法是this       静态方法是 类名.class 字节码对象
        // 1. 先判断取的钱够不够
        if (balance >= money) {
            String name = Thread.currentThread().getName();
            // 2. 取钱
            System.out.println(name + "取出了" + money + "元");
            // 3. 更新余额
            balance -= money;
            System.out.println("当前余额为:" + balance);
        } else {
            System.out.println("您的余额不足");
        }
    }

原理每次只能一个线程进入执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法底层原理

同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

如果方法是实例方法:同步方法默认用this作为的锁对象。

如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

Lock锁

Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

private final ReentrantLock lock = new ReentrantLock();
public void drawMoney(double money) {

    try {
        lock.lock();
        // 1. 先判断取的钱够不够
        if (balance >= money) {
            String name = Thread.currentThread().getName();
            // 2. 取钱
            System.out.println(name + "取出了" + money + "元");
            // 3. 更新余额
            balance -= money;
            System.out.println("当前余额为:" + balance);
        } else {
            System.out.println("您的余额不足");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

线程通信

当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

线程通信的常见模型(生产者与消费者模型

生产者线程负责生产数据

消费者线程负责消费生产者生产的数据。

注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

Object类的等待和唤醒方法:

public class Desk {
    // 使用一个集合用于存放包子
    private ArrayList<String> list = new ArrayList<>();
    private int index;

    // 定义一个方法,用于生产包子
    public synchronized void create() {
        String name = Thread.currentThread().getName();
        if (list.size() == 0) {
            index++;
            System.out.println(name + "生产了第" + index + "个包子");
            list.add("第" + index + "个包子");
            this.notifyAll();
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } else {
            this.notifyAll();
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    // 定义一个方法,用于吃包子
    public synchronized void eat() {
        String name = Thread.currentThread().getName();
        if (list.size() == 1) {
            String s = list.get(0);
            System.out.println(name + "吃掉了" + s);
            list.remove(0);
            this.notifyAll();
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } else {
            this.notifyAll();
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
 

 public static void main(String[] args) {
        // 1.先创建一个桌子
        Desk desk = new Desk();

        // 2.创建3个厨师【线程】
        new Thread(() -> {
            while (true) {
                desk.create();
            }
        }, "厨师1号").start();

        new Thread(() -> {
            while (true) {
                desk.create();
            }
        }, "厨师2号").start();

        new Thread(() -> {
            while (true) {
                desk.create();
            }
        }, "厨师3号").start();


        // 3.创建2个吃货【线程】

        new Thread(() -> {
            while (true) {
                desk.eat();
            }
        }, "吃货1号").start();

        new Thread(() -> {
            while (true) {
                desk.eat();
            }
        }, "吃货2号").start();
    }

线程的生命周期

java总共定义了6种状态

6种状态都定义在Thread类的内部枚举类中

public class Thread{

     ...
     public enum State {
   
  NEW,
   
  RUNNABLE,
   
  BLOCKED,
   
  WAITING,
   
  TIMED_WAITING,
   
  TERMINATED;
    }

     ...

}

 

线程池

每次创建线程存在的问题

1、每个线程在创建和销毁时都要写占用CPU,内存等资源。

2、在并发系统中,频繁创建和销毁大量线程, 对性能影响很大,甚至会导致系统资源耗尽的风险。

线程池介绍

线程池是一个容器,可以保存一些长久存活的线程对象,负责创建、复用、管理线程。

线程池的优势

降低资源消耗,重复利用线程池中线程,不需要每次都创建、销毁 。

便于线程管理,线程池可以集中管理并发线程的数量。

Executors工具类创建线程池【不使用】

JDK 1.5起提供了线程池,Executors类是线程池的工具类,通过Executors工具类可以创建线程池

ExecutorService:真正的代表线程池的接口。常见实现类ThreadPoolExecutor

ExecutorService线程池中的常用方法

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "执行了!";
    }
}
public static void main(String[] args) throws Exception {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);

    MyCallable m1 = new MyCallable();

    Future<String> future1 = threadPool.submit(m1);
    System.out.println(future1.get());

    Future<String> future2 = threadPool.submit(m1);
    System.out.println(future2.get());

    Future<String> future3 = threadPool.submit(m1);
    System.out.println(future3.get());

    Future<String> future4 = threadPool.submit(m1);
    System.out.println(future4.get());

    threadPool.shutdown();

//pool-1-thread-1执行了!
//pool-1-thread-2执行了!
//pool-1-thread-3执行了!
//pool-1-thread-1执行了!
}

使用ThreadPoolExecutor创建线程池【使用】

Executors使用可能存在的弊端

高并发系统环境中使用Executors如果不注意可能会出现资源耗尽的风险。

public ThreadPoolExecutor(

                          int corePoolSize,
                         
int maximumPoolSize,
                         
long keepAliveTime, 
                         
TimeUnit unit,  -》TimeUnit.单位
                         
BlockingQueue<Runnable> workQueue,   -》new ArrayBlockingQueue<>(6)
                         
ThreadFactory threadFactory,    -》Executors.defaultThreadFactory(),

                           RejectedExecutionHandler handler)

                           -》new ThreadPoolExecutor.AbortPolicy()                                              

参数一 corePoolSize指定线程池的线程数量(核心线程) -》 正式员工数量

参数二 maximumPoolSize指定线程池可支持的最大线程数 -》最大员工数量 = 正式员工 + 临时员工数量

参数三 keepAliveTime指定临时线程的最大存活时间  -》临时工没事做多久解雇

参数四 unit:指定线程存活时间的单位(秒、分、时、) -》时间单位

参数五 workQueue:指定任务存放的队列 -》等候区

参数六 threadFactory:指定用谁来创建线程 -》人才市场,提供员工

参数七 handler:任务的时候如何处理(拒绝策略) -》客人太多坐不下怎么办

public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,  // 1.核心员工
                6,  // 2.总的员工数量 = 临时工 + 核心员工
                100,  // 3.指定时间
                TimeUnit.SECONDS,  // 4.指定时间单位
                new ArrayBlockingQueue<>(6), // 5.等候区
                Executors.defaultThreadFactory(),  // 6. 人才市场


                new ThreadPoolExecutor.AbortPolicy()  // 7. 学会拒绝
                // new ThreadPoolExecutor.DiscardPolicy()  // 直接抛弃任务,不抛异常
                // new ThreadPoolExecutor.DiscardOldestPolicy() // 抛弃最开始等待的任务,把当前的放进去
                // new ThreadPoolExecutor.CallerRunsPolicy() // 当前线程去执行任务
        );
        Runnable r = () -> {
            System.out.println(Thread.currentThread().getName() + "执行了任务~~~");
        };
        // 核心线程
        pool.submit(r);
        pool.submit(r);
        pool.submit(r);

        // 6个线程处于等候区
        pool.submit(r);
        pool.submit(r);
        pool.submit(r);
        pool.submit(r);
        pool.submit(r);
        pool.submit(r);

        // 交给临时线程处理
        pool.submit(r);
        pool.submit(r);
        pool.submit(r);

        // 如果任务再多就要拒绝
        pool.submit(r);

        pool.shutdown();


    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值