前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Java 多线程学习

Java 多线程学习

作者头像
默 语
发布2024-11-20 12:10:45
发布2024-11-20 12:10:45
9800
代码可运行
举报
文章被收录于专栏:JAVAJAVA
运行总次数:0
代码可运行

1、线程简介

1.1 多任务理解

我们举例来理解下多任务;

开车 + 打电话

吃饭 + 玩手机

这些动作都可以抽象为任务,虽然看起来一心二用,但人只有一个大脑,在一个时间片刻只能处理一个任务。

CPU 也是一样,面对多个任务,只能在一个时间片刻处理一个任务。

1.2 多线程理解

我们举例来理解下多线程;

多条线路同时跑起来;有多个分支共同去执行;

主线程调用 run 方法和调用 start 方法开启子线程的区别如下图所示。

1.3 线程与进程

执行程序的过程,叫进程;一个进程中有多个线程;

核心概念;

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,GC 线程;
  • main()称之为主线程,为系统的入口,用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  • 线程会带来额外的开销,如 CPU 调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

2、线程实现

线程的三种实现方式:(线程实现在实际工作中优先使用Runnable接口和Callable接口方式

2.1 第一种:继承 Thread 类,重写 run 方法

继承 Thread 类,重写 run 方法。创建这个类的对象,再调用 start() 即可

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

/**
 * @author 闫文超
 */

//多线程调用 先继承Thred 方法 重写run方法 最后再用start 开启线程

//总结:线程的开启不一定立即开始执行,由CPU调度执行
public class testThread01 extends Thread {

    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程---" + i);
        }

    }


    public static void main(String[] args) {
        //创建一个线程对象
        testThread01 thread01 = new testThread01();

        //先执行run的方法在执行下面的内容
        // thread01.run();
        //start的方法和main同步执行;顺序同步
        // 调用start 开启线程
        thread01.start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我在学习thread--" + i);
        }

    }
}

下载文件需要在 pom.xml 中 commons io 包。

代码语言:javascript
代码运行次数:0
运行
复制
  <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class testThred02 extends Thread {

    public String url;//图片路径
    public String name;//图片的名字

    public testThred02(String url, String name) {
        this.name = name;
        this.url = url;
    }


    @Override //代表重写
    public void run() {
        downloadpng down = new downloadpng();
        down.download(url, name);
        System.out.println("下载的文件名为" + name);

    }

    //主线程 main方法
    public static void main(String[] args) {
        testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg");
        testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg");
        testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg");


        //开启线程
        thred01.start();
        thred02.start();
        thred03.start();
    }


    //先写一个下载器
    class downloadpng {
        //引用地址和名字进行下载
        public void download(String url, String name) {

            try {
                FileUtils.copyURLToFile(new URL(url), new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader 方法出现问题");
            }


        }

    }

}

使用该方法下载网络图片。

在这里插入图片描述
在这里插入图片描述

2.2 第二种:

继承 Runnable 接口,创建 Tread 对象 或者使用Runnable接口通过线程池 下面演示的是继承 Runnable 接口,创建 Tread 对象: 继承 Runnable 接口,创建 Tread 对象,传入实现类,开启 start 方法

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;


//1.实现Runnable接口 重写run方法 执行线程需要丢人Runnable接口实线路调用start方法启动线程
public class testThread03 implements Runnable {


    @Override
    public void run() {

        //run方法线程体

        for (int i = 0; i < 20; i++) {
            System.out.println("闫文超在摸鱼啊---" + i);
        }

    }


    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        testThread03 thread03 = new testThread03();

        //创建线程对象,通过线程对象来开启我们的线程;代理
        Thread thread = new Thread(thread03);

        thread.start();

        // 或者
       // new Thread(thread03).start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习当当中" + i);
        }

    }


}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

以上两种方式的比较:

继承 Thread 类
  • 子类继承 Thread 类具备多线程能力
  • 启动线程:子类对象 .start()
  • 不建议使用:避免 OOP 单继承局限性
实现 Runnable 接口
  • 实现接口 Runnable 具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,方便同一个对象被多个线程使用。

火车抢票实例:

Runnable 实现多线程,创造一个实列 ticketRunnable ,可共享给多个线程。

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

//实现Runnnable的方式调用多线程
//多个线程调用一个对象
//    买火车票的例子
//    发现问题:多个线程操作同一个资源,线程不安全,数据紊乱!
public class testThread04 implements Runnable{

    public int ticketNums=30;

    @Override
    public void run() {

        while (true){

            if (ticketNums<=10){
                break;
            }

            try {
                //线程模拟休眠2s
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
        }


    }

    public static void main(String[] args) {
        testThread04 thread04 = new testThread04();

        new Thread(thread04,"小王").start();
        new Thread(thread04,"小龙").start();
        new Thread(thread04,"小李").start();
        new Thread(thread04,"黄牛").start();

    }
}
在这里插入图片描述
在这里插入图片描述

龟兔赛跑案例:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;


//实现runnable的接口
public class testThred05 implements Runnable {

    //胜利者
    public static String winner;

    @Override
    public void run() {


        for (int i = 0; i < 1001; i++) {

            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {

                //每跑10步 兔子休眠0.5毫秒
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }


            Boolean gameover = gameover(i);
            if (gameover)
                break;


            System.out.println(Thread.currentThread().getName() + "-跑了" + i);
        }


    }

    public Boolean gameover(int steps) {

        if (winner != null) {
            return true;

        } else {


            if (steps >= 1000) {

                winner = Thread.currentThread().getName();
                System.out.println("winner is" + winner);
                return true;
            }


        }


        return false;
    }


    public static void main(String[] args) {

        testThred05 thred05 = new testThred05();

        new Thread(thred05, "兔子").start();
        new Thread(thred05, "乌龟").start();

    }
}
在这里插入图片描述
在这里插入图片描述

2.3 第三种:实现 Callable 接口

Callable接口详解_逍遥绝情的博客-CSDN博客_callable接口(不懂看这个博客)

Callable两种执行方式:第一种借助线程池来运行

  1. 实现 Callable 接口,需要返回值类型
  2. 重写 call 方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService = Executor.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(1);
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow():
在这里插入图片描述
在这里插入图片描述

Future接口 Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、 等待完成和得到计算的结果。 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。 一旦计算完成了,那么这个计算就不能被取消。

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//Callable<Boolean>  布尔类型和call 同步
//callable的好处:
//1.可以定义返回值
//2.可以抛出异常

public class testThred02 implements Callable<Boolean> {

