Java的多线程

什么是多线程?

有了多线程,我们就可以让程序同时做多件事情

多线程的作用?

提高效率

多线程的应用场景?

只要你想让多个事情同时运行就需要用到多线程

比如:软件中的耗时操作,所有的聊天软件,所有的服务器

并发和并行

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

并行:在同一时刻,有多个指令在多个cpu上同时执行

多线程的实现方式

1. 继承Thread

这种方式是通过继承Thread类,并重写其run()方法来定义线程的任务。

步骤:

定义一个类继承Thread类。

在类中重写run()方法,定义线程需要执行的任务。

创建该类的实例,并调用start()方法启动线程。

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running...");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 启动线程
    }
}

2. 实现Runnable接口

这种方式是通过实现Runnable接口的run()方法来定义线程的任务。这种方式可以避免Java单继承的限制,更适用于需要继承其他类的情况。

步骤:

定义一个类实现Runnable接口。

在类中实现run()方法,定义线程需要执行的任务。

创建该类的实例,作为参数传递给Thread类的构造函数,创建Thread对象。

调用Thread对象的start()方法启动线程。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread is running...");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t= new Thread(myRunnable);
        t.start(); // 启动线程
    }
}

3. 使用Callable接口和Future接口

这种方式与前两种不同,Callable接口可以返回线程执行的结果,并且可以抛出异常。它需要配合Future接口来获取执行结果。

步骤:

定义一个类实现Callable接口,并重写call()方法来定义线程的任务。

使用ExecutorService创建线程池,提交Callable任务。

通过Future对象获取线程的执行结果。

 接口
package ThridCallabelAndFuture;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 0; i <= 100; i++) sum+=i;
        return sum;
    }
}
测试 

package ThridCallabelAndFuture;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
         多线程第三种实现方式
          特点:可以获取到多线程运行的结果

          创建一个类MyCallable实现callable接口
          重写call(有返回值,表示多线程的运行结果)

          创建MyCallable的对象(表示多线程要执行的任务)
          创建FutureTask的对象(作用管理多线程运行的结果)

          创建Thread类的对象,并且启动(表示线程)
         */

        MyCallable mc = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<>(mc);
        Thread t1 = new Thread(ft);
        t1.start();

        // 获取多线程运行的结果
        int ans = ft.get();
        System.out.println(ans);
    }
}

 多线程常用成员方法

            String getName()                 返回线程的名称
            void setName(String name)        设置线程的名字(构造方法也可以设置名字)
            细节:
            1.如果我们没有给线程设置名字,线程也是有默认的名字的
            格式:Thread-x(x序号,从0开始)
            2.如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置

            static Thread currentTread()        获取当前线程的对象

            细节:
            当JVM虚拟机启动之和,会自动的启动多条线程
            其中一条线程叫做main线程
            他的作用就是调用main方法,并执行里面的代码
            在以前,我们写的所有代码都是运行在main线程中的

            static void sleep(long time)           让线程休眠指定的时间,单位为毫秒

             细节:
            1.哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
            2.方法的参数:表示睡眠的时间。单位是毫秒
            当时间到了之后,线程就会自动结束,继续执行下面的其他代码

            优先级:
            setPriority(int newPriority)
            final int getPriority()
            默认的优先级为5

            守护线程:
            final void setDaemon(boolean on)        设置为守护线程
            细节:
            当其他的非守护线程执行完毕后,守护线程会陆续结束(不会立即结束)


            出让线程/礼让线程
            public static void yield()

            插入线程/插队线程
            public static void join()
//优先级
MyThread mh1 = new MyThread("飞机");
        MyThread mh2 = new MyThread("坦克");
        mh2.setPriority(Thread.MAX_PRIORITY);// 设置为10
        System.out.println(mh1.getPriority());
        System.out.println(mh2.getPriority());
// 守护线程
        MyThread mh3 = new MyThread("女神");
        MyThread mh4 = new MyThread("备胎");

        // 把第四个线程设置为守护线程
        // 当女神线程结束后,备胎就没有存在的必要了,会陆续结束
        mh4.setDaemon(true);
        mh3.start();
        mh4.start();
// 插入线程
MyThread mh5 = new MyThread("土豆");
        mh5.start();
        mh5.join();
        // 表示把mh5这个线程插入到当前线程之前
        // mh5:土豆
        // 当前线程:main线程


        for(int i = 0; i < 10; i++){
            System.out.println("main线程"+i);
        }

 线程的生命周期

 线程的安全问题

数据竞争:当多个线程同时读取和写入共享变量时,可能会导致不可预测的结果。例如,一个线程正在更新一个变量的值,而另一个线程同时读取这个变量的值,就可能导致读取到错误的数据。

死锁:当两个或多个线程相互等待对方释放资源,从而进入无限等待的状态,这种情况称为死锁。死锁会导致程序的部分或全部停止运行。

解决:

1. 同步代码块(Synchronized Block)

同步代码块是指对一段代码进行同步控制,使用synchronized关键字指定需要同步的代码块。它可以用来锁住一个对象,使得同一时间只有一个线程可以执行该代码块。

