java多线程基础

java多线程编程

课程视频来源:多线程06:初识并发问题_哔哩哔哩_bilibili

线程简介

进程

编写的静态代码文件,经过编译成为可执行的二进制文件,执行后它被加载至内存,被CPU执行。这个运行的程序就是进程

线程

为何要引入线程

如视频播放时,需要三个核心模块:1. 从视频读取数据,2. 数据解压缩,3. 解压缩的数据播放。单个进程肯定是按顺序执行这3个模块,但造成的问题是画面与声音不连贯,函数之间无法并发执行,效率低。那改成多进程呢?问题依旧存在:不同进程通信不方便,维护进程的开销大。

所以需要一种新的实体,满足以下两点,它便是线程。

  1. 实体间可并发执行
  2. 共享相同的地址空间。

什么是线程

线程thread是操作系统调度的最小单位,多数情况下,线程被包含与进程process之中,是其实际运行单位。一个进程可并发多个线程,各个线程执行不同的任务。每个线程有自己独立的寄存器和栈,确保各个线程独立。

java中线程的3中创建方式

  1. 继承Thread
  2. 实现Runnable接口
  3. 实现Callable接口

继承Thread类

创建一个子线程类Mythead继承Thead类,重写run方法即可

package ThreadExtend;

public class MyThread extends Thread {
    @Override
    public void run() {
       for(int i = 0;i < 10;++i){
           System.out.println("son: "+ Thread.currentThread());
       }
    }
}

主线程调用

import ThreadExtend.MyThread;

public class Main {
      public static void main(String[] args) {
        test1();
        for (int i = 0; i < 30; ++i) {
            System.out.println("father: " + Thread.currentThread());
        }
    }

   public static void test1()
    {
        MyThread t = new MyThread();
        t.start();
    }
}


注意:

  1. 只有通过start调用才会开启子线程,调用run只是普通的调用。
  2. 主线程中只有开启线程的之后的代码,才会和子线程并发执行。

实现Runnable接口

实现Runnable接口,重写run方法。

package RunnableInterface;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i< 20;++i){
            System.out.println("Runnable实现线程:"+Thread.currentThread());
        }
    }
}

创建线程:

 public static void test2(){
        MyRunnable t = new MyRunnable();
        new Thread(t).start();
}

查看Thread.java原码文件可发现,实际上Thread类正是实现了Runnable接口:

public class Thread implements Runnable
图片名称

推荐使用Runnable接口!!!

下面展示同一个对象被多个线程访问的例子

package RunnableInterface;

public class TestRunnable implements Runnable {
    private  int totalTicketNum;
   // private String name;
    TestRunnable(int num){
        this.totalTicketNum = num;
    }

    @Override
    public  void run() {
        buy();
    }

     void buy(){
        try {
            while(true){
                synchronized (this) {
                    if(totalTicketNum > 0){
                        System.out.println(Thread.currentThread().getName() + "拿到了第" + totalTicketNum-- + "票");
                    }
                    else{
                        break;
                    }
                }
                Thread.sleep(300);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package RunnableInterface;

public class Main {
    public static void main(String[] args) {
        TestRunnable t = new TestRunnable(10);
        new Thread(t,"小明").start();
        new Thread(t,"小花").start();
        new Thread(t,"小三").start();

    }
}

问题:为啥不把buy整体同步呢?因为想让totalTicketNum 变为0的时候线程结束执行,如果synchronized 同步整个buy方法,里面的while的存在导致totalTicketNum>0时,第一个线程一直持有锁,其他线程无法进入,导致只有一个人拿到了所有票。同时为了避免因为线程执行过快让一个人拿了全部票,添加了延时。

Callable接口

特点:

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(int nThreads);
  5. 提交执行​ Futureinteger ft1 = ser.submit(t1);
  6. 获取结果 ​ ft1.get()
  7. 关闭服务ser.shutdownNow();

例子:

package CallableInterface;

import java.util.concurrent.Callable;

public class MyCall implements Callable<Integer> {
    private int n;
    public MyCall(int n){
        this.n = n;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= n;++i){
            sum += i;
        }
        System.out.println(Thread.currentThread().getName() + "计算1 + 2 + ... + " + n + " = " + sum);
        return sum;
    }
}

package CallableInterface;

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

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCall t1 = new MyCall(100);
        MyCall t2 = new MyCall(200);
        MyCall t3 = new MyCall(300);
        MyCall t4 = new MyCall(400);

        ExecutorService ser = Executors.newFixedThreadPool(3);
        Future<Integer> ft1 = ser.submit(t1);
        Future<Integer> ft2 = ser.submit(t2);
        Future<Integer> ft3 = ser.submit(t3);
        Future<Integer> ft4 = ser.submit(t4);

        System.out.println("返回值1:" + ft1.get());
        System.out.println("返回值2:" + ft2.get());
        System.out.println("返回值3:" + ft3.get());
        System.out.println("返回值4:" + ft4.get());

        ser.shutdownNow();
    }
}

执行结果:

图片名称

https://s2.loli.net/2022/09/26/w3RAbNcMIu1WKTo.png

图片名称

​​

lambda表达式

为何要lambda表达式?

避免匿名内部类定义过多,关注核心逻辑,让代码更简洁。

应用场景

任何接口interface,若只包含一个抽象方法,那么可以i通过lambda表达式创建改接口对象。

特点