    public String url;//图片路径
    public String name;//图片的名字

    public testThred02(String url, String name) {
        this.name = name;
        this.url = url;
    }


    @Override //代表重写
    public Boolean call() {
        downloadpng down = new downloadpng();
        down.download(url, name);
        System.out.println("下载的文件名为" + name);
        return true;
    }

    //主线程 main方法
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg");
        testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg");
        testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg");

        // 创建执行服务  线程池中有3个线程
        ExecutorService ser = Executors.newFixedThreadPool(3);
        // 提交执行 执行每个线程
        Future<Boolean> result1 = ser.submit(thred01);
        Future<Boolean> result2 = ser.submit(thred02);
        Future<Boolean> result3 = ser.submit(thred03);

        // 获取结果:
        boolean r1 = result1.get();
        boolean r2 = result2.get();
        boolean r3 = result3.get();


        //关闭服务
        ser.shutdownNow();

    }


    //先写一个下载器
    class downloadpng {
        //引用地址和名字进行下载
        public void download(String url, String name) {

            try {
                FileUtils.copyURLToFile(new URL(url), new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader 方法出现问题");
            }


        }

    }

}
在这里插入图片描述
在这里插入图片描述

Callable两种执行方式:第二种借助FutureTask执行

示例代码中,CallableTest 类实现了 Callable 接口的 call() 方法。在 main() 函数内首先创建了一个 FutureTask 对象(构造函数为 CallableTest 实例),然后使用创建的 FutureTask 对象作为任务创建了一个线程并且启动它,最后通过 futureTask.get() 等待任务执行完毕返回结果。

代码语言:javascript
代码运行次数:0
运行
复制
Future接口
Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、
等待完成和得到计算的结果。
当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
一旦计算完成了,那么这个计算就不能被取消。

FutureTask类
FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
FutureTask有2个构造方法:入参分别是Callable或者Runnbale对象。

FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
代码语言:javascript
代码运行次数:0
运行
复制
public class FutureTask<V> implements RunnableFuture<V>{}

public interface RunnableFuture<V> extends Runnable, Future<V>

代码示例:

代码语言:javascript
代码运行次数:0
运行
复制
package com.sjmp.practice;

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



public class CallableTest implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Hello CallableThread";
    }

    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        FutureTask<String> futureTask = new FutureTask<>(callableTest);

        new Thread(futureTask).start();

        try {
            String s = futureTask.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
代码语言:javascript
代码运行次数:0
运行
复制
Hello CallableThread

Process finished with exit code 0
Runnable和Callable的区别(重要)
代码语言:javascript
代码运行次数:0
运行
复制
相同点
1、两者都是接口;(废话)
2、两者都可用来编写多线程程序;
3、两者都需要调用Thread.start()启动线程;

不同点
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;
而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;
而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

注意点
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

扩展(重要):

Callable接口详解

  • Callable: 返回结果并且可能抛出异常的任务。
  • 优点:
    • 可以获得任务执行返回值;
    • 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。
Runnable和Callable的区别:

1、Callable规定的方法是call(),Runnable规定的方法是run().

2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得

3、call方法可以抛出异常,run方法不可以

4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

5、代码示例:

代码语言:javascript
代码运行次数:0
运行
复制
  //Callable 接口
  public interface Callable<V> {
     V call() throws Exception;
  }
  // Runnable 接口
  public interface Runnable {
      public abstract void run();
  }

Future接口
  • Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
  • 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
  • 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
  • 一旦计算完成了,那么这个计算就不能被取消。
FutureTask类
  • FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
  • FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor(如上面例子那样)。
Callable两种执行方式

1、借助FutureTask执行

FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

具体流程:

代码语言:javascript
代码运行次数:0
运行
复制
  //定义实现Callable接口的的实现类重写call方法。
  public class MyCallableTask implements Callable<Integer>{
      @Override
          public Integer call() throws Exception {
             //TODO 线程执行方法
          }
  }
  ---------------------------------------------------------
  //创建Callable对象
  Callable<Integer> mycallabletask = new MyCallableTask();
  //开始线程
  FutureTask<Integer> futuretask= new FutureTask<Integer>(mycallabletask);
  new Thread(futuretask).start();
  --------------------------------------------------------
  通过futuretask可以得到MyCallableTask的call()的运行结果:
  futuretask.get();

2、借助线程池来运行

线程池中执行Callable任务原型:

代码语言:javascript
代码运行次数:0
运行
复制
  public interface ExecutorService extends Executor {
      //提交一个Callable任务,返回值为一个Future类型
      <T> Future<T> submit(Callable<T> task);

          //other methods...
  }

借助线程池来运行Callable任务的一般流程为:

代码语言:javascript
代码运行次数:0
运行
复制
   ExecutorService exec = Executors.newCachedThreadPool();
   Future<Integer> future = exec.submit(new MyCallableTask());

通过future可以得到MyCallableTask的call()的运行结果: future.get();

举例说明

例1:

代码语言:javascript
代码运行次数:0
运行
复制
  public class CallableTest {
      public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{
          //创建一个线程池
          ExecutorService executor = Executors.newCachedThreadPool();
          Future<String> future = executor.submit(()-> {
                  TimeUnit.SECONDS.sleep(5);
                  return "CallableTest";
          });
          System.out.println(future.get());
          executor.shutdown();
      }
  }

例2:Callable任务借助FutureTask运行:

代码语言:javascript
代码运行次数:0
运行
复制
  public class CallableAndFutureTask {
      Random random = new Random();
      public static void main(String[] args) {
          Callable<Integer> callable = new Callable<Integer>() {
              public Integer call() throws Exception {
                  return random.nextInt(10000);
              }
          };
          FutureTask<Integer> future = new FutureTask<Integer>(callable);
          Thread thread = new Thread(future);
          thread.start();
          try {
              Thread.sleep(2000);
              System.out.println(future.get());
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }

例3:Callable任务和线程池一起使用,然后返回值是Future:

代码语言:javascript
代码运行次数:0
运行
复制
   public class CallableAndFuture {
       Random random = new Random();
       public static void main(String[] args) {
           ExecutorService threadPool = Executors.newSingleThreadExecutor();
           Future<Integer> future = threadPool.submit(new Callable<Integer>() {
               public Integer call() throws Exception {
                   return random.nextInt(10000);
               }
           });
           try {
               Thread.sleep(3000);
               System.out.println(future.get());
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }

例4:当执行多个Callable任务,有多个返回值时,我们可以创建一个Future的集合:

代码语言:javascript
代码运行次数:0
运行
复制
  class MyCallableTask implements Callable<String> {
      private int id;
      public OneTask(int id){
          this.id = id;
      }
      @Override
      public String call() throws Exception {
          for(int i = 0;i<5;i++){
              System.out.println("Thread"+ id);
              Thread.sleep(1000);
          }
          return "Result of callable: "+id;
      }
  }
  public class Test {

      public static void main(String[] args) {
          ExecutorService exec = Executors.newCachedThreadPool();
          ArrayList<Future<String>> results = new ArrayList<Future<String>>();

          for (int i = 0; i < 5; i++) {
              results.add(exec.submit(new MyCallableTask(i)));
          }

          for (Future<String> fs : results) {
              if (fs.isDone()) {
                  try {
                      System.out.println(fs.get());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              } else {
                  System.out.println("MyCallableTask任务未完成!");
              }
          }
          exec.shutdown();
      }
  }

StopWatch的使用

Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。

代码语言:javascript
代码运行次数:0
运行
复制
  public String call() throws Exception {
  	StopWatch stopWatch = new StopWatch();
  	stopWatch.start("测试StopWatch");
  	//TODO 业务逻辑
  	stopWatch.stop();
  	return "test";
  }

2.4 Lamda 表达式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Lamda 表达式属于函数式编程的概念

  • 理解 Functional Interface(函数式接口)是学习 Java8 Lambda 表达式的关键所在。
  • 函数式接口:说起Lambda,就必须了解函数式接口,因为要使用Lambda,必须在函数式接口上使用。函数式接口:就是一个有且仅有一个抽象方法,但是可以有多个默认方法的接口,这样的接口可以隐式转换为Lambda表达式。一般在函数式接口上都有个注解@FunctionalInterface,该注解的作用类似@Override一样告诉编译器这是一个函数式接口,用于编译期间检测该接口是否仅有一个抽象方法,如果拥有多个则编译不通过。

Lamda 表达式的演进:

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

public class testLambda {

    //3.静态内部类
    static class like2 implements iLike{

        @Override
        public void lamdba() {
            System.out.println("我在学习lamdba-2");
        }
    }



    public static void main(String[] args) {









        iLike like = new like();
        like.lamdba();

        like = new like2();
        like.lamdba();


        //4.局部内部类
        class like3 implements iLike{

            @Override
            public void lamdba() {
                System.out.println("我在学习lamdba-3");
            }
        }

        like=new like3();
        like.lamdba();


        //5.匿名内部类 没有类的名称必须借助接口或者父类
        like=new iLike() {
            @Override
            public void lamdba() {
                System.out.println("我在学习lamdba-4");
            }
        };
        like.lamdba();


         //6.lambda简化
        like=()->{
            System.out.println("我在学习lamdba-5");
        };
        like.lamdba();

    }



}

//1.定义一个函数式接口
interface iLike{

    void lamdba();
}

//2.实现类
class like implements iLike{

    @Override
    public void lamdba() {
        System.out.println("我在学习lamdba-1");
    }
}
在这里插入图片描述
在这里插入图片描述

DEMO:

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

public class testLamdba01 {

    //静态内部类
    static class Love1 implements iLove {


        @Override
        public void love(int a) {
            System.out.println("打印我输入的数字是" + a);
        }
    }


    public static void main(String[] args) {


        iLove love = new Love();
        love.love(20);

        love = new Love1();
        love.love(12);

//匿名类
        love = new iLove() {
            @Override
            public void love(int a) {
                System.out.println("打印我输入的数字是" + a);
            }
        };
        love.love(33);


        //lambba简化
        love = (int a) -> {
            System.out.println("打印我输入的数字是" + a);
        };
        love.love(56);

        //简化参数类型
        love = (a) -> {
            System.out.println("打印我输入的数字是" + a);
        };
        love.love(57);

        //简化中括号
        love = a -> {
            System.out.println("打印我输入的数字是" + a);
        };
        love.love(58);


        //简化花括号
        love = a -> System.out.println("打印我输入的数字是" + a);
        love.love(59);


        //总结:lamdba表达式只能有代码一行的情况下 才能简化一行,多行用大括号包围
          //用户函数是接口
        //函数是接口:任何接口如果只包含唯一一个 抽象方法那么他就是一个函数式接口
    }


}


interface iLove {

    void love(int a);
}

//实现类
class Love implements iLove {


    @Override
    public void love(int a) {
        System.out.println("打印我输入的数字是" + a);
    }
}
在这里插入图片描述
在这里插入图片描述

2.5 静态代理模式

在这里插入图片描述
在这里插入图片描述

多线程 Thread 为代理,Runnable 为被代理对象!底层就是动态代理

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

/**
 * //静态代理模式
 * //真实对象和代理对象都要实现同一接口
 * //代理对象 必须要代理真实对象
 * //好处:
 *     //代理对象可以做很多真实对象做不了的事情
 *     //真实对象专注于做自己的事情
 */
public class testThred06 {


    public static void main(String[] args) {
        You you = new You();
        WeddingCompany company = new WeddingCompany(you);
        company.HappyMarry();

        new Thread( ()-> {System.out.println("我爱你");} ).start();
        new Thread( ()-> System.out.println("我爱你") ).start();
//        new Thread(Runnable::run).start();

        new WeddingCompany(you).HappyMarry(); //  2个用法一样,都是静态代理
        
        
    }



}
interface Marry{
    
    void HappyMarry();
    
}   


class You implements Marry{

    @Override
    public void HappyMarry() {
        System.out.println("老王要结婚不知道他开心不");
    }
}

//代理角色,婚庆公司,帮助你结婚
class WeddingCompany implements Marry{

    //代理对象-->真实目标角色
    private Marry target;


    public WeddingCompany(Marry target){
        this.target=target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();
        after();
    }

    private void after() {
        System.out.println("结婚后");
    }

    private void before() {
        System.out.println("结婚前");
    }
}
在这里插入图片描述
在这里插入图片描述

3、线程的6种状态

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
线程有6种状态,下面有错误: 
public enum State { 
//线程刚创建 
    NEW, 
//在JVM中正在运行的线程 
    RUNNABLE, 
//线程处于阻塞状态,等待监视锁,可以重新进行同步代码块中执行 
    BLOCKED, 
//等待状态 WAITING,
//调用sleep() join() wait()方法可能导致线程处于等待状态 
   TIMED_WAITING, 
//线程执行完毕,已经退出
   TERMINATED; 
}
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;


//观察 线程的状态
public class testState {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(()->{

            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("---等待结束");

            }

        });

        //观察线程的状态  NEW
        Thread.State state = thread.getState();
        System.out.println(state);

        //观察线程的状态   RUNNABLE
        thread.start();
        state = thread.getState();
        System.out.println(state);


    //终止线程的线程状态。线程已完成执行。 只要线程不停止运行他就会进入到此处进行运行
      while (state!=Thread.State.TERMINATED){
          Thread.sleep(100);
          state = thread.getState();//TIMED_WAITING
          System.out.println(state);
      }


    }









}
在这里插入图片描述
在这里插入图片描述

BLOCKED是指线程正在等待获取锁;WAITING是指线程正在等待其他线程发来的通知(notify),收到通知后,可能会顺序向后执行(RUNNABLE),也可能会再次获取锁,进而被阻塞住(BLOCKED)。

3.1 线程的一些常用方法

(Thread t = new Thread)线程 t 的一些方法如下图所示:

在这里插入图片描述
在这里插入图片描述
线程中常用的方法

1、public void start()  使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 2、public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 3、public final void setName(String name) 改变线程名称,使之与参数 name 相同 4、public final void setPriority(int piority) 更改线程的优先级。 5、public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 6、public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 7、public void interrupt() 中断线程。 8、public final boolean isAlive() 测试线程是否处于活动状态。 9、public static void static yield() 暂停当前正在执行的线程对象,并执行其他线程。 10、public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 11、public static Thread currentThread() 返回对当前正在执行的线程对象的引用。

配一张图:

3.1.1 线程休眠——sleep()
  • sleep(时间)指定当前线程阻塞的毫秒数;
  • sleep 存在异常 InterruptedException;
  • sleep 时间达到后线程进入就绪状态;
  • sleep 可以模拟网络延时,倒计时等;
  • sleep 每一个对象都有一个锁,sleep 不会释放锁;

sleep() 方法的用处

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import java.text.SimpleDateFormat;
import java.util.Date;

public class testSleep {


    public static void main(String[] args) throws InterruptedException {

     //   tenDown();

        testDate();
    }


    public static void testDate() throws InterruptedException {


        Date date = new Date(System.currentTimeMillis());//获取系统当前的时间

        while (true){
            //休眠1秒
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
            //上面就获取了一次 每过1s咱们就需要更新一次
            date=new Date(System.currentTimeMillis());//更新当前的时间

        }

    }


    //数字倒计时demo
    public static  void tenDown() {

        int num = 10;

        try {

            while (true) {

                Thread.sleep(1000);
                System.out.println(num--);
                if (num < 0) {
                    break;
                }


            }


        } catch (Exception e) {
            e.printStackTrace();
        }


    }


}

主线程调用子线程的interrupt()方法,导致子线程抛出InterruptedException, 在子线程中catch这个Exception,不做任何事即可从Sleep状态唤醒线程,继续执行。

代码语言:javascript
代码运行次数:0
运行
复制
//new 一个线程
 Thread thread = new Thread(new TestThread(1));
//开启线程
thread .start();

        try {
        //休眠
            Thread.sleep(3000);
            //结束休眠开始继续运行
            thread .interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

\

3.1.2 线程礼让——yield()
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞;
  • 将线程从运行状态转为就绪状态;
  • 让 CPU 从新调度,有可能还是调度该礼让线程。
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

public class testyield {


    public static void main(String[] args) {


        yield yield = new yield();
        new Thread(yield,"a").start();
        new Thread(yield,"B").start();

    }




}


class yield implements Runnable{

    @Override
    public void run() {


        System.out.println(Thread.currentThread().getName()+"开始执行");
        //礼让线程 但是不一定礼让就会成功是一个概率的事件
        //礼让线程,并不能一定成功礼让,只能让运行状态线程变为就绪状态,重新竞争,看CPU心情
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束执行");

    }
}
在这里插入图片描述
在这里插入图片描述
3.1.3 合并线程——Join()

Join 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。 可以想象成插队。

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

//测试Join方法
//想象为插队
//Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
public class TestJoin {


    public static void main(String[] args) throws InterruptedException {
        Join join = new Join();
        Thread thread = new Thread(join);
        thread.start();


        for (int i = 0; i < 50; i++) {
            if (i==30){
                //join插队优先进行运行
                thread.join();
            }
            System.out.println("main"+i);
        }

    }



}
class Join implements Runnable{


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我是vip的线程");
        }

    }
}
在这里插入图片描述
在这里插入图片描述

3.2 停止线程的方式

第一种:标志位

  • 不推荐使用 JDK 提供的 stop ()、destroy()方法。【已弃用】
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量 , 当 flag == false,则终止线程运行。
线程的停止
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;
//测试stop
//1.建议线程正常停止--->利用次数,不建议死循环。
//2.建议使用标志位--->设置一个标志位.
//3.不要使用stop或destroy等过时或JDK不建议使用的方法
public class testStop implements Runnable {

    private Boolean flag = true;

    @Override
    public void run() {

        int i = 0;
        while (flag) {
            System.out.println("执行线程任务进行中"+i++);
        }
    }

    public void stop() {

        this.flag = false;
    }

    public static void main(String[] args) {
        testStop stop = new testStop();
        new Thread(stop).start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
            if (i == 90) {
                stop.stop();
                System.out.println("执行到90停止");
            }

        }

    }
}
在这里插入图片描述
在这里插入图片描述

第二种: interrupt方法

1、直接调用interrupt()方法

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

public class testStop01 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            //直接调用interrupt
            while (!Thread.interrupted()){
                System.out.println("你好啊");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //加入break才是终止执行
                    break;
                }
            }
            System.out.println("停止");
        });
        t1.start();

        Thread.sleep(2000);
        System.out.println("终止");
        //让t1终止 使用此方法
        t1.interrupt();
    }

}
在这里插入图片描述
在这里插入图片描述