synchronized (锁对象) {
    // 同步代码块
}

锁对象:通常使用的是共享资源的对象(如this或类的某个静态对象)。锁对象必须要是唯一的,可以用字节码文件对象(MyThread.class)

同步代码块:在锁定的范围内,只有持有该锁对象的线程才能执行,其他线程必须等待

package synchronous;

public class MyThread extends Thread {
    static int tic = 1;
    // 锁对象一定要是唯一的
//    static Object obj   = new Object();
    @Override
    public void run() {
        // 同步代码块
        while(true){
            // 如果锁对象是this,那么锁就不管用了
            // 字节码文件对象一定是唯一的,可以作为锁对象
            synchronized(MyThread.class){
                // 同步代码块
                if(tic <= 100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(getName()+ "正在卖第"+tic+"张票");
                    tic++;
                }else break;
            }
        }
        System.out.println("票卖完了");
    }
}

使用第二种实现方式: 

package synchronous;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    static Lock lock = new ReentrantLock();
    int tic = 0;
    @Override
    public void run() {
        while (true) {
            if (method()) break;
        }
        System.out.println("票卖完了");
    }
    private boolean method() {
//         自动锁
        synchronized (Runnable.class) {
            // 同步代码块
            if (tic <= 100) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + tic + "张票");
                tic++;
            } else return true;
        }
        return false;
    }
}

// 避免死锁(避免两个锁嵌套)
package synchronous;

public class Test {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();


        MyRunnable mr =  new MyRunnable();
        Thread t4 = new Thread(mr);
        Thread t5 = new Thread(mr);
        Thread t6 = new Thread(mr);

        t4.setName("窗口1");
        t5.setName("窗口2");
        t6.setName("窗口3");

//        t1.start();
//        t2.start();
//        t3.start();

        t4.start();
        t5.start();
        t6.start();

    }
}

2. 同步方法(Synchronized Method)

同步方法是指使用synchronized关键字修饰整个方法,使得该方法在某个时刻只能被一个线程访问。

public synchronized void 方法名() {
    // 方法体
}

 锁

import java.util.concurrent.locks.ReentrantLock;

public class Example {
    private final ReentrantLock lock = new ReentrantLock();

    public void method() {
        lock.lock();  // 获取锁
        try {
            // 临界区代码
        } finally {
            lock.unlock();  // 释放锁
        }
    }
}
/*
    1.循环
    2.同步代码块
    3.判断共享数据是否到了末尾(先写到了末尾)
    没有到末尾,执行核心逻辑
     */

package synchronous;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyRunnable implements Runnable {
    static Lock lock = new ReentrantLock();
    int tic = 0;
    @Override
    public void run() {
        while(true){
            // 手动锁
            lock.lock();
            try {
                if(tic==100){
                    break;
                }else{
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + tic + "张票");
                    tic++;
            }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
                //finally是一定会被执行的
            } finally {
                lock.unlock();
            }
        }
    }
}

等待唤醒机制

核心方法

wait()

使当前线程进入等待状态,并释放锁。

线程在调用wait()时,必须已经持有该对象的锁(即在同步方法或同步块中调用)。

调用wait()后,当前线程会进入等待队列,直到其他线程调用notify()notifyAll()唤醒它。

notify()

唤醒在等待队列中等待同一个锁的一个线程。如果有多个线程在等待同一个锁,具体唤醒哪一个线程是由JVM决定的。

唤醒的线程必须重新获得该锁才能继续执行。

notifyAll()

唤醒所有在等待队列中等待同一个锁的线程。被唤醒的线程仍然需要竞争锁,只有获得锁的线程才能继续执行。

样例:

背景:吃货吃面,厨师做面,其中需要一个桌子作为中间人。我们需要完成的是如果桌子上面有食物就吃食物,十碗吃饱,如果没有,就等待。厨师则是判断桌子上面是否有食物,如果有,就等待,如果没有就做面条。

桌子:

package waitandnotify;

