并发编程1:线程基础——create、setDaemon、join、interrupt、synchronized及综合应用案例完成一个多线程任务

为什么看源码可以开阔思维,提升架构设计能力?

  • 技术的东西单靠书本和自己思考是很难快速提升的,必须通过看源码,看别人如何设计思考为何这样设计才可以领悟得到。
  • 能力的提升不在于你写了多少代码,做过多少项目,而是给你一个业务场景时,你是否能拿出几种靠谱的解决方案,并且说出各自的优缺点
  • 怎么才能拿得出来?一来靠经验,二来靠归纳总结,而看源码可以快速增加你的经验。

如何看源码?

  • 第一遍:了解并简单使用,知道这个模块的作用,再debug进去看一下调用的逻辑、具体实现、用到哪些类,最好能画出一个大致的架构图出来
  • 第二遍:有重点的debug,知道这些类担任了系统架构图中的哪些功能,使用了什么设计模式实现
  • 第三遍:把主要类的调用时序图以及类图结构画出来,再对着时序图分析调用流程,知道类之间的调用关系以及类的功能及它们相互的依赖关系

1、并发编程基础知识

1.1 进程与线程

进程:操作系统资源分配与调度的基本单位

线程:CPU调度与执行时的基本单位,依赖于时间片轮转

当Java程序启动时,启动了一个JVM就是一个进程,而从main入口开始,就分配了一个非守护线程与多个后台守护线程。

在JVM启动后,从字节码文件加载运行,JVM就为当前进程分配了私有与共享的内存空间。

私有空间:JVM栈、本地方法栈、程序计数器

共享的空间:方法区、堆内存

其中程序计数器就是CPU轮转调度时需要知道当前运行的机器指令的内存地址信息以及当有新的线程插入执行的地址,或执行完成返回后继续恢复执行的指令地址。

JVM栈:存放当前进程里面main栈帧以及一系列的待调用栈帧链信息,每个栈帧都存储了一份局部变量表、操作数栈、动态链接信息、方法出口等

本地方法栈是与第三方(如C/C++)外部调用时需要用的内存空间

方法区:是一块堆外内存,由JVM进行管理的,用来存放字节码所有的变量与常量信息、静态变量、类与方法描述信息等,是直接内存空间,可理解为java程序的所有元信息存放处,供JVM栈调用与引用。

堆内存:对象创建时需要分配与使用了一块内存,是JVM中最活跃的一块内存,从新生代Eden到survivor1、survivor2间不断进行年龄代管理,直到15次后进入Old Memory老年代内存中。一般一些大的对象(如文件对象、数据库连接对象等都是直接放到年老代中)

在这里插入图片描述

eden和survivor默认比例是8:1:1,进行垃圾回收采用的是分代复制算法。每次新生代的使用,会是eden区和一块survivor区。当我们进行垃圾回收的时候,清除正在使用的区域,将其中的存货对象,放入到另一个survivor区域,并进行整理,保证空间的连续。如果对象长时间存活,则将对象移动到老年区。存活下来的对象,他的年龄会增长1。当对象的年龄一次次存活,一次次增长,到达15的时候,这些对象就会移步到老年代。在年轻代执行gc的时候,如果老年代的连续空间小于新生代对象的总大小,就会触发一次full gc。是为了给新生代做担保,保证新生代的老年对象可以顺利的进入到老年代的内存区。

1.2 线程创建与线程的状态

线程创建的三种方式:第一种和第二种无返回值,第三种有返回值

  • 继承Thread类,并重写run()方法
  • 实现Runnable接口,重写run()方法
  • FutureTask,类似Runnable,实现Callable接口,是需要返回一个信息的,常用于异步执行的代码块中

FutureTask异步任务步骤

  • 先用任务类实现Callable接口并重写callable中的call()方法
  • 再用任务类创建FutureTask异步异步任务
  • 启动线程任务
  • 利用异步任务对象.get()返回异步执行结果

线程start()后也并不是马上进行Running状态,而是进入了Ready状态,等待时间片调度到CPU中才开始进入Running状态。

在这里插入图片描述

1.3 三种线程创建方式的演示:银行叫号系统

继承Thread类:

四个柜台,100个号码同时叫号:

package pub.ryan.concurrency.stage1;

public class TicketWindow extends Thread{
    //最大票号
    private static final int MAX_TICKET = 10;
    //当前票号
    private static int index = 1;
    private final String windowName;

    public TicketWindow(String windowName) {
        this.windowName = windowName;
    }