2、拿到当前线程在调用interrupt()方法(Thread.currentThread().isInterrupted())

代码语言:javascript
代码运行次数:0
运行
复制
public class ThreadDemo14 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            //拿到当前线程 判断是否终止
        while (!Thread.currentThread().isInterrupted()){
            System.out.println("别烦我");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //加入break才是终止执行
                break;
            }
        }
            System.out.println("转账终止");
        });
        t1.start();

        Thread.sleep(2000);
        System.out.println("有内鬼终止交易");
        //让t1终止 使用此方法
        t1.interrupt();
    }
}
在这里插入图片描述
在这里插入图片描述

注:当调用interrupt方法时,一定要加上break终止代码否则只会出现暂时终止。

在这里插入图片描述
在这里插入图片描述

\

调用interrupt()方法的两种方法的区别 Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志,也就是复位

代码语言:javascript
代码运行次数:0
运行
复制
public class ThreadDemo16 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            while (!Thread.interrupted()){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //获取当前线程
                    Thread curr=Thread.currentThread();
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                    System.out.println("---------------------");
                    Thread.interrupted();
                   //Thread.currentThread().interrupt();
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                }
            }
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}
在这里插入图片描述
在这里插入图片描述

Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志,不会复位

代码语言:javascript
代码运行次数:0
运行
复制
public class ThreadDemo16 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            while (!Thread.interrupted()){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //获取当前线程
                    Thread curr=Thread.currentThread();
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                    System.out.println("---------------------");
                   // Thread.interrupted();
                   Thread.currentThread().interrupt();
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                    System.out.println("打印当前线程状态"+curr.isInterrupted());
                }
            }
        });
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }
}

