Java----多线程笔记

操作系统基本概念(掌握)

例子:

程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)

单线程

package com.cskaoyan._01introduction;

import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 **/

/*
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)

 */
public class Demo {
    public static boolean flag = true;
    public static void main(String[] args) {
        System.out.println("say hello before");
        sayHelloRecycling();
        System.out.println("say hello after");

        System.out.println("wait stop before");
        waitToStop();
        System.out.println("wait stop after");

    }

    private static void waitToStop() {
        Scanner scanner = new Scanner(System.in);
        while (flag) {
            String s = scanner.nextLine();
            if ("gun".equals(s)) {
                flag = false;
                break;
            }
        }
    }

    private static void sayHelloRecycling() {
        while (flag) {
            System.out.println("在吗?");
            // 暂停一下下
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

多线程

package com.cskaoyan._01introduction;

import java.util.Scanner;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 **/

/*
程序不停地在屏幕上输出一句问候的语句(比如“你好”)
“同时”,当我通过键盘输入固定输入的时候,程序停止向屏幕输出问候的语句(比如说输入gun)

 */
public class Demo2 {
    public static boolean flag = true;
    public static void main(String[] args) {
        System.out.println("say hello before");
        sayHelloRecycling();
        System.out.println("say hello after");

        System.out.println("wait stop before");
        waitToStop();
        System.out.println("wait stop after");

    }

    private static void waitToStop() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Scanner scanner = new Scanner(System.in);
                while (flag) {
                    String s = scanner.nextLine();
                    if ("gun".equals(s)) {
                        flag = false;
                        break;
                    }
                }
            }
        }).start();

    }

    private static void sayHelloRecycling() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (flag) {
                    System.out.println("在吗?");
                    // 暂停一下下
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


    }

}

Java----多线程笔记

进程

  • 计算机程序在某个数据集合上的运行活动.进程是操作系统进行资源调度与分配的基本单位
  • 简单理解: 正在运行的程序或者软件

线程

  • 进程中可以有多个子任务,每1个子任务都是1个线程.从执行路劲的角度看,一个线程就是1个执行路径.线程是CPU进行资源调度与分配的基本单位
  • 迅雷下载电影

进程与线程什么关系

  • 线程依赖于进程而存在
  • 1个进程可以有多个线程
  • 线程共享进程的资源

串行

  • 一个任务接一个任务的按顺序执行

并行

  • 在同一个时间点,多个任务同时执行

并发

  • 在同一个时间段内,多个任务同时执行

Java----多线程笔记

java程序运行原理(掌握)

java + 主类类名原理

  • java命令会去创建1个jvm进程
  • jvm进程会去创建1个线程,主线程 main
  • 执行主线程中的main方法

jvm是单线程还是多线程的

jvm是多线程的

  • 有1个main线程
  • 还有1个垃圾回收线程

多线程的实现方式一:继承Thread类(重点)

线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程

文档示例

Java----多线程笔记

步骤

  1. 定义1个类,继承Thread类
  2. 重写run方法
  3. 创建子类对象
  4. 启动start()
package com.cskaoyan._02implone;

/**
 * @description:
 * @author: 景天
 * @date: 2022/1/20 10:25
 **/
/*
1. 定义1个类,继承Thread类
2. 重写run方法
3. 创建子类对象
4. 启动start()
 */
public class Demo1 {
    public static void main(String[] args) {
        // 3. 创建子类对象
        MyThread myThread = new MyThread();
        // 4. 启动start()
        myThread.start();
    }
}

//1. 定义1个类,继承Thread类
class MyThread extends Thread{
    //2. 重写run方法

    @Override
    public void run() {
        System.out.println("子线程执行了!!!");
    }
}