    @Override
    public void run() {
        while (index <= MAX_TICKET){
            System.out.println("当前柜台:" + windowName + ", 当前票号:" + index++);
        }
    }
}

class Bank{
    public static void main(String[] args) {
        new TicketWindow("一号柜台").start();
        new TicketWindow("二号柜台").start();
        new TicketWindow("三号柜台").start();
        new TicketWindow("四号柜台").start();
    }
}
当前柜台:二号柜台, 当前票号:1
当前柜台:二号柜台, 当前票号:4
当前柜台:二号柜台, 当前票号:5
当前柜台:二号柜台, 当前票号:6
当前柜台:二号柜台, 当前票号:8
当前柜台:二号柜台, 当前票号:9
当前柜台:二号柜台, 当前票号:10
当前柜台:一号柜台, 当前票号:3
当前柜台:三号柜台, 当前票号:2
当前柜台:四号柜台, 当前票号:7

实现Runnable接口:

package pub.ryan.concurrency.stage1;

public class TicketWindowRunnable implements Runnable{
    private final int MAX_TICKET = 10;
    private int index = 1;

    @Override
    public void run() {
        while (index <= MAX_TICKET){
            System.out.println("当前柜台:" + Thread.currentThread().getName() + ",  当前票号:" + index++);
        }
    }
}

class BankRunnable{
    public static void main(String[] args) {
        TicketWindowRunnable tr = new TicketWindowRunnable();
        new Thread(tr, "一号柜台").start();
        new Thread(tr, "二号柜台").start();
        new Thread(tr, "三号柜台").start();
        new Thread(tr, "四号柜台").start();
    }
}
当前柜台:一号柜台,  当前票号:1
当前柜台:四号柜台,  当前票号:4
当前柜台:三号柜台,  当前票号:3
当前柜台:二号柜台,  当前票号:2
当前柜台:三号柜台,  当前票号:7
当前柜台:四号柜台,  当前票号:6
当前柜台:一号柜台,  当前票号:5
当前柜台:四号柜台,  当前票号:10
当前柜台:三号柜台,  当前票号:9
当前柜台:二号柜台,  当前票号:8

但多执行几次后会出现:超出最大票号情况

当前柜台:一号柜台,  当前票号:2
当前柜台:一号柜台,  当前票号:4
当前柜台:一号柜台,  当前票号:5
当前柜台:一号柜台,  当前票号:6
当前柜台:一号柜台,  当前票号:7
当前柜台:一号柜台,  当前票号:8
当前柜台:一号柜台,  当前票号:9
当前柜台:一号柜台,  当前票号:10
当前柜台:三号柜台,  当前票号:11
当前柜台:二号柜台,  当前票号:1
当前柜台:四号柜台,  当前票号:3

利用FutureTask任务类,调用实现了Callable接口的类:

package pub.ryan.concurrency.stage1;

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

//创建任务类
public class TicketWindowFutureTask implements Callable<String> {

    //具体异步执行的内容
    @Override
    public String call() throws Exception {
        for (int i = 1; i <= 10; i++) {
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName() + i);
        }
        return Thread.currentThread().getName() + " 【处理完成】";
    }
}