打印结果

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
补:线程中的重要属性

public class ThreadDemo11 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
          for (int i=0;i<10;i++){
              try {
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
        });
        //重要属性
        System.out.println("线程的ID:"+t1.getId());
        System.out.println("线程的名称:"+t1.getName());
        System.out.println("线程的优先级:"+t1.getPriority());
        System.out.println("线程的状态:"+t1.getState());
        System.out.println("线程的类型:"+t1.isDaemon());
        System.out.println("线程是否存活:"+t1.isAlive());
        t1.start();
在这里插入图片描述
在这里插入图片描述

Thread的中断机制(interrupt) (扩展)

3.3 线程状态观测

Thread.State 线程状态。线程可以处于以下状态之一:

  • NEW 尚未启动的线程处于此状态。
  • RUNNABLE 在 Java 虚拟机中执行的线程处于此状态。
  • BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
  • WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
  • TERMINATED 已退出的线程处于此状态。 一个线程可以在给定时间点处于一个状态。
代码语言:javascript
代码运行次数:0
运行
复制
package com.can.ThreadState;

//观察测试线程的状态
public class TestState {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread( () -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("///");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state); //NEW

            //观察启动后
            thread.start();//启动线程
            System.out.println(thread.getState()); //Run

        while (state != Thread.State.TERMINATED){ //只要线程不终止,就一直输出状态。
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
        //死亡之后的线程是不能在启动的Exception in thread "main" java.lang.IllegalThreadStateException
        //thread.start();

    }
}
3.4 线程优先级
  • Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从1~10. Thread.MIN_PRIORITY=1; Thread.MAX_PRIORITY=10; Thread.NORM_PRIORITY=5;
  • 使用以下方式改变或获取优先级 getPriority().setPriority(int xxx)