注意事项

  • 执行特点

    • 随机性

    • package com.cskaoyan._02implone;
      
      /**
       * @description:
       **/
      /*
      1. 定义1个类,继承Thread类
      2. 重写run方法
      3. 创建子类对象
      4. 启动start()
       */
      public class Demo2 {
          public static void main(String[] args) {
              // 3. 创建子类对象
              MyThread2 myThread = new MyThread2();
              MyThread2 myThread2 = new MyThread2();
              // 4. 启动start()
              myThread.start();
              myThread2.start();
          }
      }
      
      //1. 定义1个类,继承Thread类
      class MyThread2 extends Thread{
          //2. 重写run方法
      
          @Override
          public void run() {
              for (int i = 0; i < 1000; i++) {
                  System.out.println(i);
              }
          }
      }
      
  • run方法跟start方法有啥区别

    • 直接调用run方法执行结果
      before
      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      after
      
      使用start方法 执行结果
      before
      after
      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
    • run方法根本就没有开辟新的执行路径,还是按照顺序执行的.

    • 直接调用run方法,相当于普通成员方法调用

    • start方法才是真正的去创建线程

    • 只有run方法当中的代码才会执行在子线程中,我们要把我们的代码写到run方法中,并且启动的时候一定是start方法

    • run方法中掉用了别的方法

  • 同1个线程,能否启动多次?

    • 不可以 java.lang.IllegalThreadStateException 同一个线程 多次启动会报异常
  • 谁才代表一个真正的线程?

    • Thread类的对象及其子类的对象才代表1个线程

设置/获取线程名称

String getName() 返回该线程的名称
void setName(String name) 改变线程名称,使之与参数 name 相同。
Thread(String name) 分配新的 Thread 对象。 通过构造方法也可以设置线程名称

线程的默认的名字 Thread-编号

获取主线程名称

static Thread currentThread() 返回对当前正在执行的线程对象的引用
package com.cskaoyan._02implone;

/**
 * @description: 设置获取线程名称
 **/

public class Demo4 {
    public static void main(String[] args) {
        // 获取主线程名字
        Thread thread = Thread.currentThread();
        System.out.println("主线程名 = " + thread.getName());
        // 创建子类对象
        MyThread5 t1 = new MyThread5();
        MyThread5 t2 = new MyThread5();
        // 设置线程名称
        // setName(String name)
        t1.setName("王道彭于晏");
        t2.setName("王道吴彦祖");
        // start方法启动
        t1.start();
        t2.start();
    }
}

// 继承Thread类
class MyThread5 extends Thread{
    // run方法重写

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"------"+i);
        }
    }
}

线程调度(掌握)

什么是线程调度?

系统给线程分配CPU资源的过程

分类

  • 协同式线程调度方式
    • 线程的执行时间由线程本身决定,线程完成了自己的工作,报告系统,切换到别的线程
  • 抢占式线程调度方式
    • 线程的执行时间不由线程决定,由系统决定.谁抢到了谁就执行

java中采用什么调度方式?

抢占式线程调度方式

线程优先级(了解)

操作系统线程优先级

  • 静态优先级 固定值
  • 动态优先级: 正在执行中的线程会随着执行时间的延长,优先级会降低.同时,正在等待的线程会随着等待时间的延长,优先级会升高
  • 静态优先级 + 动态优先级

java中的线程优先级

static int MAX_PRIORITY 线程可以具有的最高优先级。 10
static int MIN_PRIORITY 线程可以具有的最低优先级。 1
static int NORM_PRIORITY 分配给线程的默认优先级。 5

设置获取线程优先级

int getPriority() 返回线程的优先级。
void setPriority(int newPriority) 更改线程的优先级。
package com.cskaoyan._02implone;

/**
 * @description: 线程优先级
 **/

public class Demo5 {
    public static void main(String[] args) {
        // 创建子类对象
        MyThread6 t = new MyThread6();

        // | static int | MAX_PRIORITY        线程可以具有的最高优先级。 10  |
        //| static int | MIN_PRIORITY        线程可以具有的最低优先级。   1 |
        //| static int | NORM_PRIORITY        分配给线程的默认优先级。  5   |

        //setPriority(int pri)
        t.setPriority(Thread.MAX_PRIORITY);

        // 获取线程优先级
        int priority = t.getPriority();
        System.out.println("priority = " + priority);
        // start方法启动

        t.start();
    }
}

class MyThread6 extends Thread{

    // run

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

java中的优先级到底有没有用?

练习:

需求:

创建2个线程,都是打印10个数

1个线程最高优先级 , 另一个线程最低优先级

启动

正常逻辑分析: 高优先级的先执行,低优先级的后执行.高优先级的10个数先打印,低优先级的再打印

结论:

执行结果跟我们的预期不符合

原因:

java中的优先级仅仅是1个静态优先级,仅仅是对操作系统的1个"建议"

操作系统中 : 动态 + 静态

java官方: 线程优先级并非完全没有用,我们Thread的优先级,它具有统计意义,总的来说,高优先级的线程
占用的cpu执行时间多一点,低优先级线程,占用cpu执行时间,短一点

package com.cskaoyan._02implone;

/**
 * @description: java中的优先级有没有用
 **/
/*
需求:

创建2个线程,都是打印10个数

1个线程最高优先级 , 另一个线程最低优先级

启动

正常逻辑分析: 高优先级的先执行,低优先级的后执行.高优先级的10个数先打印,低优先级的再打印
 */
public class Demo6 {
    public static void main(String[] args) {
        // 创建线程对象2个
        MyThread7 t1 = new MyThread7();
        MyThread7 t2 = new MyThread7();
        t1.setName("牛x");
        t2.setName("小垃圾");
        // 设置优先级 1个10   1个1
        t1.setPriority(10);
        t2.setPriority(Thread.MIN_PRIORITY);
        // start方法启动
        t1.start();
        t2.start();
    }
}

// 定义类 继承Thread
class MyThread7 extends Thread{