public class Desk {

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

吃货:

package waitandnotify;

public class Foodie extends Thread {
    /*
    1.循环
    2.同步代码块
    3.判断共享数据是否到了末尾(先写到了末尾)
    没有到末尾,执行核心逻辑
     */
    @Override
    public void run() {
        while(true){
            synchronized(Desk.lock){
                if(Desk.cnt == 0) {
                    break;
                } else {
                    // 判断桌子上面是否有面条
                    if(Desk.foodFlag == 1){
                        Desk.cnt--;
                        if(Desk.cnt == 0){
                            System.out.println("吃货吃饱啦!!");
                        }else {
                            System.out.println("吃货正在吃面条还能再吃"+Desk.cnt+"碗面条");
                        }
                        Desk.foodFlag--;
                        // 唤醒厨师做面条
                        Desk.lock.notifyAll();
                    } else if(Desk.foodFlag == 0){
                        // 没有就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }
}

厨师:

package waitandnotify;

public class cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if(Desk.cnt== 0){
                    break;
                }else{
                    if(Desk.foodFlag==1){
                        // 有食物就等待必须用锁对象调用wait
                        // 这样就能把锁对象和线程绑定起来
                        // 绑定之和唤醒就知道唤醒什么线程了
                        // 不能直接wait();
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else if(Desk.foodFlag== 0){
                        // 开始做面
                        System.out.println("厨师做了一碗面条");
                        Desk.foodFlag++;
                        Desk.lock.notify();// 唤醒这把锁绑定的线程
                    }
                }
            }
        }
    }
}

阻塞队列完成等待唤醒机制

阻塞队列的基本概念

阻塞队列:一种支持线程安全操作的队列,当队列为空时,从队列中取元素的操作会阻塞线程,直到队列中有元素可用;当队列已满时,向队列中插入元素的操作会阻塞线程,直到队列中有空间。

常见的阻塞队列实现

  • ArrayBlockingQueue:基于数组的有界阻塞队列,队列大小在初始化时指定,之后不可变。
  • LinkedBlockingQueue:基于链表的阻塞队列,可以是有界或无界的(实际上是有界的,但默认值为Integer.MAX_VALUE)。
  • PriorityBlockingQueue:基于优先级堆的无界阻塞队列,元素根据其优先级进行排序。
  • SynchronousQueue:一种特殊的阻塞队列,不存储元素,每个put操作必须等待一个take操作,反之亦然。
  • DelayQueue:其中的元素只有在指定的延迟时间过后才能被取走。

阻塞队列的核心方法

  • 插入操作

    • put(E e):将元素插入队列,如果队列满了,线程将被阻塞,直到队列有空间。
    • offer(E e):尝试将元素插入队列,返回true表示插入成功,返回false表示队列已满。
    • offer(E e, long timeout, TimeUnit unit):在给定的时间内尝试插入元素,如果插入失败线程将被阻塞,直到成功插入或超时。
  • 移除操作

    • take():从队列中取走一个元素,如果队列为空,线程将被阻塞,直到队列有可用元素。
    • poll():尝试从队列中取走一个元素,返回null表示队列为空。
    • poll(long timeout, TimeUnit unit):在给定的时间内尝试取走一个元素,如果队列为空线程将被阻塞,直到成功取出元素或超时。

情景:

厨师:
package waitandnotifyQue;

import java.util.concurrent.ArrayBlockingQueue;

public class cook extends Thread {
    ArrayBlockingQueue<String> aq;
    public cook( ArrayBlockingQueue<String> aq ) {
        this.aq = aq;
    }
    @Override
    public void run() {
        while (true){
            try {
                aq.put("食物");
                System.out.println("厨师做了一碗面条");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
吃货:
package waitandnotifyQue;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
    ArrayBlockingQueue<String> aq;
    public Foodie( ArrayBlockingQueue<String> aq ) {
        this.aq = aq;
    }

    // 不会结束
    @Override
    public void run() {
        while (true){
            try {
                // take里面自带锁,如果再加上锁就会变成死锁
                String food = aq.take();
                System.out.println(food);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
            }
        }
    }
}

测试:
package waitandnotifyQue;

import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> aq = new ArrayBlockingQueue<>(1); // 定义一个队列
        cook c = new cook(aq);
        waitandnotifyQue.Foodie f = new Foodie(aq);
        c.start();
        f.start();

    }
}

 线程池:

线程池的基本概念

线程池:一个容纳多个可复用线程的池子。线程池中的线程可以被多次复用,以执行不同的任务,从而避免了频繁的线程创建和销毁。

工作原理

  • 提交任务到线程池。
  • 线程池将任务分配给空闲的线程执行。
  • 如果所有线程都在忙,线程池会根据配置决定是创建新线程、将任务排队,还是拒绝任务。
  • 线程执行完任务后回到线程池等待新的任务。

主要核心原理

  1. 创建一个池子,池子是空的。
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
package ThreadPoll;

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

 没有上限的线程池

package ThreadPoll;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        /*
        public static ExecutorService newCachedThreadPool()     创建一个没有上限的线程池
        public static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池
         */

        ExecutorService poolCached = Executors.newCachedThreadPool();

        // 会创建7个线程
        poolCached.submit(new MyRunnable());
        poolCached.submit(new MyRunnable());
        poolCached.submit(new MyRunnable());
        poolCached.submit(new MyRunnable());
        poolCached.submit(new MyRunnable());
        poolCached.submit(new MyRunnable());
        poolCached.submit(new MyRunnable());
       

        // 销毁线程池
        poolCached.shutdown();

    }
}

有上限的线程池 

package ThreadPoll;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        /*
        public static ExecutorService newCachedThreadPool()     创建一个没有上限的线程池
        public static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池
         */
        ExecutorService poolFix = Executors.newFixedThreadPool(3);

       // 只有3个线程
        poolFix.submit(new MyRunnable());
        poolFix.submit(new MyRunnable());
        poolFix.submit(new MyRunnable());
        poolFix.submit(new MyRunnable());
        poolFix.submit(new MyRunnable());
        poolFix.submit(new MyRunnable());

        // 销毁线程池
        poolFix.shutdown();

    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值