优先级的设定建议在 start() 调度前

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;
//测试线程的优先级,由高到低,越高越容易优先,但不一定
//优先级低只意味着获取调度的概念低,并不是优先级低就不会被优先调用,这都是看CPU的调度。
public class testPriority {


    public static void main(String[] args) {
        //主线程 优先级默认 是5 无法修改
        System.out.println("线程名字"+Thread.currentThread().getName()+"线程优先级"+Thread.currentThread().getPriority());

        Priority priority = new Priority();
        Thread t1 = new Thread(priority,"t1");
        Thread t2 = new Thread(priority,"t2");
        Thread t3 = new Thread(priority,"t3");
        Thread t4 = new Thread(priority,"t4");
        Thread t5 = new Thread(priority,"t5");
        Thread t6 = new Thread(priority,"t6");

        //注意需要先设置优先级 然后启动线程
        t1.start();
        //最小优先级 1
        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        //最大线程优先级 10
        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();



    }
}


class Priority implements Runnable{


    @Override
    public void run() {
        System.out.println("线程名字"+Thread.currentThread().getName()+"线程优先级"+Thread.currentThread().getPriority());
    }
}
在这里插入图片描述
在这里插入图片描述

注意:

优先级低只意味着获取调度的概念低,并不是优先级低就不会被优先调用,这都是看CPU的调度。

默认优先级都是5,优先级不能小于1大于10否则会抛出异常

3.5 守护(daemon)线程
  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存垃圾回收等待…
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

//测试守护线程
//上帝守护你
//所有的用户线程结束了,守护线程才会结束了
//当只剩下守护线程时,JVM就退出了,守护线程不依赖于终端,而依赖于系统,与系统同生共死
public class TestDaemon {

    public static void main(String[] args) {
        God god = new God();
        Yous you = new Yous();

        Thread thread = new Thread(god);
        //设置守护线程必须要在线程启动前进行
        thread.setDaemon(true); //默认是false,表示是用户线程,正常线程都是用户线程,设置成true变为守护线程
        thread.start();

        new Thread(you).start(); // 你 用户线程启动..

    }
}

//上帝
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你");
        }
    }
}

//你
class Yous implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着");
        }
        System.out.println("-=========goodbye! world!==========");
    }
}
在这里插入图片描述
在这里插入图片描述
3.6 线程同步(并发)

同一个对象被多个线程同时操作

在这里插入图片描述
在这里插入图片描述

现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队一个个来。

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步.线程同步其实就是一种等待机制,多个需要同时访问!此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

在这里插入图片描述
在这里插入图片描述

形成线程安全的条件:

队列和锁: 在多线程场合下,最重要的就是保障数据的一致性问题,而保障数据一致性问题,就需要借助于锁了。 其实我们在多线程的场景下应该搞清楚一个问题,就是到底什么需要保护?并不是所有的的数据都需要加锁保护,只有那些涉及到被多线程访问的共享的数据才需要加锁保护。 锁的本质其实就是确保在同一时刻,只有一个线程在访问共享数据,那么此时该共享数据就能得到有效的保护。

4、线程同步

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可.存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.
在这里插入图片描述
在这里插入图片描述

4.1 线程不安全举例

下面的售票,取钱示例,那么需要多线程模拟客户去抢票或者取钱。

举例:不安全的售票

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;