    // 重写run方法

    @Override
    public void run() {
        // run方法里面打印数
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"------"+i);
        }
    }


}


线程的控制API(掌握)

线程休眠sleep

static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
package com.cskaoyan._03api;

import java.util.concurrent.TimeUnit;

/**
 * @description: 线程休眠
 **/

public class SleepDemo {
    public static void main(String[] args) {
        System.out.println("1");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("2");

        // 创建子类对象
        ThreadSleep threadSleep = new ThreadSleep();
        // start
        threadSleep.start();
    }
}

class ThreadSleep extends Thread{
    // run

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            try {
                // 让程序阻塞(等待)
                Thread.sleep(2000);
                //TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程加入join

void join() 等待该线程终止
没有使用join之前 结果:
main start
main------0
main------1
main------2
main end
Thread-0------0
Thread-0------1
Thread-0------2
Thread-0------3
Thread-0------4
Thread-0------5
Thread-0------6
Thread-0------7
Thread-0------8
Thread-0------9



使用join 结果:
main start
Thread-0------0
Thread-0------1
Thread-0------2
Thread-0------3
Thread-0------4
Thread-0------5
Thread-0------6
Thread-0------7
Thread-0------8
Thread-0------9
main------0
main------1
main------2
main end

等待谁?

等待的是子线程.等待的应该是调用join方法的那个线程

谁等待?

主线程在等待.执行join的这行代码在哪个线程上运行,就是那个线程等待

Java----多线程笔记

package com.cskaoyan._03api;

/**
 * @description: 线程加入
 **/

public class JoinDemo {
    public static void main(String[] args) {
        System.out.println("main start");

        // 创建子类对象
        ThreadJoin threadJoin = new ThreadJoin();
        // start
        threadJoin.start();
        // join()
        try {
            threadJoin.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }

        System.out.println("main end");
    }
}


class ThreadJoin extends Thread{
    // run

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"------"+i);
        }
    }
}

线程礼让yield

static void yield() 暂停当前正在执行的线程对象,并执行其他线程。

练习:

创建2个线程对象,分别打印10个数

A线程打印1 , B线程打印1,A线程打印2 , B线程打印2…

结论:

并不能通过yield做到

原因:虽然当前线程放弃了CPU的执行权,但是仍然参与下轮的CPU竞争.谁抢到谁执行

package com.cskaoyan._03api;

/**
 * @description: 线程礼让
 **/

/*
创建2个线程对象,分别打印10个数

A线程打印1 , B线程打印1,A线程打印2 , B线程打印2......
 */
public class YieldDemo {
    public static void main(String[] args) {
        // 创建2个线程对象
        ThreadYield t1 = new ThreadYield();
        ThreadYield t2 = new ThreadYield();
        t1.setName("A");
        t2.setName("B");
        // start
        t1.start();
        t2.start();
    }
}


class ThreadYield extends Thread{
    // run

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "-----" + i);
            // 执行yield方法
            // 暂停当前正在执行的线程对象,并执行其他线程(包括他自己)
            Thread.yield();
            // java 当中采用的是抢占的线程调度方式
        }
    }
}

守护线程daemon

void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。

线程分类:

  • 用户线程
  • 守护线程

注意

  • 当正在运行的线程都是守护线程时,Java 虚拟机退出。
  • 该方法必须在启动线程前调用。(start) java.lang.IllegalThreadStateException(如果放到start之后会报异常)
package com.cskaoyan._03api;

/**
 * @description: 守护线程
 **/

