Java基础——多线程

1.什么是多线程

多线程可以让程序同时做多件事情,可以提高软件的运行效率,当让多个事情同时运行时就需要用到多线程。例如软件中的耗时操作、所有聊天软件、所有的服务器。

2.并发和并行

并发:在同一时刻,有多个指令在单个CPU上交替进行
并行:在同一时刻,有多个指令在多个CPU上同时进行

3.多线程的实现方式

第一种实现方式:继承Thread类

(1)定义一个类继承Thread
(2)重写run方法
(3)创建子类的对象,并启动线程

//MyThread.java

public class MyThread extends Thread{
    @Override
    public void run() {
     //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName() + "HelloWorld");
        }
    }
}
//ThreadDemo1.java

public class ThreadDemo1 {
    public static void main(String[] args) {
   		//创建多线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        //为线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();
    }
}
第二种实现方式:实现Runnable接口

(1)定义一个类实现Runnable接口
(2)重写里面的run方法
(3)创建自己的类的对象
(4)创建一个Thread类的对象,并开启线程

//MyRun.java
public class MyRun implements Runnable{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            //获取到当前线程的对象
            System.out.println(Thread.currentThread().getName() +
                    "HelloWorld");
        }
    }
}
//ThreadDemo2.java
public class ThreadDemo {
    public static void main(String[] args) {
        //创建MyRun对象表示多线程要执行的任务
        MyRun mr = new MyRun();
        //创建多线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        //为线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();
    }
}

第三种实现方式:实现Callable接口

特点:可以获取到多线程中运行的结果
(1)创建一个类MyCallable实现Callable接口
(2)重写call方法(有返回值,表示多线程运行的结果)
(3)创建MyCallable的对象(表示多线程要执行的任务)
(4)创建FutureTask的对象(表示管理多线程运行的结果)
(5)创建Thread类的对象,并启动

//MyCallable.java
public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        //求1-100之间的整数和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

//ThreadDemo3.java

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable对象,表示多线程要执行的任务
        MyCallable mc = new MyCallable();
        //创建FutureTask对象,管理多线程任务运行的结果
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程的对象
        Thread t1 = new Thread(ft);
        t1.start();
        //获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }
}

优点缺点
继承Thread类编程比较简单,可以直接使用Thread类中的方法可扩展性差,不能再继承其他的类
实现Runnable接口, 实现Callable接口扩展性强,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法

4.常见的成员方法

  • String getName() 返回此线程的名称
  • void setName() 设置线程的名字(构造方法也可以设置)
    (1)如果没有给线程设置名字,线程会有默认的名字
    (2)如果我们要给线程设置名字,可以用set方法设置,也可以用构造方法设置
  • static Thread currentThread() 获取当前线程的对象
    当JVM虚拟机启动之后,会自动启动多条线程,其中有一条线程叫做main线程,其作用是去调用main方法,并执行里面的代码。
  • static void sleep(long time) 让线程休眠指定的时间,单位是毫秒
//MyThread.java
public class MyThread extends Thread{
    public MyThread() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + i);
        }
    }
}

//ThreadDemo4.java
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();

        Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name);
        //哪条线程执行到该方法,此时获取的就是哪条线程的对象
        System.out.println(11111);
        Thread.sleep(10000);
        System.out.println(22222);
    }
}

  • setPriority(int new Priority) 设置线程的优先级
  • final int getPriority() 获取线程的优先级
    程优先级只是概率大小问题,优先级高的也不一定每次都先完成。
  • final void setDaemon(boolean on) 设置为守护线程
    当其他的非守护线程执行完毕之后,守护线程会陆续结束
  • public static void yield() 出让线程/礼让线程
    表示出让当前CPU的执行权
  • public final void join() 插入线程/插队线程
    表示将某个线程插入到当前线程之前

5.线程的生命周期

6.同步代码块

在使用多线程时,多个线程执行同一个任务时,它们之间没有限制,这个线程的任务没结束,另一个线程就开始执行任务。可能会发生数据异常问题。
此时需要一个同步代码块为线程任务加上一个锁,当一个线程执行任务时,其他线程不能进入该代码块执行任务。只有等这个线程执行完任务,才可以抢占CPU执行代码。

public class MyThread extends Thread{
    static int ticket = 0;

    @Override
    public void run() {
        while(true){
        	//同步代码块
            synchronized (MyThread.class){
                if(ticket < 100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket ++;
                    System.out.println(getName() + "正在执行第" +ticket + "条任务");
                }else{
                    break;
                }
            }
        }
    }
}

7.同步方法

将synchronized关键字加到方法上
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定。非静态方法:this;静态方法:当前类的字节码文件对象