//不安全的买票
//线程不安全
//3人 可能会拿到同一张票,甚至在最后一张票的时候可能都会将最后一张票执行,就会有-1
public class UnsafeBuyTick {

    public static void main(String[] args) {


        BuyTicket ticket = new BuyTicket();

        new Thread(ticket,"小王").start();
        new Thread(ticket,"小李").start();
        new Thread(ticket,"小赵").start();
    }
}


class BuyTicket implements Runnable{
  //票
    private  int tickNums=10;

    Boolean flag=true;

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    //判断是否有票
    public void buy() throws InterruptedException {
       //无票;
        if (tickNums<=0){
            flag=false;
        return;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"拿到了,第"+tickNums--);

    }

}
在这里插入图片描述
在这里插入图片描述

举例:银行取钱\

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import com.example.democrud.democurd.Prototype.demo01.Video;

//不安全的取钱
//两人去银行取钱
public class testUnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100, "结婚");
        Drawing you = new Drawing(account,50,"你");
        Drawing girl = new Drawing(account,100,"媳妇");

        you.start();
        girl.start();
    }


}


class Account {

    int money; //余额
    String name;//卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }

}

//银行  模拟取钱
class Drawing extends Thread {

    Account account;//账号
    int drawingMoney;// 取多少钱
    int nowMoney; //现在手里有多少钱
    String name;

    public Drawing(Account account, int drawingMoney, String name) {
        this.account = account;
        this.drawingMoney = drawingMoney;
        this.name = name;

    }


    //取钱
    @Override
    public void run(){

        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"取了"+drawingMoney+"钱不够了抱歉");
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡内余额=余额-取的钱
        account.money=account.money-drawingMoney;
        //手里的钱+取的钱
        nowMoney=nowMoney+drawingMoney;

        System.out.println(account.name+"余额:"+account.money);

        System.out.println(this.name+"手里的钱"+nowMoney);
    }
}
在这里插入图片描述
在这里插入图片描述

举例:线程不安全的集合 可参考:ArrayList为什么是线程不安全的:https://blog.csdn.net/qq_42183409/article/details/100586255

代码语言:javascript
代码运行次数:0
运行
复制
package com.can.syn;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
//不够安全的原因是因为,两个线程同一瞬间,操作了同一个位置上,把两个数组添加到了同一位置,就覆盖掉了,元素就会少,少的元素就这么来的
public class UnsafeList {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            //lambda表达式
            new Thread( ()->{
//                synchronized (list) { 解决并发问题
                    list.add(Thread.currentThread().getName());
//                }
            }).start();
        }
        Thread.sleep(100);
        //输出大小
        System.out.println(list.size());
    }
}

举例:JUC 线程安全的集合:CopyOnWriteArrayList

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import java.util.concurrent.CopyOnWriteArrayList;

public class TestJuc {
    public static void main(String[] args) {
        //list线程安全的
        CopyOnWriteArrayList<String> list= new CopyOnWriteArrayList<String>();

        for (int i = 0; i < 1000; i++) {

            new Thread(()->{

                list.add(Thread.currentThread().getName());

            }).start();

        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }

}
在这里插入图片描述
在这里插入图片描述

4.2 同步方法

这个博客很好,可参考:synchronized的四种用法 修饰方法 Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来

由于我们可以通过关键字 private 关键字来保证数据对象只能被方法访问,所以我们只要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种方法: synchronized 方法和 synchronized 块.

在这里插入图片描述
在这里插入图片描述

同步方法:

代码语言:javascript
代码运行次数:0
运行
复制
public synchronized void method(int args){}
在这里插入图片描述
在这里插入图片描述
  • synchronized 方法控制 “对象” 的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放,后面被阻塞的线程才能获得这个锁,继续执行。 缺陷:若将一个大的方法申明为 synchronized 将会影响效率。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,
synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,
修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。

例如:

方法一

public synchronized void method()
{
   // todo
}
方法二

public void method()
{
   synchronized(this) {
      // todo
   }
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,
锁的对象都是方法的调用者,就是当前类的实例化对象

使用synchronized 同步方法 (同步方法中无需指定同步监视器,因为同步方法的同步监视器默认就是 this ,就是当前类的对象,如果是静态同步方法就是当前类Class

代码语言:javascript
代码运行次数:0
运行
复制
package com.can.syn;

//不安全的买票
//线程不安全
//"苦逼的我","牛逼的你","可恶黄牛"可能会拿到同一张票,甚至在最后一张票的时候可能都会将最后一张票执行,就会有-1
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"苦逼的我").start();
        new Thread(buyTicket,"牛逼的你").start();
        new Thread(buyTicket,"可恶黄牛").start();
    }

}

class BuyTicket implements Runnable{

    private int ticketNums = 10; //票
    private boolean flag = true; //外部停止方式

    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 //此处加油synchronized 更好的解决 线程不安全的问题 加入之后正常
    private synchronized void buy() throws InterruptedException {
        //判断是否有票
        if(ticketNums<=0){
            flag = false;
            return;
        }

        //模拟延时
        Thread.sleep(10);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}

扩展:多线程抢票系统浅析

4.3 同步块

同步块:synchronized(Obj){} Obj 称之为同步监视器

  • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this ,就是当前类的对象本身,如果是静态同步方法就是当前类Class
  • 同步监视器的执行过程
  1. 第一个线程访问,锁定同步监视器,执行其中的代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

当我们需要锁指定的对象(Account)时就需要使用同步块,我们如果使用同步方法锁,锁run()方法,默认会锁住this,即Drawing对象,这不是我们想锁的,所以我们使用同步块。

锁的是发生变化的量
代码语言:javascript
代码运行次数:0
运行
复制
package com.can.syn;

import lombok.experimental.Accessors;

//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {

    public static void main(String[] args) {
        //账户
        Account account = new Account(100,"结婚基金");

        Drawing you = new Drawing(account,50,"你");
        Drawing girlFriend = new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.start();
    }


}


//账户
class Account{
    public int money; //余额
    public String name; //账户

    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }
}

//银行:模拟取款
class Drawing extends Thread{
    Account account; //账户

    //取多少钱
    int drawingMoney;

    //现在手里有多少钱
    int nowMoney;