public class DaemonDemo {
    public static void main(String[] args) {
        // 创建线程对象
        ThreadDaemon t = new ThreadDaemon();

        // setDaemon(boolean on)
        // 将该线程标记为守护线程或用户线程。
        // 将子线程设置为守护线程

        // start
        t.start();
        t.setDaemon(true);

        // main 中 打印3个数字 每打印1个 休眠1s
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "----" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class ThreadDaemon extends Thread{
    //run

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // 每打印1个数休眠1s
            System.out.println(getName() + " -------" + i);
            // sleep
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程中断interrupt/stop

void interrupt() 中断线程。如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。
stop() 该方法具有固有的不安全性
package com.cskaoyan._03api;

/**
 * @description: 中断线程
 **/

public class InterruptDemo {
    public static void main(String[] args) {
        // 创建子类对象
        ThreadInterrupt t = new ThreadInterrupt();
        // start方法启动
        t.start();

        // main 打印3个数
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "----" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 执行中断操作 interrupt()
        // 通过异常处理机制 会产生一个异常
        //t.interrupt();
        // stop 不安全
        t.stop();

    }
}

class ThreadInterrupt extends Thread{

    // run

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"-----"+i);
            // 每打印1个 休眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();

            }
            // 假设这是对数据库的操作
        }
    }
}

补充 安全终止线程

练习:

创建1个子线程,子线程里面要求打印10个数,每打印1个休眠一s

main线程中打印3个数,每打印1个休眠1s, 要求终止线程

使用boolean flag, 默认为true, 设置为false , 表示中断线程

此时 ,子线程中要求把中断信息保存到日志文件log.txt

格式: 2022-01-20 16:06:00 发生了中断

package com.cskaoyan._03api;

import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @description: 安全的终止线程
 **/

/*
创建1个子线程,子线程里面要求打印10个数,每打印1个休眠一s

main线程中打印3个数,每打印1个休眠1s, 要求终止线程

使用boolean flag, 默认为true, 设置为false , 表示中断线程

此时 ,子线程中要求把中断信息保存到日志文件log.txt

格式: 2022-01-20 16:06:00 发生了中断
 */
public class StopSecurity {
    public static void main(String[] args) {
        // 创建线程对象
        ThreadStop t = new ThreadStop();
        // start
        t.start();
        // main线程中打印3个数,每打印1个休眠1s
        for (int i = 0; i < 3; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 中断线程 更改flag的值
        t.flag = false;


    }
}

class ThreadStop extends Thread{
    // 定义成员变量
    boolean flag = true;

    //run