public class MyRunnable implements Runnable {
    int ticket = 0;
    
    @Override
    public void run() {
        while(true){
            if (method()) break;
        }
    }

    private synchronized boolean method() {
        if(ticket == 100){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket ++;
            System.out.println(Thread.currentThread().getName() + "---" + ticket);
        }
        return false;
    }
}

8.lock锁

为了更清晰的表达如何加锁和如何释放锁
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock() 获得锁
void unlock() 释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法:ReentrantLock() 创建一个ReentrantLock的实例

public class MyRunnable implements Runnable {
    int ticket = 0;
    static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
//            synchronized (Thread.class){
            lock.lock();
            try {
                if(ticket == 100){
                    break;
                }else{
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket ++;
                    System.out.println(Thread.currentThread().getName() + "---" + ticket);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
//            }
        }
    }
}

9.死锁

是一种错误类型,在实现代码时需要避免,一般嵌套锁会产生死锁

10.等待唤醒机制

常见方法:

  • void wait() 当前线程等待,被其他线程唤醒
  • void notify() 随机唤醒单个线程
  • void notifyAll() 唤醒单个线程
生产者和消费者的工作流程

消费者:

  • 判断桌子上是否有食物
  • 如果没有就等待
  • 如果有就开吃
  • 吃完之后唤醒厨师继续做

生产者:

  • 判断桌上是否有食物
  • 有:等待
  • 没有:制作食物
  • 把食物放在桌子上
  • 叫醒等待的消费者开吃
public class ThreadDemo {
    public static void main(String[] args) {
        Cook c = new Cook();
        Foodie f = new Foodie();

        c.setName("厨师");
        f.setName("吃货");

        c.start();
        f.start();
    }
}


public class Desk {
//    作用:控制生产者和消费者的执行

    //是否有面条 0:没有面条  1:有面条
    public static int foodFlag = 0;
    //总个数
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}



public class Cook extends Thread{
    /*
     * 1.循环
     * 2.同步代码块
     * 3.判断共享数据是否到了末尾(到了末尾)
     * 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
     */
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //判断桌上是否有食物
                    if(Desk.foodFlag == 1){
                        //有:等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //没有:制作食物
                        System.out.println("厨师做了一碗面条");
                        //把食物放在桌子上
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}



public class Foodie extends Thread{
    @Override
    public void run() {
        /*
        * 1.循环
        * 2.同步代码块
        * 3.判断共享数据是否到了末尾(到了末尾)
        * 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         */
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //先判断桌上有没有面条
                    if(Desk.foodFlag == 0){
                        //如果没有,就等待
                        try {
                            Desk.lock.wait();//让当前线程跟锁绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        Desk.count --;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗");
                        //吃完后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        //修改桌子状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
阻塞队列

阻塞队列的实现代码中已经自动加锁了,所以用阻塞队列实现生产者消费者任务时,不用自己写锁。

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

        //1.创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        //2.创建线程的对象,并把阻塞队列传递进去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        c.start();
        f.start();
    }
}


public class Desk {
//    作用:控制生产者和消费者的执行

    //是否有面条 0:没有面条  1:有面条
    public static int foodFlag = 0;
    //总个数
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}

public class Cook extends Thread{

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {
        while (true) {
            try{
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }
    @Override
    public void run() {

        while(true){
            try{
                String food = queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

11.线程的状态请添加图片描述

12.线程池

核心原理:
(1)创建一个池子,池子中是空的
(2)提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可
(3)但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
核心方法:
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
piblic static ExecutorService newFixedThreadPool() 创建一个有上限的线程池

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        //1.获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
        //2.提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        //3.销毁线程池
//        pool1.shutdown();
    }
}


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

13.自定义线程池

public class MyThreadPoolDemo1 {
    public static void main(String[] args) {
        /*
        * ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        * (核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略)
        *
        * 参数一:核心线程数量           不能小于0
        * 参数二:最大线程数量           不能小于0,最大数量>=核心线程数量
        * 参数三:空闲线程最大存活时间     不能小于0
        * 参数四:时间单位              用TimeUnit指定
        * 参数五:任务队列              不能为null
        * 参数六:创建线程工厂           不能为null
        * 参数七:任务的拒绝策略         不能为null
        *
        * */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,
                6,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }
}

线程池大小问题:

获取java的最大并行数:

int count = Runtime.getRuntime().availableProcessors();
System.out.println(count);

CPU密集型运算:最大并行数+1
I/O密集型运算:最大并行数 * 期望CPU利用率 *(总时间(CPU计算时间+等待时间)/ CPU计算时间)

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值