    public Drawing(Account account,int drawingMoney,String name){
            super(name);
            this.account = account;
            this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        //锁的是变化的对象。即增删改的对象
        synchronized(Account){

        //判断有没有钱
        if(account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"余额不足,取不了");
            //余额不够取得钱,return
            return;
        }

        //模拟延时,放大问题的发生性
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡内余额 = 余额 - 取的钱
        account.money = account.money-drawingMoney;
        //手里的钱 = 手里的钱 + 去的钱
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        //Thread.currentThread().getName() = this.getName,因为继承了Thread,所以有Thread的全部方法
        System.out.println(this.getName()+"手里的钱:"+nowMoney);
    }
}
}
静态方法中的同步块

下面是两个静态方法同步的例子。这些方法同步在该方法所属的类对象上。

代码语言:javascript
代码运行次数:0
运行
复制
public class MyClass {
    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);
       }
    }
  }

这两个方法不允许同时被线程访问。

如果第二个同步块不是同步在 MyClass.class 这个对象上。那么这两个方法可以同时被线程访问。

4.3 死锁

在这里插入图片描述
在这里插入图片描述

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥“两个以上对象的锁”时,就可能会发生“死锁”的问题。

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
在这里插入图片描述
在这里插入图片描述

上述四个条件,只要破坏其任意一个就可避免死锁的发生。

死锁演示:

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

//死锁:多个线程互相用者对象需要的资源,互不释放相互僵持
public class DeadLock {


}


//口红
class Lipstick {

}

//镜子
class Mirror {

    public static void main(String[] args) {
        Makeup makeupA = new Makeup(0,"A女孩");
        Makeup makeupB = new Makeup(1,"B女孩");

        makeupA.start();
        makeupB.start();
    }
}

class Makeup extends Thread {


    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();


    int choice;//选择
    String girlName;//使用化妆品的人

    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    //化妆 互相持有对方的锁
    private void makeup() throws InterruptedException {

        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
                //1s后他下拿到镜子
                synchronized (mirror) {
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }

        } else {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");

                Thread.sleep(1000);
                synchronized (lipstick) {
                    System.out.println(this.girlName + "获得口红的锁");
                }

            }


        }


    }


}

此处就是死锁;他们互相拿着对方需要的东西;互补撒手;你等我 我等你;…

在这里插入图片描述
在这里插入图片描述

避免代码:

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
 //化妆 互相持有对方的锁
    private void makeup() throws InterruptedException {

        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
                //1s后他下拿到镜子
            }
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");

                Thread.sleep(1000);
            }
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
            }

        }

    }

4.4 Lock(可重入锁)

在这里插入图片描述
在这里插入图片描述
  • 从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用 Lock对象充当
  • java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock ,可以显示加锁、释放锁。

synchronized 与 Lock 的对比

在这里插入图片描述
在这里插入图片描述
  • Lock 是显示锁(手动开启和关闭),synchronized 是隐式锁,出了作用域自动释放
  • Lock 只有代码加锁,synchronized 有代码块锁和方法锁
  • 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并具有更好的扩展性(提供更多的子类)
  • Lock > 同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    public static void main(String[] args) {


        Testlock2 testlock2 = new Testlock2();

        new Thread(testlock2, "A").start();
        new Thread(testlock2, "B").start();
        new Thread(testlock2, "C").start();
    }
}


class Testlock2 implements Runnable {
    // 10张票
    int tickNums = 10;


    //定义lock锁 私有不可变
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while (true) {

            try {
                //打开锁
                lock.lock();
                if (tickNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "的票" + tickNums--);

                } else {
                    break;
                }
            } finally {
                lock.unlock(); //关闭锁
            }


        }

    }
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.线程通信

应用场景:生产者和消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
  • 如果仓库中没有产品,则将生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费,直到仓库中再次放入产品为止。
在这里插入图片描述
在这里插入图片描述

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费.
  • 在生产者消费者问题中,仅有synchronized是不够的 synchronized 可阻止并发更新同一个共享资源,实现了同步 synchronized 不能用来实现不同线程之间的消息传递(通信)
在这里插入图片描述
在这里插入图片描述

5.1 解决线程之间通信问题的几个方法

注意:均是 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常 llegalMonitorStateException

在这里插入图片描述
在这里插入图片描述

\

sleep 与 wait 的区别

5.2 解决线程之间通信的方式1:管程法

并发写作模型“生产者/消费者模式”–>管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
代码运行次数:0
运行
复制
package com.sjmp.advanced;


// 生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    }

}

// 生产者
class Productor extends Thread{
    SynContainer container;
    public Productor(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i+"只鸡");
            container.push(new Chicken(i));
        }
    }
}


class SynContainer{
//    需要一个容器的大小
    Chicken[] chickens = new Chicken[10];
//    容器计数器
    int count = 0;


//    生产者放入产品
    public synchronized void push(Chicken chicken){
//        如果容器满了,就需要等待消费者消费
        if (count == chickens.length){
//            通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        chickens[count] = chicken;
        count++;
        this.notifyAll();
    }


//    消费者消费产品
    public synchronized Chicken pop(){
//        判断能否消费
        if(count==0){
//            等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        如果可以消费
        count--;
        Chicken chicken = chickens[count];
//        可以通知消费了
        this.notifyAll();

        return chicken;
    }
}



class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了-->"+container.pop().id+"只鸡");
        }
    }
}



// 产品
class Chicken{
    int id;  //产品编号
    public Chicken(int id){
        this.id = id;
    }
}

5.3 解决线程之间通信的方式1:信号灯法

在这里插入图片描述
在这里插入图片描述

这段代码是一个简单的生产者-消费者模型,其中演员(Player)作为生产者生产节目(voice),观众(Wathcher)作为消费者观看节目。TV 类中的 flag 变量用于控制生产者和消费者之间的配合,flag 为 true 表示可以生产,观众需要等待;flag 为 false 表示可以消费,演员需要等待。

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

public class TestSync {

    public static void main(String[] args) {
        TV tv = new TV();  // 创建一个 TV 实例
        Player player = new Player(tv);  // 创建演员线程
        Watcher watcher = new Watcher(tv);  // 创建观众线程
        player.start();  // 启动演员线程
        watcher.start();  // 启动观众线程
    }
}

// 生产者 - 演员
class Player extends Thread{
    private final TV tv;  // TV 对象

    public Player(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {  // 生产 20 个节目
            if(i % 2 == 0){
                this.tv.play("快乐大本营");  // 生产“快乐大本营”节目
            } else {
                this.tv.play("天天向上");  // 生产“天天向上”节目
            }
        }
    }
}

// 消费者 - 观众
class Watcher extends Thread{
    private final TV tv;  // TV 对象