class BankWindowFuture{
    public static void main(String[] args) {
        //创建异步任务
        FutureTask<String> ft = new FutureTask<>(new TicketWindowFutureTask());
        //启动任务
        new Thread(null,ft,"1号柜台:").start();

        //获得执行的结果
        try {
            //等待任务执行完成后,get返回执行的结果
            String result = ft.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    
}
1号柜台:1
1号柜台:2
1号柜台:3
1号柜台:4
1号柜台:5
1号柜台:6
1号柜台:7
1号柜台:8
1号柜台:9
1号柜台:10
1号柜台: 【处理完成】

1.4 线程通知与等待 ——Object的三个方法:wait,notify,notifyAll

为什么wait/notify/notifyAll要放到Object类中?

  • **JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。**如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
  • Object类是所有类的父类,由于继承的原因,所以要给对象加锁最好的方法就是放到Object中

具体看下图对象头即知道为什么java中的锁是对象级的了:

为什么wait/notify/notifyAll必须放到sychronized代码块中?

假设没有应用Synchronized关键字,当消费者线程执行wait操作的同时,生产线线程执行notify,生产者线程可能在等待队列中找不到消费者线程。导致消费者线程一直处于阻塞状态。那么这个模型就要失败了。所以必须要加Synchronized关键字。

等待方遵循的原则: 获取对象的锁,不满足条件就调用wait()方法,条件满足继续执行

通知方原则: 获取对象的锁,改变条件,然后notify

每个对象都有一个监视器锁,这个监视器锁的数据结构如下:wait()底层ObjectMonitor 是一个C++监视器锁,负责调度

ObjectMonitor() {
 _header       = NULL;
 _count        = 0;
 _waiters      = 0,
 _recursions   = 0;
 _object       = NULL;
 _owner        = NULL;  // 这个指向哪个Object,JAVA层面来说,就是哪个Object就获得了锁
 _WaitSet      = NULL;  // 对象调用 wait后,就存在了这里
 _WaitSetLock  = 0 ;
 _Responsible  = NULL ;
 _succ         = NULL ;
 _cxq          = NULL ;
 FreeNext      = NULL ;
 _EntryList    = NULL ;  // 这是一个队列,存储准备获取锁的那堆在排队的Object
 _SpinFreq     = 0 ;
 _SpinClock    = 0 ;
 OwnerIsThread = 0 ;
}

注意: _owner在一个时刻,只会指向一个对象,指到哪个Object就是哪个Object获得到了锁。所以你看上面绿色的原点在一个时刻只会有一个。

在这里插入图片描述

1.enter: 对象进入 EntrySet (也就是在进入 synchronized 方法之前被blocking了),放在ObjectMonitor的

  1. acquire:JAVA层面就是争抢到锁了,比如就是A对象抢到锁了, 内部就是 ObjectMonitor的_owner 指针指向了A
  2. release:java这边调用了 wait方法,比如说A对象
  3. acquire : A对象 wait(10) ,10ms到钟了,或者是被 notify或者notifyAll 叫醒起来抢夺锁了。然后抢到锁了。
  4. release and exit : 正常流程处理完毕,退出。

2、Thread API学习

2.1 构造函数:

在这里插入图片描述

target:线程对象

name:线程名称

group:线程组,默认时使用父类的线程组,线程组的用处主要是用来对整组的线程进行中断或等待、唤醒等操作

stacksize:栈帧的大小,默认为main函数的大小,可自定义,1<<24约为8M

例:测试自定义栈大小

package pub.ryan.concurrency.stage1;

public class CreateThread {
    private static int counter = 1;

    public static void main(String[] args) {
    	//new status
        Thread t1 = new Thread(null, new Runnable() {
            @Override
            public void run() {
                try {
                    add(1);
                } catch (Error e) {
                    System.out.println(counter);
                }
            }

            private void add(int i) {
                counter++;
                add(i + 1);
            }
        }, "测试栈帧", 1 << 30);

        //runnable  或 running 或 blocked 、 waiting 、 terminated
        t1.start();

    }
}

2.2 设置为守护线程setDaemon——将线程进行关联,守护线程作为非守护线程共享非守护线程的协助

public final void setDaemon(boolean on)

将一个线程设置为守护线程,当为守护线程时,它所在的父线程结束后,此守护线程也会相应结束

在未作为守护线程时,主线程运行完成,子线程会继续运行:

package pub.ryan.concurrency.stage1;

public class ThreadSetDaemon {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running!");
                    Thread.sleep(10000);
                    System.out.println(Thread.currentThread().getName() + " is done!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, " 守护线程。 ");

        t1.start();
        System.out.println(Thread.currentThread().getName() + " is done.");
    }
}
main is done.
 守护线程。  is running!
 守护线程。  is done!

当作为守护线程,主线程结束,守护线程也会结束!

package pub.ryan.concurrency.stage1;

public class ThreadSetDaemon {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + " is running!");
                    Thread.sleep(10000);
                    System.out.println(Thread.currentThread().getName() + " is done!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, " 守护线程。 ");

        //作为守护线程
        t1.setDaemon(true);
        t1.start();
        System.out.println(Thread.currentThread().getName() + " is done.");
    }
}
main is done.
 守护线程。  is running!

用途:如主从端的心跳机制

当网络连接起来后,使用守护线程与主控端进行不断检测,只要业务线程还在,心跳检测就会不断与主控端进行通信,一旦业务线程结束,心跳也就跟着结束。

package pub.ryan.concurrency.stage1;

public class ThreadDaemon2 {
    public static void main(String[] args) {
        Thread master = new Thread(()->{
            Thread worker = new Thread(()->{
                try {
                    while (true){
                        System.out.println(" 心跳检测 ");
                        Thread.sleep(1_000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " is  done!");
            }," worker ");
            worker.setDaemon(true);
            worker.start();

            try {
                System.out.println(Thread.currentThread().getName() + " is  running!");
                Thread.sleep(1_000);
                System.out.println(Thread.currentThread().getName() + " is  done!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }," master ");

        master.start();
    }
}

 master  is  running!
 心跳检测 
 心跳检测 
 master  is  done!

若不将worker设置为守护线程,那主线程结束后,worker线程会一直不断在发心跳检测!

2.3 线程join()——精准控制线程执行先后的时间

放弃当前正在执行的线程,并调入对应的线程,例如t1.join()的意思就是:
程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会

package pub.ryan.concurrency.stage1;

public class JoinDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + " is: " + i);
                    Thread.sleep(1_000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, " t1 ");
        
        t1.start();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

上述代码,main先执行完成,再继续执行t1剩下的

如果加入:t1.join()后,则它会先让t1()先执行完成后再继续执行main

package pub.ryan.concurrency.stage1;

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + " is: " + i);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, " t1 ");
        t1.start();
        //先启动起来后再优先将t1线程插队执行
        t1.join();

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
 t1  is: 94
 t1  is: 95
 t1  is: 96
 t1  is: 97
 t1  is: 98
 t1  is: 99
 main0
 main1
 main2

t1.join(1000) : 先让t1执行1秒后再继续当前线程

t1.join(1000,30) : 先让t1执行1秒30纳秒后再继续当前线程

一种特殊的应用:Thread.currentThread().join(); ————让自己的线程一直是Join状态,一直运行

package pub.ryan.concurrency.stage1;

public class CurrentJoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().join();
    }
}

综合例子:从三台电脑上采集数据,等所有数据采集完成后统一保存到指定地方:

package pub.ryan.concurrency.stage1;

public class CaptureData {
    public static void main(String[] args) {
        long start_time = System.currentTimeMillis();
        System.out.printf("系统在%s开始采集数据:", start_time);

        Thread m1 = new Thread(new Capture("m1", 10_000));
        Thread m2 = new Thread(new Capture("m2", 30_000));
        Thread m3 = new Thread(new Capture("m3", 20_000));

        m1.start();
        m2.start();
        m3.start();

        long end_time = System.currentTimeMillis();
        System.out.printf("所有数据在[%s]均已采集完成, 开始保存数据", end_time);
    }
}

class Capture implements Runnable{
    private String serverName;
    private long spendtime;
    public Capture(String serverName, long spendTime){
        this.spendtime =  spendTime;
        this.serverName = serverName;
    }

