秋招突击——7/13——多线程编程(基础知识回顾+编程练习 )

news2024/12/25 13:14:08

文章目录

    • 引言
    • 基础知识
      • Synchronized关键字
        • 使用方式
        • 用于同步方法
        • 针对同步块的方法
        • 静态方法使用
        • 原理解析
      • Volatile
        • 使用方式
        • 实现原理
      • final关键字
    • 编程练习(synchronized就能实现)
      • 双线程轮流打印1-100
        • 个人实现
        • 参考实现
      • 三线程顺序打出1-100
        • 个人实现
        • 参考实现
      • 三个线程分别打印1,2,3,然后执行10次
        • 个人实现
        • 参考实现
      • 线程交叉打印12A34B56C
        • 个人实现
        • 参考实现
      • 交替打印大小写字母
        • 个人实现
        • 参考实现
      • 模仿购票系统
        • 个人实现
        • 参考实现
    • 总结

引言

  • 今天面试字节,让我用Java实现一个多线程编程,控制三个线程完成按照顺序输出1、2、3,给我整郁闷了,我就是背了,看了,但是就是没有写过!今天一定要花时间,把这些东西全部都写一遍,做一遍,以面试题为导向!
  • 这篇文章主要是先补充基础知识,然后针对性的找一些编程题目进行训练。
  • 这里关于JMM并没有深究,单纯是从使用的角度来看的,所以happens-before并没有展开讲,后续有时间在好好讲讲!

基础知识

Java内存模型JMM

  • JMM决定了一个线程对共享变量写入时,能对另一个线程可见。
  • 线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程读写共享变量的副本

Synchronized关键字

使用方式
  • 对于普通方法而言,锁是当前实例对象
  • 对于同步方法块,锁是Synchronized括号里面的指定的对象(可以是this,或者其他传入的实例对象)
  • 对于静态同步方法,锁是当前类的Class对象
用于同步方法
  • 当前的实例对象
public class Main implements Runnable{
  
    @Override
    public void run(){
        method();
    }
	// 这里使用sychronzied直接修饰方法,使用这个实例的所有线程都是的需要获得这个锁
    public synchronized void method(){
        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }

        System.out.println("Thread " + Thread.currentThread().getName() + " is finished");
    }

    public static void main(String[] args) {
        // 定义一个对象实例,所有使用这个实例的线程都是使用一个对象锁
        Main main = new Main();
        Thread t1 = new Thread(main);
        Thread t2 = new Thread(main);
        t1.start();
        t2.start();
    }


}

上述程序共用一个main实例,然后是顺序执行,第一个线程执行完了,然后在执行第二个线程
在这里插入图片描述
分析

  • synchronized修饰一个类非静态方法,默认是使用这个类的具体实例
  • t1和t2是使用同一个类的实例main,两个线程会进行锁的竞争,获得锁之后才能顺利执行
针对同步块的方法
  • synchronized需要传入一个对应的实例,这里是使用了这个代码块所在类的一个实例对象。
public class Main implements Runnable{
    private Object lock = new Object();

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

    public void method(){
//        synchronized (this){
        synchronized (lock){
            System.out.println("Thread " + Thread.currentThread().getName() + " is running");
            try{
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("Thread " + Thread.currentThread().getName() + " is finished");
        }

    }

    public static void main(String[] args) {
        // 定义一个对象实例,所有使用这个实例的线程都是使用一个对象锁
        Main main = new Main();
        Thread t1 = new Thread(main);
        Thread t2 = new Thread(main);
        t1.start();
        t2.start();
    }


}

上述程序和synchronized修饰方法的效果是一致的,不顾需要传入一个单独的对象,当然也可以传入对应的this,也就是当前对象本身作为锁

在这里插入图片描述
分析

  • 上述同步代码块中,synchronized传入的对象是lock,也可传入当前对象this
  • 两个使用同一个实例创建线程t1和t2,会竞争获取对象锁lock,执行成功后,会自动解锁,运行下一个线程。
静态方法使用
  • 对于静态同步方法,使用的对象锁是当前的类Class对象
  • 和同步方法差不多,只不过方法必须要用static进行修饰
public class Main implements Runnable{
//    private Object lock = new Object();

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