  • 可省略参数类型
  • 一个参数可省略括号
  • 一条语句可省略花括号
package Lambda;

public class MyLambda {
    static class Like2 implements ILike{
        @Override
        public void say() {
            System.out.println("peipei like me");
        }
    }

    public static void main(String[] args) {
         ILike like = new Like1();

         like.say();

         like = new Like2();
         like.say();
        //局部内部类
        class Like3 implements ILike{
            @Override
            public void say() {
                System.out.println("Marry me, peipei!");
            }
        }
        like = new Like3();
        like.say();

        like = new ILike() {
            @Override
            public void say() {
                System.out.println("I will marry peipei");
            }
        };
        like.say();
        like = ( )->{
                System.out.println("peipei will marry me!");
        };

        like.say();
    }
}

//函数接口
interface ILike{
    void say();
}

class Like1 implements ILike{
    @Override
    public void say() {
        System.out.println("I love peipei");
    }
}

线程状态

线程5个状态

图片名称

线程方法

图片名称

终止线程

一般用标志法,即外部修改变量启动或者停止线程

package ThreadExtend;

public class MyThread extends Thread {
    volatile private Boolean flag = true;
    @Override
    public void run() {
        long num = 0;
       while(flag){
           try {
               Thread.sleep(50);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
           num++;
       }
        System.out.println("num = " + num);
    }
    public void stopThread(){
        flag = false;
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        for(int i = 0;i<100;++i){
           if(i < 50){
                Thread.sleep(10);
            }
           else if(i == 50){
                System.out.println("线程停止");
                t.stopThread();
               break;
            }
        }
        t.join();
    }
}

操作系统往往将变量缓存下来,故用volatile 修饰,使得读取flag时强制从其内存地址读取最新值。

线程休眠sleep

sleep(int millisecond); 时间达到后线程进入就绪状态,可用于模拟网络延时,倒计时等。每个对象都有一把锁,sleep不会释放锁.

线程礼让yield

礼让的线程先出来,再和其他线程竞争。线程礼让一定成功,但CPU依旧可能会调度它。

package RunnableInterface;


public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始");
        System.out.println(Thread.currentThread().getName()+"结束");
    }

}

package RunnableInterface;

public class MyYeildRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束");
    }
}


package RunnableInterface;

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        MyYeildRunnable myYeildRunnable = new MyYeildRunnable();
        new Thread(myYeildRunnable,"a").start();
        new Thread(myRunnable,"b").start();

    }
}

/*礼让成功
a开始
b开始
b结束
a结束
*/

join线程

合并线程,听不懂???就是插队执行,其他线程阻塞等我执行完你再接着。

package ThreadExtend;

public class MyThread extends Thread {

    @Override
    public void run() {
       for(int i = 0; i< 1000;++i){
           System.out.println("vip来了!" + i);
       }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.start();
        for(int i = 0;i<100;++i){
            if(i == 50){
                System.out.println("aaa:" + t.getState());
                t.join();
                System.out.println("bbb:" + t.getState());
            }
            System.out.println("main" + i);
        }

    }
}

图片名称 图片名称

线程优先级

事实上没什么卵用

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        Thread t1 = new Thread(myRunnable,"a");
        Thread t2 = new Thread(myRunnable,"b");
        Thread t3 = new Thread(myRunnable,"c");
        Thread t4 = new Thread(myRunnable,"d");

        t1.setPriority(4);
        t2.setPriority(8);
        t3.setPriority(2);
        t4.setPriority(10);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

守护线程

什么是守护线程?

java中线程分为:1. 用户线程, 2. 守护线程。 守护线程通常后台执行,为用户线程服务。JVM的垃圾回收线程是典型的守护线程,在程序执行时它一直保持执行,但当所有线程结束,它就可以结束了。

package RunnableInterface;


class DaemonThred  implements Runnable{
    @Override
    public void run() {
        int i = 0;
        while(true){
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("人生路漫漫,活着的第" + i++ +"天");
        }
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i  = 1;i <= 365;++i){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("今天是一年中第" +i + "天");
        }
    }
}


package RunnableInterface;

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        DaemonThred daemonThred = new DaemonThred();

        Thread t =  new Thread(daemonThred);
        t.setDaemon(true);   //设置为守护线程
        t.start();
        new Thread(myRunnable).start();
    }
}

图片名称

posted @   shmilyt  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示