    @Override
    public void run() {
        try {
            long start_time = System.currentTimeMillis();
            Thread.sleep(spendtime);
            long end_time = System.currentTimeMillis();
            System.out.printf("\n【%s】采集完成,时间从%s到%s结束\n", serverName, start_time, end_time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在未加join前,线程各自完成,由于main线程比较快,会最先完成,再继续执行其它三台电脑的。

系统在1611142650125开始采集数据:所有数据在[1611142650145]均已采集完成, 开始保存数据
【m1】采集完成,时间从1611142650145到1611142660149结束

【m3】采集完成,时间从1611142650145到1611142670145结束

【m2】采集完成,时间从1611142650145到1611142680148结束

加入后:

package pub.ryan.concurrency.stage1;

public class CaptureData {
    public static void main(String[] args) throws InterruptedException {
        long start_time = System.currentTimeMillis();
        System.out.printf("系统在%s开始采集数据:", start_time);

        Thread m1 = new Thread(new Capture("m1", 10_000));
        Thread m2 = new Thread(new Capture("m2", 30_000));
        Thread m3 = new Thread(new Capture("m3", 20_000));

        m1.start();
        m2.start();
        m3.start();
        m1.join();
        m2.join();
        m3.join();

        long end_time = System.currentTimeMillis();
        System.out.printf("所有数据在[%s]均已采集完成, 开始保存数据", end_time);
    }
}

class Capture implements Runnable{
    private String serverName;
    private long spendtime;
    public Capture(String serverName, long spendTime){
        this.spendtime =  spendTime;
        this.serverName = serverName;
    }

    @Override
    public void run() {
        try {
            long start_time = System.currentTimeMillis();
            Thread.sleep(spendtime);
            long end_time = System.currentTimeMillis();
            System.out.printf("\n【%s】采集完成,时间从%s到%s结束\n", serverName, start_time, end_time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

此时就解决了,准备开始前,开始采集,最后采集完成后统一保存数据

系统在1611142785681开始采集数据:
【m1】采集完成,时间从1611142785703到1611142795707结束

【m3】采集完成,时间从1611142785703到1611142805703结束

【m2】采集完成,时间从1611142785703到1611142815704结束
所有数据在[1611142815704]均已采集完成, 开始保存数据

2.4 线程中断interrupt()——停止一个线程的优雅的方式

利用interrupt()和isInterrupted()来取代stop()优雅控制线程中断

如果这个线程被阻塞的调用wait(),wait(long),或wait(long, int) Object类的方法,或join(),join(long),join(long, int),sleep(long),或sleep(long, int),这个类的方法,那么它的中断状态将被清除,它将接收一个InterruptedException

voidinterrupt() Interrupts this thread.
static booleaninterrupted() Tests whether the current thread has been interrupted.
package pub.ryan.concurrency.stage1;

public class InterruptDemo {
    private static final String A = "A";

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + ": is running!");
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                System.out.printf("\n%s收到中断信号,即将退出线程:", Thread.currentThread().getName());
            }

        }, "t1");
        t1.start();
        //先让t1运行1秒后再进入中断
        Thread.sleep(1000);
        //t1开始中断
        t1.interrupt();
        System.out.println("t1中断状态:" + t1.isInterrupted());
    }
}


t1: is running!
t1: is running!
t1: is running!
t1: is running!

t1中断状态:true

t1收到中断信号,即将退出线程

中断线程一旦遇到wait,sleep,join等方法后,就会抛出一个中断异常InterruptException,中断标志就会被清除,配合while条件即可退出当前线程。

综合实例:设计一个多线程程序执行某个操作,如果指定时间还没有执行完成,则强制中断,如果提前完成,则直接结束

应用场景:在一个分布式集群文件拷贝中,如果指定时间内还没有同步完成副本,则强制结束

package pub.ryan.concurrency.stage1;

public class ThreadForce {
    public static void main(String[] args) throws InterruptedException {
        ThreadService service = new ThreadService();
        long start = System.currentTimeMillis();
        service.execute(()->{
//            无休止运行,直到超时后再退出
//            while (true){
//
//            }

            //超时为5s,让其运行3s
            try {
                Thread.sleep(3_000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //特殊情况main线程启动后1s意外退出
        //Thread.sleep(1_000L);
        //Thread.currentThread().interrupt();

        //设置超时时间5s
        service.shutdown(5_000L);
        long end = System.currentTimeMillis();
        System.out.printf("\n共执行了%s秒!", end - start);
    }
}

class ThreadService{
    //主任务线程
    private Thread executeThread;
    //子线程是否执行完成标志
    private boolean isFinished = false;

    //启动执行任务:将传入task作为守护线程执行
    public void execute(Runnable task) {
        //主任务线程new
        executeThread = new Thread(){
            @Override
            public void run() {
                //外部传入的子任务线程new
                Thread runner = new Thread(task);
                //将子任务设置为守护线程
                runner.setDaemon(true);
                //启动守护线程
                runner.start();

                try {
                    //让子任务线程优先执行完成
                    runner.join();
                    //子任务执行完成
                    isFinished = true;
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    //捕获到超时中断,直接退出当前线程
                }
            }
        };
        //启动主任务线程
        executeThread.start();
    }

    //任务mills超时时间
    public void shutdown(long millis)  {
        long start = System.currentTimeMillis();
        while (!isFinished){
            long end = System.currentTimeMillis();
            if(end - start >= millis) {
                System.out.println("任务已超时");
                //中断主任务线程
                executeThread.interrupt();
                //退出当前检测超时
                break;
            }

            //即没超时也没执行结束,作短暂休眠后再不断
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                //e.printStackTrace();
                System.out.println("执行线程被打断");
                break;
            }
        }
        isFinished = false;
    }
}

共执行了3043秒!

2.5 线程同步synchronized——让指定的代码段或方法串行式的运行,避免多线程同时修改

package pub.ryan.concurrency.stage1;

public class SynchronizedDemo {
    private static int index=1;
    private static final int MAX=50;
    private static final Object LOCK = new Object();

    public static void main(String[] args) {
        Runnable runnable = ()->{
            while(true){
                synchronized (LOCK){
                    if(index>MAX)
                        break;
                    try {
                        System.out.println(Thread.currentThread().getName() + " 线程正在执行: " + (index++));
                        Thread.sleep(20L);

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

        };
        Thread t1 = new Thread(runnable,"t1");
        Thread t2 = new Thread(runnable,"t2");
        Thread t3 = new Thread(runnable,"t3");

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

防止票号越界版:

package pub.ryan.concurrency.stage1;

public class TicketWindowRunnableSync implements Runnable {
    private final int MAX_TICKET = 10;
    private int index = 1;
    private final Object MONITOR = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (MONITOR){
                if (index <= MAX_TICKET) {
                    System.out.println("当前柜台:" + Thread.currentThread().getName() + ",  当前票号:" + index++);
                }else{
                    break;
                }
            }
        }
    }
}

class BankRunnableSync {
    public static void main(String[] args) {
        TicketWindowRunnableSync tr = new TicketWindowRunnableSync();
        new Thread(tr, "一号柜台").start();
        new Thread(tr, "二号柜台").start();
        new Thread(tr, "三号柜台").start();
        new Thread(tr, "四号柜台").start();
    }
}
  public pub.ryan.concurrency.stage1.TicketWindowRunnableSync();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field MAX_TICKET:I
      10: aload_0
      11: iconst_1
      12: putfield      #3                  // Field index:I
      15: aload_0
      16: new           #4                  // class java/lang/Object
      19: dup
      20: invokespecial #1                  // Method java/lang/Object."<init>":()V
      23: putfield      #5                  // Field MONITOR:Ljava/lang/Object;
      26: return

  public void run();
    Code:
       0: aload_0
       1: getfield      #5                  // Field MONITOR:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: aload_0
       8: getfield      #3                  // Field index:I
      11: bipush        10
      13: if_icmpgt     68
      16: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: new           #8                  // class java/lang/StringBuilder
      22: dup
      23: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
      26: ldc           #10                 // String 当前柜台:
      28: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokestatic  #12                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
      34: invokevirtual #13                 // Method java/lang/Thread.getName:()Ljava/lang/String;
      37: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      40: ldc           #14                 // String ,  当前票号:
      42: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      45: aload_0
      46: dup
      47: getfield      #3                  // Field index:I
      50: dup_x1
      51: iconst_1
      52: iadd
      53: putfield      #3                  // Field index:I
      56: invokevirtual #15                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      59: invokevirtual #16                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      62: invokevirtual #17                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      65: goto          73
      68: aload_1
      69: monitorexit
      70: goto          86
      73: aload_1
      74: monitorexit
      75: goto          83
      78: astore_2
      79: aload_1
      80: monitorexit
      81: aload_2
      82: athrow
      83: goto          0
      86: return
    Exception table:
       from    to  target type
           7    70    78   any
          73    75    78   any
          78    81    78   any

monitorenter

monitorexit

对照java程序与反汇编代码来看下来的图片就更清楚了:

在这里插入图片描述

现在的加锁是加在判断条件上,这样只要有一个线程进入条件判断开始一直到自增结束,一次只允许一个进程可以串行化操作,保证了判断的准确性。

也可以直接把锁放到run()方法上,如:public synchronized void run() {},如果是这样就会出现哪一个线程最先抢到执行权就会一个线程完成整个任务,彻底变成了串行化执行。

package pub.ryan.concurrency.stage1;

public class TicketWindowRunnableSync implements Runnable {
    private final int MAX_TICKET = 10;
    private int index = 1;
    private final Object MONITOR = new Object();

    @Override
    public void run() {
        while (true) {
            if (ticket()) break;
        }
    }

    private synchronized boolean ticket() {
        if (index <= MAX_TICKET) {
            System.out.println("当前柜台:" + Thread.currentThread().getName() + ",  当前票号:" + index++);
        } else {
            return true;
        }
        return false;
    }
}

class BankRunnableSync {
    public static void main(String[] args) {
        TicketWindowRunnableSync tr = new TicketWindowRunnableSync();
        new Thread(tr, "一号柜台").start();
        new Thread(tr, "二号柜台").start();
        new Thread(tr, "三号柜台").start();
        new Thread(tr, "四号柜台").start();
    }
}

注意:

  • 尽可能少的范围内应用synchronized加锁

  • 同步代码块是要尽量去保护共享数据: index,保证Index的三个阶段:1、get field 2、field=field++ 3、put field,保证此变量从取出到获取全过程串行化运行,防止同时被其他进程获取或修改

  • 也可以使用this来进行加锁,如下:

  • @Override
    public void run() {
        while (true) {
            synchronized (this){
                if (index <= MAX_TICKET) {
                    System.out.println("当前柜台:" + Thread.currentThread().getName() + ",  当前票号:" + index++);
                }else{
                    break;
                }
            }
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pub.ryan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值