    public static synchronized void method(){

            System.out.println("Thread " + Thread.currentThread().getName() + " is running");
            try{
                Thread.sleep(2000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("Thread " + Thread.currentThread().getName() + " is finished");

    }

    public static void main(String[] args) {
        // 定义一个对象实例,所有使用这个实例的线程都是使用一个对象锁
        Main main1 = new Main();
        Main main2 = new Main();
        Thread t1 = new Thread(main1);
        Thread t2 = new Thread(main2);
        t1.start();
        t2.start();
    }


}

在这里插入图片描述
分析

  • 如果synchronized是修饰静态方法static的话,这个锁就是当前类的Class对象,然后只要是使用这个类的实例创建对象的线程,都是必须要竞争这个锁,实现线程同步。
  • 不像前两种方法,必须要使用同样的实例对象,创建线程。
原理解析

加锁原理分析

  • synchronized是基于监视器monitor实现
    • 当一个线程尝试访问一个被synchronized修饰的对象时,一定要先获取对象的monitor监视器
      • 获取成功,执行monitor.enter方法,执行对应操作,执行完毕之后,执行monitor.exit方法
      • 获取失败,执行monitor.exit方法,进入等待队列SynchronizedQueue方法

可重入的实现原理

  • 拥有一个monitor计数器,当线程获取该对象锁之后,计数器会加1,不会出现问题,释放锁会减1,直到计数器为0,才能彻底释放锁。

保证同一个对象中的多个被synchonized方法能够顺序执行

public class Main implements Runnable{
//    private Object lock = new Object();

    @Override
    public void run(){
        method1();
        method2();
        method3();
    }

    public static synchronized void method1(){

        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
        System.out.println("method 1 running");
        System.out.println("Thread " + Thread.currentThread().getName() + " is finished");

    }

    public static synchronized void method2(){

        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
        System.out.println("method 2 running");
        System.out.println("Thread " + Thread.currentThread().getName() + " is finished");

    }

    public static synchronized void method3(){

        System.out.println("Thread " + Thread.currentThread().getName() + " is running");
        System.out.println("method 3 running");
        System.out.println("Thread " + Thread.currentThread().getName() + " is finished");

    }

    public static void main(String[] args) {
        // 定义一个对象实例,所有使用这个实例的线程都是使用一个对象锁
        Main main1 = new Main();
        Main main2 = new Main();
        Thread t1 = new Thread(main1);
        Thread t2 = new Thread(main2);
        t1.start();
        t2.start();
    }


}

通过下述代码可以看出,每一个线程都是先执行方法1,方法2,方法3的顺序执行的
在这里插入图片描述

锁的内存语义

  • 当线程释放锁的时候,JMM会把该线程对应的本地内存中的共享变量,刷新到主内存中
  • 当线程获取锁时,JMM会把该线程对应的本地内存置为无效

锁的升级

  • 偏向锁

    • 不需要加锁解锁,默认只有一个人用
    • 没有加锁解锁消耗
  • 轻量级锁

    • 轻量级锁的状态下,需要通过自旋+CAS,实现抢锁,使用拷贝的方式,将对应的LR锁记录复制到Mark Word中。
    • 会自旋
  • 重量级锁

    • 轻量级锁自旋获取失败之后,就会升级为重量级锁
    • 线程加锁失败后,会进入阻塞状态,等待唤醒。
    • 直接阻塞

Volatile

使用方式
  • 如果一个字段被声明为volatile,就能使所有线程都能看到这个变量的值

未使用volatile的线程同步

import java.util.concurrent.TimeUnit;

public class Main{

    public static void main(String[] args) {

        new Thread("Thread A"){
            @Override
            public void run(){
                while(flag){
                }
                System.out.println("Thread A is finished");
            }
        }.start();

        try{
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Thread main wait for a second");
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("Thread main change the flag");
        flag = false;

    }


}

在这里插入图片描述

上述程序会一直执行,不会退出,因为在Thread A的本地内存中,flag的值一直是true,main线程修改并没有同步到他的线程中

使用volatile的线程同步

import java.util.concurrent.TimeUnit;

public class Main{
    public static volatile boolean flag = true;


    public static void main(String[] args) {

        new Thread("Thread A"){
            @Override
            public void run(){
                while(flag){
                }
                System.out.println("Thread A is finished");
            }
        }.start();

        try{
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Thread main wait for a second");
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("Thread main change the flag");
        flag = false;

    }


}

在这里插入图片描述
使用volatile之后,main修改flag之后,Thread A能够立刻看见,然后退出执行

实现原理

内存JMM模型不同线程的本地存储空间

  • 因为不同线程都有一个本地的内存空间,对于共享变量也会复制一份到本地内存中。
  • 程序出现一直运行的状态是因为本地内存的共享变量没有和主内存中的变量进行同步,即使共享内存修改了,但是本地内存还是原来那个值。

volatile写-读的内存语义

  • 当写一个volatile变量时
    • JMM会立刻将本地内存中的共享变量刷新到主内存中。
  • 当读一个volatile修饰的变量时
    • JMM会将本地内存的变量设置为无效,线程只能从主内存中读取对应的共享变量。

volatile禁止重排序

  • 为了性能优化,JMM会在不改变正确语义的前提下,允许编译器和处理器对指令进行重新排序
  • volatile会在对应的位置加上内存屏障,禁止指令重排。
    至于怎么禁止重排的,没有必要深究,问到了就是不会,总不至于让我写一下!而且有没有需要编码的!

原子性相关

  • volatile不能保证原子性,但是对于long和double的操作会保证原子性
  • volatile会保证先行关系,写操作会在读操作之前执行

final关键字

  • 修改类,这个类不能被继承
  • 修饰方法,这个方法不能被子类重写
  • 修饰参数,无法更改参数引用所指向的对象
  • 修饰变量,这个变量是常量,编译期就确定,后期无法修改(可能不够严谨,但是应付面试足够了!)
    • static final:该字段只占据一段不能改变的存储空间,必须在定义的时候就进行赋值,否则编译器不能通过。

final禁用指令重排

感觉其他的ReentranLock等有点多了,今天有点腻了,看不下去了,明天再看,下面的题目也是使用synchronized就能实现的

编程练习(synchronized就能实现)

双线程轮流打印1-100

  • 一个线程打印奇数,一个线程打印偶数,你写一个程序,控制这两个线程在控制台输出1到100
个人实现
import java.util.concurrent.TimeUnit;

public class Main{
    public static volatile int current = 1;
    public static final Object lock = new Object();

    static class Solution implements Runnable{

        int outputType = 0;

        public Solution(int num){
            outputType = num;
        }

        @Override
        public void run(){

            synchronized (lock){
                while(current < 100){
                    try {
                        while(current % 2 != outputType) lock.wait();
                        System.out.println(current);
                        current++;
                        lock.notifyAll();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }


    public static void main(String[] args) {
       Solution odd = new Solution(1);
       Solution even = new Solution(0);

       Thread t1 = new Thread(odd);
       Thread t2 = new Thread(even);

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


}

在这里插入图片描述

总结

  • 本来想按照一个特定的步骤开始写,先写一个循环输出的线程,然后在进行协调,结果发现,还是不断修改,改回来最初的样子,也算是实现了。
参考实现
import java.util.concurrent.TimeUnit;

public class Main{
    private static int currentNum = 1;  //当前需要打印的数字
    public static final Object lock = new Object();

    static class Solution implements Runnable{

        boolean isOdd ;

        public Solution(boolean isOdd){
            this.isOdd = isOdd;  // 是否是奇数
        }

        @Override
        public void run(){
            while(currentNum < 10){
                synchronized (lock){
                    // 判定阻塞情况,自身是奇数,然后需要输出的数字是偶数
                    // 自身是偶数,然后需要输出的数字是奇数
                    while((isOdd && currentNum % 2 == 0) || (!isOdd && currentNum % 2 == 1)){
                        try{
                            lock.wait();
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                    // 进入当前状态后开始打印

                    System.out.println("Thread odd or even:" + isOdd +" print:" +  currentNum ++);
                    lock.notifyAll();
                }
            }
        }
    }


    public static void main(String[] args) {
       Solution odd = new Solution(true);
       Solution even = new Solution(false);

       Thread t1 = new Thread(odd);
       Thread t2 = new Thread(even);

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

在这里插入图片描述
总结

  • 这里就没有必要使用volatile,因为每一次加锁和解锁,线程都会更新本地内存中的共享变量。
  • 线程之间的配合需要使用wait和notify两个函数进行相互配合。
  • 这个东西写过了就直到,没写过,就是写不出来。

相当于遍历currentNum,从1到100,然后轮流由两个对应奇数和偶数的线程输出

三线程顺序打出1-100

  • 需要实现三个线程交替打印输出,一直输出到100。
个人实现
  • 这里就是分为三个部分,第一个线程负责3n+1,第二个线程负责打印3n+2,第三个负责打印3n+3,控制一下输出的次序就行!
class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object();
    public static int count = 1;

    static class PrintThread implements Runnable{

        // 0: 3n,1: 3n+1,2: 3n+2
        private int threadNum ;
        private int threadName;

        PrintThread(int threadNum,int name){
            this.threadNum = threadNum;
            this.threadName = name;
        }

        @Override
        public void run(){
            while(count < 100) {
                synchronized (lock) {

                    // judge whether the thread is de right one
                    try {
                        while (count % 3 != threadNum) {
                            lock.wait();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // print out the current
                    System.out.println("Thread - " + threadName + " : " + count);
                    count++;

                    // wake up other threads
                    lock.notifyAll();

                }
            }

        }
    }
    public static void main(String[] args) {


        Thread t1 = new Thread(new PrintThread(1,1));
        Thread t2 = new Thread(new PrintThread(2,2));
        Thread t3 = new Thread(new PrintThread(0,3));
        t1.start();
        t2.start();
        t3.start();

    }
}

在这里插入图片描述
整体思路是一致的,就是改一下部分东西

参考实现
  • 这里的思路和我的一致,不过用到了匿名内部类的方法。
  • 在最后输出加了一个判定,保证最终的输出100以及以内的数字
class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static int count = 1;  // 要打印的数字
    private static int turn = 1;// 控制哪个线程输出

    public static void printNum(int offset){
        while(count < 100){
            synchronized (lock){
                // judge whether should print the result
                while(turn % 3 != offset){
                    try{
                        lock.wait();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }

                // judge to print the result
                if(count <= 100){
                    System.out.println(count);
                    count++;
                    turn = (turn + 1) % 3;
                    lock.notifyAll();
                }
            }
        }
    }

    public static void main(String[] args) {


        Thread t1 = new Thread(()->printNum(1));
        Thread t2 = new Thread(()->printNum(2));
        Thread t3 = new Thread(()->printNum(0));
        t1.start();
        t2.start();
        t3.start();

    }
} 
  • 感觉这个代码更加繁琐,不过基本思路都是一样,练习一下完事了!

三个线程分别打印1,2,3,然后执行10次

  • 线程A负责循环打印输出1,线程B负责循环打印输出2,线程C负责循环打印输出3

这题是字节面试的原题

个人实现
  • 跟之前的题目很相似,控制三个线程各自输出对应的值,然后控制一下输出的总的次数就行了
class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static int count = 1;  // 要打印的数字
    private static int turn = 1;// 控制哪个线程输出

    public static void printNum(int offset,int num){
        while(count < 30){
            synchronized (lock){
                // judge whether should print the result
                while(turn % 3 != offset){
                    try{
                        lock.wait();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }

                // judge to print the result
                if(count <= 30){
                    System.out.println(num);
                    count++;
                    turn = (turn + 1) % 3;
                    lock.notifyAll();
                }
            }
        }
    }

    public static void main(String[] args) {


        Thread t1 = new Thread(()->printNum(1,1));
        Thread t2 = new Thread(()->printNum(2,2));
        Thread t3 = new Thread(()->printNum(0,3));
        t1.start();
        t2.start();
        t3.start();

    }
}
  • 没咋改,基本上就是增加了一个变量num,进行输出即可。
参考实现
class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static int count = 1;  // 要打印的数字
    private static int turn = 1;// 控制哪个线程输出

    public static void printNum(int offset,int num){
        for(int i = 1;i <= 10;i ++){
            // control each thead run 10 times
            synchronized (lock){
                // judge whether should print the result
                while(turn % 3 != offset){
                    try{
                        lock.wait();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }

                // judge to print the result
                if(count <= 30){
                    System.out.println(num);
                    count++;
                    turn = (turn + 1) % 3;
                    lock.notifyAll();
                }
            }
        }
    }

    public static void main(String[] args) {


        Thread t1 = new Thread(()->printNum(1,1));
        Thread t2 = new Thread(()->printNum(2,2));
        Thread t3 = new Thread(()->printNum(0,3));
        t1.start();
        t2.start();
        t3.start();

    }
}
  • 他就是加了一个for循环,进行精确控制。

线程交叉打印12A34B56C

  • 创建两个线程,一个线程专门用来的打印数字,一个线程专门用来打印字母,然后写一个多线程程序,控制输出如下顺序12A34B45C…
个人实现
  • 还是两个线程,主要是打印输出的问题,
class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static int count = 1;  // 要打印的数字
    private static int turn = 1;// 控制哪个线程输出

    public static String changeStr(String str){
        StringBuilder res = new StringBuilder("");
        for(int i = 0;i < str.length();i ++){
            res.append((char) (str.charAt(i) + str.length()));
        }
        return res.toString();
    }

    public static void printNum(String output,int offset){
        for(int i = 1;i <= 3;i ++){
            // control each thead run 10 times
            synchronized (lock){
                // judge whether should print the result
                while(turn % 2 != offset){
                    try{
                        lock.wait();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }

                // judge to print the result
                System.out.print(output);
                output = changeStr(output);
                count++;
                turn = (turn + 1) % 2;
                lock.notifyAll();
            }
        }
    }

    public static void main(String[] args) {


        Thread threadNum = new Thread(()->printNum("12",1));
        Thread threadChar = new Thread(()->printNum("A",0));

        threadNum.start();
        threadChar.start();

    }
}

在这里插入图片描述
总结

  • 通过trun来控制不同线程之间的输出打印顺序,每一个线程一个偏移号offset
  • 找到迭代输出的规律,在统一的实现方法中进行修改!
参考实现
  • 这里就写的很明确,写了两个线程,各自负责各自的输出,然后通过一个标志位进行控制,具体实现如下
import java.awt.*;

class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static boolean printNum = true;  // 要打印的数字

    public static void main(String[] args) {


        Thread threadNum = new Thread(()-> {
            for(int i = 1;i <= 26;i ++){
                synchronized(lock){
                    while(!printNum){
                        try{
                            lock.wait();
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                    }


                        System.out.print(i);
                        System.out.print(++i);
                        printNum = false;
                        lock.notifyAll();
                }
            }
        });
        Thread threadChar = new Thread(()-> {
            for(char i = 'A';i <= 'Z';i ++){
                synchronized(lock){
                    while(printNum){
                        try{
                            lock.wait();
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                    }

                        System.out.print(i);
                        printNum = true;
                        lock.notifyAll();
                }
            }
        });

        threadNum.start();
        threadChar.start();

    }
}

总结

  • 可以各个线程各自实现自己的输出,通过一个多线程共享的变量协调线程之间的输出顺序。

交替打印大小写字母

  • 创建两个线程,第一个线程打印大写字母,第二个线程打印小写字母,然后让他们交替输出
个人实现
  • 这个跟上面很像,就是一个输出数字一个输出字母的。
import java.awt.*;

class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static boolean printBig = true;  // 要打印的数字

    public static void main(String[] args) {


        Thread threadBig = new Thread(()-> {
              for(char i = 'a';i <= 'z';i ++){
                synchronized(lock){
                    while(printBig){
                        try{
                            lock.wait();
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                    }

                        System.out.print(i);
                        printBig = true;
                        lock.notifyAll();
                }
            }
        });
        Thread threadSmall = new Thread(()-> {
            for(char i = 'A';i <= 'Z';i ++){
                synchronized(lock){
                    while(!printBig){
                        try{
                            lock.wait();
                        }catch(InterruptedException e){
                            e.printStackTrace();
                        }
                    }

                        System.out.print(i);
                        printBig = false;
                        lock.notifyAll();
                }
            }
        });

        threadBig.start();
        threadSmall.start();

    }
}

在这里插入图片描述
总结

  • 其实这类题目还是蛮好做的,就是记住一个范式就行了!
参考实现
  • 这里使用使用了一个函数类来实现的,通过toLowerCase来实现小写的转换

  • 这里就不写了,基本上大差不差!

模仿购票系统

  • 模仿购票系统,目前有500张票,同时有4个窗口,模拟购票过程,打印购票结果,窗口1购买一张票,剩余499张票。
个人实现
  • 4 个窗口,对应4个线程
  • 票总数是500,这个应该对应的是需要互斥访问并且修改的变量,并不需要wait和notifyAll,只需要使用synchronized关键字就行,控制互斥访问。
import java.awt.*;

class Main{
    // 实现三个线程交替输出1到100

    // define the lock Object
    public static final Object lock = new Object(); // 同步的锁对象
    public static boolean printBig = true;  // 要打印的数字

    static class TicketSystem implements Runnable{
        static int ticketNum = 500;


        public synchronized void buyTicket(){
            System.out.println(Thread.currentThread().getName() + " buy ticket " + ticketNum-- + " left " + ticketNum);
        }

        @Override
        public void run(){
            while(ticketNum > 0){
                buyTicket();
            }

        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new TicketSystem(), "1");
        Thread t2 = new Thread(new TicketSystem(), "2");
        Thread t3 = new Thread(new TicketSystem(), "3");
        Thread t4 = new Thread(new TicketSystem(), "4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

在这里插入图片描述

参考实现
  • 它实现的更加具体,还模拟了购票之后的随机操作,确实更加具体,还模拟了购买票之后的随机操作。
  • 该方法使用的同步代码块实现的
import java.awt.*;

class Main{
    // 实现三个线程交替输出1到100
    public static final int TICKET_NUM = 500;
    public static int resTicket = TICKET_NUM;
    public static final Object lock = new Object();

    static class TicketSystem implements Runnable{

        public void buyTicket(){
            System.out.println("Windows:" + Thread.currentThread().getName() +
                    " buy ticket " + 1 + " left " + resTicket--);
        }

        @Override
        public void run(){
            while(resTicket > 0) {
                synchronized (lock) {
                    if (resTicket > 0) buyTicket();
                    else System.out.println("Windows:" + Thread.currentThread().getName() + " sold out");
                }
            }

            try {
                Thread.sleep((long) (Math.random() * 10));
            }catch(InterruptedException e){
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new TicketSystem(), "1");
        Thread t2 = new Thread(new TicketSystem(), "2");
        Thread t3 = new Thread(new TicketSystem(), "3");
        Thread t4 = new Thread(new TicketSystem(), "4");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

在这里插入图片描述

总结

  • 这里关于JMM并没有深究,单纯是从使用的角度来看的,所以happens-before并没有展开讲,后续有时间在好好讲讲!

  • 后续还有AtomicInteger实现安全递增和ReentranLock两种方式,以及线程池,明天在学吧。

  • 后续对应的题目分别是

    • 假设有三个线程,如何保证三个线程按照顺序执行,每一个线程都是在前一个线程执行完毕后在继续执行。
    • 多种方式实现线程交叉打印12A34B56C,多种实现,主要是使用reentranlock
    • 批次任务执行
  • 对于我这种快速入门,纯靠背的人来说,最怕的就是这种具体的编程题,如果你让我做算法,完全没有问题,但是就是这种偏向于具体某种业务实现的,真的很害怕!害怕归害怕,还是得练!

  • 这一类题目还是很好的实现的

    • 确定每一个线程应该干什么,最好写成一个函数
    • 确定多个线程之间协同的变量,声明为静态变量,在同步方法块中操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1924506.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

笔记 4 :linux 0.11 中继续分析 0 号进程创建一号进程的 fork () 函数

&#xff08;27&#xff09;本条目开始&#xff0c; 开始分析 copy_process () 函数&#xff0c;其又会调用别的函数&#xff0c;故先分析别的函数。 get_free_page &#xff08;&#xff09; &#xff1b; 先 介绍汇编指令 scasb &#xff1a; 以及 指令 sstosd &#xff1a;…

[USACO24OPEN] Smaller Averages G (单调性优化dp)

来源 题目 Bessie 有两个长度为 N的数组&#xff08;1≤N≤500&#xff09;。第一个数组的第 i 个元素为 ai​&#xff08;1≤ai​≤10^6&#xff09;&#xff0c;第二个数组的第 i个元素为bi​&#xff08;1≤bi​≤10^6&#xff09;。 Bessie 希望将两个数组均划分为若干非空…

大数据开发中的数据驱动决策:关键问题与实践指南

目录 决策前的准备工作1. 我已经掌握了哪些信息&#xff1f;2. 我们已经做出决定了吗&#xff1f;3. 我们需要哪些额外信息以及何时需要&#xff1f; 决策过程中的关键问题1. 我们需要做这个决定吗&#xff1f;2. 错误地做出这个决定的代价是什么&#xff1f; 决策后的反思1. 我…

主机安全-开源HIDS字节跳动Elkeid安装使用

目录 概述什么是HIDSHIDS与NIDS的区别EDR、XDR是啥&#xff1f; Elkeid架构Elkeid Agent && Agent centerElkeid DriverElkeid RASPElkeid HUBService DiscoveryManager安装数据采集规则&告警 参考 概述 什么是HIDS HIDS&#xff08; host-based intrusion detec…

CentOS7.X系统部署Zabbix6.0版本(可跟做)

文章目录 一、部署环境说明二、基本环境部署步骤1、环境初始化操作2、部署并配置Nginx3、部署并配置PHP4、测试NginxPHP环境5、部署并配置MariaDB 三、Zabbix-Server部署步骤1、编译安装Zabbix-Server2、导入Zabbix初始化库3、配置Zabbix前端UI4、启动Zabbix-Server5、WEB页面配…

基于 BERT+BILSTM 实现情感分析分类(附源码)

目录 一、数据集 二、数据清洗和划分 2.1 安装依赖 2.2 清洗和划分 三、下载 Bert 模型 四、训练和测试模型 本文主要基于 Bert 和 BiLSTM 实现情感分类&#xff0c;其中参考了多个博客&#xff0c;具体见参考链接。 源码已上传Gitee : bert-bilstm-in-Sentiment-classi…

基于JavaSpringBoot+Vue+uniapp微信小程序校园宿舍管理系统设计与实现

基于JavaSpringBootVueuniapp微信小程序实现校园宿舍管理系统 目录 第一章 绪论 1.1 研究背景 1.2 研究现状 1.3 研究内容 第二章 相关技术介绍 2.1 Java语言 2.2 HTML网页技术 2.3 MySQL数据库 2.4 Springboot 框架介绍 2.5 VueJS介绍 2.6 ElementUI介绍 第三章 系…

pytorch训练的时候 shm共享内存不足,导致训练停止

1.查看shm情况 df -h /dev/shm内存已经满了&#xff0c;因为之前训练多次训练意外停止到shm中的缓存不能及时被清理 2、手动清理shm 依然没被释放 3、查看关联的进程&#xff0c;一个一个kill lsof |grep deletedkill -9 46619 44618 44617 。。。。。4、搞定

3011.力扣每日一题7/13 Java(冒泡排序)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 冒泡排序 解题思路 解题过程 时间复杂度 空间复杂度 冒泡排序 冒…

jenkins系列-07.轻易级jpom安装

jpom是一个容器化服务管理工具&#xff1a;在线构建&#xff0c;自动部署&#xff0c;日常运维, 比jenkins轻量多了。 本篇介绍mac m1安装jpom: #下载&#xff1a;https://jpom.top/pages/all-downloads/ 解压&#xff1a;/Users/jelex/Documents/work/jpom-2.10.40 启动前修…

[论文阅读]MaIL: Improving Imitation Learning with Mamba

Abstract 这项工作介绍了mamba模仿学习&#xff08;mail&#xff09;&#xff0c;这是一种新颖的模仿学习&#xff08;il&#xff09;架构&#xff0c;为最先进的&#xff08;sota&#xff09;变换器策略提供了一种计算高效的替代方案。基于变压器的策略由于能够处理具有固有非…

思维+构造,CF 1059C - Sequence Transformation

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1059C - Sequence Transformation 二、解题报告 1、思路分析 n 1&#xff0c;2&#xff0c;3的情况从样例已知 考虑n > 4的情况 我们考虑要字典序最大&#xff0c;自然要最早出现非1的数&#xff0c;…

老物件线上3D回忆展拓宽了艺术作品的展示空间和时间-深圳华锐视点

在数字技术的浪潮下&#xff0c;3D线上画展为艺术家们开启了一个全新的展示与销售平台。这一创新形式不仅拓宽了艺术作品的展示空间&#xff0c;还为广大观众带来了前所未有的观赏体验。 3D线上画展制作以其独特的互动性&#xff0c;让艺术不再是单一的视觉享受。在这里&#x…

【香菇带你学Linux】Linux环境下gcc编译安装【建议收藏】

文章目录 0. 前言1. 安装前准备工作1.1 创建weihu用户1.2 安装依赖包1.2.1 安装 GMP1.2.2 安装MPFR1.2.3 安装MPC 2. gcc10.0.1版本安装3. 报错解决3. 1. wget下载报错 4. 参考文档 0. 前言 gcc&#xff08;GNU Compiler Collection&#xff09;是GNU项目的一部分&#xff0c;…

Leetcode-203-移除链表元素-临时变量作用域-c++

题目详见https://leetcode.cn/problems/remove-linked-list-elements/ 题解代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullpt…

动手学深度学习(Pytorch版)代码实践 -注意力机制-Transformer

68Transformer 1. PositionWiseFFN 基于位置的前馈网络 原理&#xff1a;这是一个应用于每个位置的前馈神经网络。它使用相同的多层感知机&#xff08;MLP&#xff09;对序列中的每个位置独立进行变换。作用&#xff1a;对输入序列的每个位置独立地进行非线性变换&#xff0c…

【Python】数据分析-Matplotlib绘图

数据分析 Jupyter Notebook Jupyter Notebook: 一款用于编程、文档、笔记和展示的软件。 启动命令&#xff1a; jupyter notebookMatplotlib 设置中文格式&#xff1a;plt.rcParams[font.sans-serif] [KaiTi] # 查看本地所有字体 import matplotlib.font_manager a sorted…

《昇思25天学习打卡营第17天|K近邻算法实现红酒聚类》

K近邻算法原理介绍 K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;最初由 Cover和Hart于1968年提出是机器学习最基础的算法之一。它正是基于以上思想&#xff1a;要确定一个样本的类别&#xff0c;可以计算它与所…

Linux-指令

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

韦东山嵌入式linux系列-具体单板的 LED 驱动程序

笔者使用的是STM32MP157的板子 1 怎么写 LED 驱动程序&#xff1f; 详细步骤如下&#xff1a; ① 看原理图确定引脚&#xff0c;确定引脚输出什么电平才能点亮/熄灭 LED ② 看主芯片手册&#xff0c;确定寄存器操作方法&#xff1a;哪些寄存器&#xff1f;哪些位&#xff1f;…