    @Override
    public void run() {
        // 子线程里面要求打印10个数,每打印1个休眠一s
        for (int i = 0; i < 10; i++) {
            // 进行判断
            // 如果为true 正常 打印
            if (flag) {
                System.out.println(getName() + "-----" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                // 如果为false 发生了中断 信息保存到文件当中
                // 此时 ,子线程中要求把中断信息保存到日志文件log.txt
                // 创建FIleWriter对象
                FileWriter fileWriter = null;
                try {
                    fileWriter = new FileWriter("log.txt");
                    //格式: 2022-01-20 16:06:00 发生了中断
                    // 指定格式
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    String date = sdf.format(new Date());
                    // write
                    fileWriter.write(date + getName()+" 发生了中断!");
                    // flush
                    fileWriter.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    // close
                    if (fileWriter != null) {
                        try {
                            fileWriter.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }


        }
    }
}

线程的生命周期(重点)

线程的5种状态

新建

  • 刚new出来的线程对象

就绪

  • start方法启动后,等待CPU的执行权

执行

  • 抢到了CPU的执行权

阻塞

  • 除了没有CPU的执行权,还缺少一些必要的资源,sleep,join

死亡

  • run方法执行完了

状态之间的转换

Java----多线程笔记

多线程实现方式二:实现Runnable接口(重点)

文档示例

Java----多线程笔记

步骤

  1. 实现Runnable接口
  2. 重写run方法
  3. 创建实现了Runnable接口的子类对象
  4. 创建线程对象,Thread对象, 并且把子类对象作为参数传递
  5. start方法启动
package com.cskaoyan._04impltwo;

/**
 * @description: 多线程的实现方式二
 **/

/*
1. 实现Runnable接口
2. 重写run方法
3. 创建实现了Runnable接口的子类对象
4. 创建线程对象,Thread对象, 并且把子类对象作为参数传递
5. start方法启动
 */
public class Demo {
    public static void main(String[] args) {
        //3. 创建实现了Runnable接口的子类对象
        MyRunnable myRunnable = new MyRunnable();
        //4. 创建线程对象,Thread对象, 并且把子类对象作为参数传递
        Thread thread = new Thread(myRunnable);
        //5. start方法启动
        thread.start();
    }
}

// 1. 实现Runnable接口

class MyRunnable implements Runnable{
//2. 重写run方法
    @Override
    public void run() {
        System.out.println("子线程执行了!!");
    }
}

为什么Runnable中的run方法会运行在子线程中?

Java----多线程笔记

class Thread {
    // 定义成员变量
    private Runnable target;
    
    init(){
        //...
        // 左边的是成员变量   右边的是我们传过来的参数
        this.target = target
        
    }
    
    void run(){
        if(target != null){
            target.run()
        }
    }
    
    
}

方式一 VS 方式二

  • 步骤角度:方式一4步, 方式二5步
  • 方式一是通过继承的方式,具有单继承的局限性
  • 方式二是实现接口的方式,仍然能够继承
  • 方式二把执行路径跟在执行路径上要做的事情区别开来(解耦)
  • 方式二更加便于数据共享

多线程仿真如下场景:
假设A电影院正在上映某电影,该电影有100张电影票可供出售,现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。

分析:
3个窗口售票,互不影响,同时进行。多线程 3个线程
3个窗口共同出售这100张电影票 3个线程共享这100张票

方式一:

package com.cskaoyan._05datasecurity;

/**
 * @description:
 **/

/*
分析:
3个窗口售票,互不影响,同时进行。多线程 3个线程
3个窗口共同出售这100张电影票       3个线程共享这100张票
 */
public class Demo {
    public static void main(String[] args) {
        // 创建3个线程
        SellWindow t1 = new SellWindow();
        SellWindow t2 = new SellWindow();
        SellWindow t3 = new SellWindow();
        t1.setName("A窗口");
        t2.setName("B窗口");
        t3.setName("C窗口");
        // start
        t1.start();
        t2.start();
        t3.start();
    }
}

class SellWindow extends Thread{
    // 成员变量
    static int tickets = 100;

    // run
    @Override
    public void run() {
        while (true) {
            // 模拟卖票
            // 判断
            if (tickets > 0) {
                // 增加网络延时
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+"卖了第"+ (tickets--) + "票");
            }
        }
    }
}

方式二:

package com.cskaoyan._05datasecurity;

/**
 * @description:
 **/

/*
分析:
3个窗口售票,互不影响,同时进行。多线程 3个线程
3个窗口共同出售这100张电影票       3个线程共享这100张票
 */
public class Demo2 {
    public static void main(String[] args) {
        // 创建子类对象
        SellWindow2 sellWindow2 = new SellWindow2();

        // 创建3个线程
        Thread t1 = new Thread(sellWindow2);
        Thread t2 = new Thread(sellWindow2);
        Thread t3 = new Thread(sellWindow2);
        t1.setName("A窗口");
        t2.setName("B窗口");
        t3.setName("C窗口");
        // start
        t1.start();
        t2.start();
        t3.start();
    }
}

class SellWindow2 implements Runnable{
    // 成员变量
    int tickets = 100;

    // run
    @Override
    public void run() {
        while (true) {
            // 模拟卖票
            // 判断
            // 分析:为什么出现重复的票
            // 假设此时tickets = 100 ,此时A抢到了CPU的执行权
            // 假设B抢到了CPU的执行权
            if (tickets > 0) {
                try {
                    // 此时A睡觉
                    // 此时B睡了
                    // 此时c睡了

                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+
                        "卖了第"+ (tickets--) + "票");

                // tickets-- 分3步
                // 1.取值 2.-1  3. 重新赋值
                // 假设A执行 取值100
                // B执行 取值100
                // c执行 取值100
            }
        }
    }
}

出现2种情况:

  • 不存在的票
  • 重复的票

Java----多线程笔记

Java----多线程笔记

多线程数据安全问题出现的原因(掌握)

解决多线程数据安全问题(重点)

synchronized

同步代码块

同步方法

静态方法

lock

实现

synchronized VS lock

死锁(掌握)

什么是死锁

死锁的场景

如何解决死锁

方式一

方式二

生产者消费者模型(了解)

线程间通信(重点)

wait

notify

notifyAll

常见问题

sleep与wait方法有什么区别?

完整的线程状态转换图(掌握)

线程工具(掌握)

线程池

三种线程池

多线程的实现方式三:实现Callable接口

定时器

定时器Timer

定时任务TimerTask

上一篇:卡利更新apt源


下一篇:Docker run 命令