    public Watcher(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {  // 观看 20 个节目
            tv.watch();  // 观众观看 TV 节目
        }
    }
}

// 产品 - 节目
class TV{
    // 标记,用于控制生产者和消费者之间的配合
    // flag 为 true 表示可以生产,观众需要等待;flag 为 false 表示可以消费,演员需要等待。
    private boolean flag = true;

    private String voice;  // 节目名称

    // 生产者生产节目
    public synchronized void play(String voice){
        while(!flag){  // 如果 flag 为 false,则说明已经有其他生产者在生产,当前线程需要等待
            try {
                this.wait();  // 等待其他线程唤醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("演员表演了: "+voice);
        // 生产完毕,唤醒其他被阻塞的消费者线程
        this.notifyAll();
        this.voice = voice;  // 存储节目名称
        this.flag = false;  // 修改标记,指示可以消费
    }

    // 消费者观看节目
    public synchronized void watch(){
        while(flag){  // 如果 flag 为 true,则说明已经有其他消费者在观看,当前线程需要等待
            try {
                this.wait();  // 等待其他线程唤醒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("观看了: "+voice);
        // 消费完毕,唤醒其他被阻塞的生产者线程
        this.notifyAll();
        this.flag = true;  // 修改标记,指示可以生产
    }
}

这段代码实现了生产者-消费者模型,使用线程同步机制 synchronized 和 wait/notifyAll 方法来控制线程安全和数据共享。其中,演员是生产者,观众是消费者,TV 节目是产品。

为了更好地理解和优化该代码,建议从以下几个方面入手:

添加注释:为代码添加必要的注释,解释类、方法和变量的作用和功能。

简化命名:对于类、方法和变量的命名应简洁明了,能快速表达其含义。

优化代码结构:将多余的空格、括号、换行符等去掉,使代码更加紧凑易读。

增加异常处理:在代码中添加必要的异常处理,防止程序因为异常而崩溃或出错。

尝试使用更简洁的方式:可以使用 Java 并发包中的 Lock 和 Condition 类来替代 synchronized 和 wait/notifyAll 方法,实现更加简洁和高效的线程同步和互斥。

增加日志输出:在代码中增加必要的日志输出,记录程序执行过程中的关键信息,方便调试和排查问题。

引入线程池:当需要创建大量线程时,可以考虑使用线程池来避免频繁创建和销毁线程的开销,提高程序的性能和效率。

通过对代码进行优化和升级,可以使其更加易读易懂、性能更加高效。

5.4 使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。 可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

在这里插入图片描述
在这里插入图片描述

优点: 提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理…

  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间会终止
代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

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

public class Testpool {

    public static void main(String[] args) {
           //创建线程池 大小为10
        ExecutorService service= Executors.newFixedThreadPool(10);


        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());


        //2.关闭链接
        service.shutdownNow();

    }
}


class  MyThread implements  Runnable{


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

5.5 补充内容

代码语言:javascript
代码运行次数:0
运行
复制
package com.sjmp.Thread01;

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


public class ThreadFutureTest {
    public static class CallerTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "Thread-Callable- hello";
        }
    }

    public static void main(String[] args) {
        // 创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        new Thread(futureTask).start();
        try {
            String result = futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

如上代码中的 CallerTask 类实现了 Callable 接口的 call() 方法。在 main 函数内首先创建了一个 FutrueTask 对象(构造函数为 CallerTask 的实例),然后使用创建的 FutrueTask 对象作为任务创建了一个线程并且启动它,最后通过futureTask.get() 等待任务执行完毕并返回结果。

小结:使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过 set 方法设置参数或者通过构造函数进行传递,而如果使用 Runnable 方式,则只能使用主线程里面被声明为 final 的变量。不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么子类不能再继承其他类,而 Runable 则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是 Futuretask 方式可以。

5.6wait() 方法

虚假唤醒

7.回顾总结

代码语言:javascript
代码运行次数:0
运行
复制
package com.example.democrud.democurd.test01;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.function.Function;

public class ThreadNew {


    public static void main(String[] args) {
//1.
        new MyThread1().start();
//2.
        new Thread(new MyThread2()).start();
//3.
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();

        try {
            Integer integer=futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

    //1.继承Thread
    class MyThread1 extends Thread {


        @Override
        public void run() {
            System.out.println("我是继承Thread的方法");
        }
    }

    //2.实现Runnable的接口
    class MyThread2 implements Runnable {


        @Override
        public void run() {
            System.out.println("我是实现Runnable的接口");
        }
    }


    //3.实现callable接口
//Callable<Integer> 中的Integer和重写的call返回值同步的
    class MyThread3 implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("我是实现callable接口");
            return 10000;
        }

    }

这个文章只是简单的一个文章是狂神的视频 ;是我一边学一边写的demo;

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、线程简介
    • 1.1 多任务理解
    • 1.2 多线程理解
    • 1.3 线程与进程
    • 核心概念;
  • 2、线程实现
    • 2.1 第一种:继承 Thread 类,重写 run 方法
    • 2.2 第二种:
      • 继承 Thread 类
      • 实现 Runnable 接口
    • 2.3 第三种:实现 Callable 接口
      • Runnable和Callable的区别(重要)
    • 扩展(重要):
    • Callable接口详解
      • Runnable和Callable的区别:
      • Callable两种执行方式
    • StopWatch的使用
    • 2.4 Lamda 表达式
    • DEMO:
    • 2.5 静态代理模式
  • 3、线程的6种状态
    • 3.1 线程的一些常用方法
      • 线程中常用的方法
      • 3.1.1 线程休眠——sleep()
      • 3.1.2 线程礼让——yield()
      • 3.1.3 合并线程——Join()
    • 3.2 停止线程的方式
      • 线程的停止
      • 3.3 线程状态观测
      • 3.4 线程优先级
      • 3.5 守护(daemon)线程
      • 3.6 线程同步(并发)
  • 4、线程同步
    • 4.1 线程不安全举例
    • 4.2 同步方法
    • 4.3 同步块
      • 锁的是发生变化的量
      • 静态方法中的同步块
    • 4.3 死锁
    • 4.4 Lock(可重入锁)
  • 5.线程通信
    • 5.1 解决线程之间通信问题的几个方法
    • 5.2 解决线程之间通信的方式1:管程法
    • 5.3 解决线程之间通信的方式1:信号灯法
    • 5.4 使用线程池
    • 5.5 补充内容
    • 5.6wait() 方法
  • 7.回顾总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档