Java多线程

目录

进程与线程

运行机制

线程的创建和启动

线程生命周期

线程常用方法

线程的调度

线程同步

单例模式

线程死锁

Object类的wait(),notify(),notifyAll()

进程与线程

  • 进程是指运行中的应用程序,每一个进程都有自己独立的内存空间,是一个实体的存在

  • 线程是指进程中的一个执行流程,有时也称为执行情景

  • 进程可以有多个线程组成,即使在同一个进程可以同时运行多个不同的线程,分别执行不同的任务

    • 好比如一辆火车-火车就是一个进程,线程则是由多个车厢组成

  • 当进程内的多个线程同时运行,这种运行方式称为并发

  • 单线程:同一时刻,只允许执行一个线程

  • 多线程:同一时刻,可以执行多个线程,比如:一个qq进程,可以打开多个聊天窗口。一个百度网盘可以同时下载多个文件

相关概念:

  • 并发:同个时刻,多个任务交替进行,造成一种同时的错觉

    • 单核CPU实现的任务就是并发

  • 并行:同个时刻,多个任务同时进行

    • 多核CPU可以实现并行

运行机制

每个线程都有一个独立的程序计数器和方法调用栈(method invocation stack):程序计数器:也称为PC寄存器,当线程执行一个方法时,程序计数器指向方法区中下一条要执行的字节码指令。

方法调用栈:简称方法栈,用来跟踪线程运行中一系列的方法调用过程,栈中的元素称为栈桢。每当线程调用一个方法,就会向方法栈压入一个新桢,桢用来存储方法的参数、局部变量和运算过程中的临时数据

  • 方法调用栈:简称方法栈,用来跟踪线程运行中一系列的方法调用过程,栈中的元素称为栈帧,每当线程调用一个方法,就会向方法压入一个新帧,帧用来存储方法的参数,局部变量和运算过程中的临时数据

  • 栈区先是为空,当调用main方法时,便将main方法压入栈,称为栈帧,后调用method方法,就将method方法加入栈

线程的创建和启动

创建线程有两种方式

  1. 继承Thread类

  2. 实现Runnable接口

继承Thread

  • Thread类代表线程类,它的最主要的两个方法

  • run()包含线程运行时所执行的代码

  • start()用于启动线程

  • 特性

    • 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常吧run()方法的主体称为线程体

    • 通过Thread方法的start()方法来启动这个线程,而非直接调用run()

  • 创建一个继承于Thread类的子类

  • 重写Thread类的run()方法

  • 创建Thread类的子类的对象

  • 通过此对象调用start()来启动一个线程

 public class Machine extends Thread{
     @Override
     public void run(){
         for(int i=0; i < 50; i++){
             System.out.println(i);
         }
     }
   
     public static void main(String args[]){
         Machine machine = new Machine();
         machine.start();  //启动machine线程
     }   
 }

例题

打印一百以内的偶数,一百以内的基数

 public class ThreadTest {
     public static void main(String[] args) {
         MyThread1 m1 = new MyThread1();
         MyThread2 m2 = new MyThread2();
         m1.start();
         m2.start();
     }
 }
 ​
 class MyThread1 extends Thread{
     @Override
     public void run(){
         for (int i = 0; i < 100; i++) {
             if(i % 2 == 0){
                 System.out.println(Thread.currentThread().getName() +":"+ i);
             }
         }
     }
 }
 ​
 class MyThread2 extends Thread{
     @Override
     public void run(){
         for (int i = 0; i < 100; i++) {
             if(i % 2 != 0){
                 System.out.println(Thread.currentThread().getName() +":"+ i);
             }
         }
     }
 }

实现Runnable

  • 创建一个实现Runnable接口的类

  • 实现类去实现Runnable接口中的抽象方法:run()

  • 创建实现类的对象

  • 将此对象作为参数传到Thread类的构造器中,创建Thread类的对象

  • 通过Thread类的对象调用start()方法

 public class Machine implements Runnable{
     @Override
     public void run(){
         for(int i = 0; i < 50; i++){
             System.out.println(Thread.currentThread().getName() +":"+ i);
             try{
                 Thread.sleep(100);
             }catch(InterruptedException e){
                 throw new RuntimeException(e);
             }
         }
     }
     public static void main(String args[]){
         Machine machine = new Machine();
         Thread t1 = new Thread(machine);
         Thread t2 = new Thread(machine);
         t1.start();
         t2.start();
     }
 }
 

Thread和Runnable区别

  • 通过继承Thread或者实现Runnable接口来创建线程本质上没有区别

  • 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制

  • 相同点两种方式都需要重写run()方法,线程的执行逻辑都在run()方法中

列题

窗口卖票 -----

 public class ThreadPracticeTest {
     public static void main(String[] args) {
         Window t1 = new Window();
         Window t2 = new Window();
         Window t3 = new Window();
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window extends Thread {
 ​
     private static int ticket = 100;
 ​
     @Override
     public void run() {
         while(true){
             if(ticket > 0){
                 System.out.println(Thread.currentThread().getName() +"---"+ ticket);
                 ticket --;
             }else{
                 break;
             }
         }
     }
 }
 ​

更换Runnable

 public class ThreadPracticeTest {
     public static void main(String[] args) {
         Window w1 = new Window();
 ​
         Thread t1 = new Thread(w1);
         Thread t2 = new Thread(w1);
         Thread t3 = new Thread(w1);
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
         
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window implements Runnable {
 ​
     private int ticket = 100;
 ​
     @Override
     public void run() {
         while(true){
             if(ticket > 0){
                 System.out.println(Thread.currentThread().getName() +"---"+ ticket);
                 ticket --;
             }else{
                 break;
             }
         }
     }
 }

线程生命周期

  • 新建状态(New):new语句创建的线程对象处于新建状态,此时它和其它对象一样,仅仅在堆区中被分配了内存

  • 就绪状态(Runnable):当一个线程对象创建后,其它线程调用它的start()方法。该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈,,等待CPU的使用

  • 运行状态(Running):运行状态的线程占用CPU执行程序代码,在并发运行环境中,如果计算机只有一个CPU,那么任何是可只会有一个线程处于这个状态,如果计算机有多个CPU,那么同一时刻可以让几个线程占用不同的CPU,使它们都处于运行状态

  • 阻塞状态(Blocked):指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态

    • 分为三种

      • 位于对象等待池中的阻塞状态(Blocked in object’s wait pool):当线程处于运行状态,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中

      • 位于对象锁池中的阻塞状态(Blocked in object’s lock pool):当线程处于运行状态,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其它线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中。

      • 其他阻塞状态(Otherwise Blocked):当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求,就会进入这个状态

  • 死亡状态(Dead): 当线程退出run()方法,就进入死亡状态,该线程结束生命周期。线程有可能是正常执行完run()方法而退出,也有可能是遇到异常而退出

 

线程常用方法

  • start() 启动当前线程,调用当前线程的run()

  • run() 通过需要重写Thread类中的此方法,将创建线程要执行的操作声明在此方法中

  • currentThread() 静态方法,返回当前代码执行的线程

  • getName() 获取当前线程的名字

  • setName() 设置当前线程的名字

  • yield() 释放CPU执行权

  • join() 在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b执行完以后,线程a才结束阻塞状态

  • stop() 已过时,当执行此方法时,强制结束当前线程

  • sleep(long militime) 让线程睡眠指定的毫秒数,在指定时间内,线程时阻塞状态

  • isAlive() 判断当前线程是否存活

  • setPriority() 更改线程的优先级

  • getPriority() 获取线程的优先级

 public class ThreadMethodTest {
     public static void main(String[] args) {
         Thread1 t1 = new Thread1();
 ​
         t1.start();
 ​
         Thread.currentThread().setName("主线程"); //设置线程名
         for (int i = 0; i < 100; i++) {
             if(i % 2 != 0){
                 System.out.println(Thread.currentThread().getName() +":"+ i);
             }
             if(i == 20){
 //                Thread.yield();
 //                System.out.println("释放CPU"+ i);
 //                try {
 //                    System.out.println("阻塞");
 //                    Thread.currentThread().join();
 //                } catch (InterruptedException e) {
 //                    e.printStackTrace();
 //                }
             }
         }
         Thread.currentThread().isAlive();
     }
 }
 ​
 class Thread1 extends Thread {
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             if(i % 2 == 0){
                 try {
                     sleep(1000);
                     System.out.println(Thread.currentThread().getName() +":"+ i);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
     }
 }

线程的调度

  • 同优先级线程组成先进去先出列(先到先服务),使用时间片策略

  • 对高优先级,使用优化制度的抢占式策略

线程优先级等级

  • MAX_PRIORITY:10

  • MIN_PRIORITY:1

  • NORM_PRIORITY:5

涉及的方法

  • getPriority() 返回线程优先值

  • setPriority(int newPriority) 改变线程优先级

说明:

  • 线程创建时继承父线程的优先级

  • 低优先级只是获得调度的概率低,反之只是概率高了,并不是百分百优先,百分百低优先

 public class ThreadPriorityTest {
     public static void main(String[] args) {
         ThreadOne t1 = new ThreadOne();
         t1.setName("子线程");//设置线程名称
         t1.setPriority(Thread.MAX_PRIORITY);//设置线程调度
         t1.start();
 ​
         Thread.currentThread().setName("主线程");
         Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
         for (int i = 0; i < 100; i++) {
             System.out.println(Thread.currentThread().getName() +"-"+ Thread.currentThread().getPriority()+"--"+ i);
         }
     }
 }
 ​
 class ThreadOne extends Thread{
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             System.out.println(getName() +"-"+ getPriority() +"--"+ i);
         }
     }
 }

线程同步

多线程的安全性问题

  • 多个线程执行的不确定性硬气执行结果的不稳定性

  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据

  • 多个线程访问共享的数据时可能存在安全性问题

线程的安全问题Demo: 卖票过程中出现了重票和错票的情况 (以下多窗口售票demo存在多线程安全问题)

 public class ThreadPracticeTest {
     public static void main(String[] args) {
         Window w1 = new Window();
 ​
         Thread t1 = new Thread(w1);
         Thread t2 = new Thread(w1);
         Thread t3 = new Thread(w1);
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window implements Runnable {
 ​
     private int ticket = 100;
 ​
     @Override
     public void run() {
         while(true){
             if(ticket > 0){
                 try {
                     Thread.sleep(100);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(Thread.currentThread().getName() +"---"+ ticket);
                 ticket --;
             }else{
                 break;
             }
         }
     }
 }

错误:当票数为1的时候,三个线程中有线程被阻塞没有执行票数-1的操作,这是其它线程就会通过if语句的判断,这样一来就会造成多卖了一张票,出现错票的情况。

极端情况为,当票数为1时,三个线程同时判断通过,进入阻塞,然后多执行两侧卖票操作

解决

当线程a在操作ticket的时候,其它的线程不能够进入操作,必须等线程a执行完成ticket时,其它的线程才能够开始操作ticket,即使出现了线程阻塞状态也不可

在Java中通过同步机制,解决线程安全问题

  • 同步代码块

    • synchronized(同步监视器){}

    • 操作共享数据的时候需要被同步操作,即为多个线程共同操作的称为共享数据

    • 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁

 public class ThreadPracticeTest {
     public static void main(String[] args) {
         Window w1 = new Window();
 ​
         Thread t1 = new Thread(w1);
         Thread t2 = new Thread(w1);
         Thread t3 = new Thread(w1);
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window implements Runnable {
 ​
     private int ticket = 100;
 ​
     @Override
     public void run() {
         while(true){
             synchronized(this) {//添加同步锁-同步代码块
                 if (ticket > 0) {
                     try {
                         Thread.sleep(100);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName() + "---" + ticket);
                     ticket--;
                 } else {
                     break;
                 }
             }
         }
     }
 }

同步的代码不能包含太多,不能少,需要共享的数据代码包含即可

  • 同步方法

    • synchronized(){}

 public class ThreadPracticeTest {
     public static void main(String[] args) {
         Window w1 = new Window();
 ​
         Thread t1 = new Thread(w1);
         Thread t2 = new Thread(w1);
         Thread t3 = new Thread(w1);
 ​
         t1.setName("窗口1");
         t2.setName("窗口2");
         t3.setName("窗口3");
 ​
         t1.start();
         t2.start();
         t3.start();
     }
 }
 ​
 class Window implements Runnable {
 ​
     private int ticket = 100;
 ​
     @Override
     public void run() {
         while(true){
             show();
         }
     }
     private synchronized void show(){
         if (ticket > 0) {
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + "---" + ticket);
             ticket--;
         }else{//不加则会进入死循环
             System.exit(1);
         }
     }
 }

多个线程共用同一个锁,只能有一个对象

  • 如果是使用Thread类继承方式实现

  • 则需要在对应的位置上加上 static静态,保存对象是唯一的状态即可

说明:使用同步的好处解决了线程的安全问题,操作同步代码时,只能有一个线程参与执行,其它线程等待执行,相当于单线程执行,效率低,线程安全问题就得这样执行,牺牲效率提高安全

单例模式

解决懒汉式线程安全

  • 使用同步机制将单例模式的懒汉式改为线程安全

 public class SingletonTest {
     public static void main(String[] args) {
         Bank b1 = Bank.getInstance();
         Bank b2 = Bank.getInstance();
         System.out.println(b1 == b2);
     }
 }
 class Bank{
     private Bank(){}
 ​
     private static Bank instance = null;
 ​
     public static Bank getInstance(){
         //方式一、效果较低
 //        synchronized (Bank.class) {
 //            if (instance == null) {
 //                instance = new Bank();
 //            }
 //        }
         //方式二、双重校验提升效率
         if(instance == null){
             synchronized (Bank.class) {
                 if(instance == null) {
                     instance = new Bank();
                 }
             }
         }
         return instance;
     }
 }

线程死锁

死锁就是指两个线程在执行过程中,竞争资源或者彼此通信而造成的一种阻塞线程,若无外力作用,无法推进下去,导致成死锁现象

 张三和李四一起去饺子馆吃饺子. 吃饺子需要酱油和醋
 ​
 张三拿起了酱油瓶, 李四拿起了醋瓶
 ​
 张三: 你先把醋瓶给我, 我用完了就把酱油瓶给你
 ​
 李四: 你先把酱油瓶给我, 我用完了就把醋瓶给你
 ​
 如果这俩人彼此之间互不相让, 就构成了死锁
 ​
 酱油和醋相当于是两把锁, 这两个人就是两个线程

解决

  • 预防死锁:通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的发生

  • 避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生

  • 检测死锁:允许系统在运行过程中发生死锁,但可设置检测机构及时检测死锁的发生,并采取适当措施加以清除

  • 解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来

死锁演示

 public class DeadLockRunnable implements Runnable {
 ​
     private int flag; //决定线程走向的标记
     //需要注意这两个对象一定要是两个实现共享
     //不然每次new的就会是这两个对象
     private static Object o1 = new Object();
     private static Object o2 = new Object();
 ​
     public DeadLockRunnable(int flag){
         this.flag = flag;
     }
 ​
     @Override
     public void run() {
         if(flag == 1){
             //线程1执行代码
             synchronized (o1){
                 System.out.println(Thread.currentThread().getName() +"获取到资源o1,请求o2");
                 try {
                     Thread.sleep(100);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 synchronized (o2){
                     System.out.println(Thread.currentThread().getName() +"获取到资源o1,和o2");
                 }
             }
 ​
         }else{
             //线程1执行代码
             synchronized (o2){
                 System.out.println(Thread.currentThread().getName() +"获取到资源o2,请求o1");
                 try {
                     Thread.sleep(100);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 synchronized (o1){
                     System.out.println(Thread.currentThread().getName() +"获取到资源o2,和o1");
                 }
             }
 ​
         }
     }
 }
 ​
 class Test{
     public static void main(String[] args) {
         //创建两个对象实例
         DeadLockRunnable d1 = new DeadLockRunnable(1);
         DeadLockRunnable d2 = new DeadLockRunnable(2);
         //创建两个线程执行两个对象实例
         Thread t1 = new Thread(d1,"DeadLock1");
         Thread t2 = new Thread(d2,"DeadLock2");
         //启动线程
         t1.start();
         t2.start();
 ​
     }
 }
 

形成死锁条件

  • 互斥条件:线程(进程)对于所分配到的资源具有排他性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放

  • 请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放

  • 不剥夺条件:线程(进程)已获得的资源在未使用完之前不能被其它线程强行剥夺,只有自己使用完毕后才释放资源

  • 循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似死循环),造成永久阻塞

解决方式

  • 只要破环产生死锁的四个条件中的其中之一就可以了

    • 互斥条件

      • 这个条件没有办法破坏,因为用锁本来就是想让他们互斥

    • 请求与保持条件

      • 一次性申请所有的资源

    • 不剥夺条件

      • 占用部分资源的线程进一步申请其它资源时,如果申请不到,可以主动释放它占有的资源

    • 循环等待条件

      • 靠按序申请资源来预防,按某一顺序申请资源,释放资源则反序释放,破坏循环等待条件

两个线程以不同的顺序来获得相同的锁,如果按照相同顺序来请求锁,那么就不会出现循环的枷锁依赖性,因此也就不会产生死锁,每个需要锁o1和锁o2的线程都以相同的顺序来获取o1和o2,那么就不会发生死锁

死锁

 

无死锁

 

 public class DeadLockRunnable implements Runnable {
 ​
     private int flag; //决定线程走向的标记
     //需要注意这两个对象一定要是两个实现共享
     //不然每次new的就会是这两个对象
     private static Object o1 = new Object();
     private static Object o2 = new Object();
 ​
     public DeadLockRunnable(int flag){
         this.flag = flag;
     }
 ​
     @Override
     public void run() {
         if(flag == 1){
             //线程1执行代码
             synchronized (o1){
                 System.out.println(Thread.currentThread().getName() +"获取到资源o1,请求o2");
                 try {
                     Thread.sleep(100);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             synchronized (o2){
                 System.out.println(Thread.currentThread().getName() +"获取到资源o1,和o2");
             }
 ​
         }else{
             //线程1执行代码
             synchronized (o2){
                 System.out.println(Thread.currentThread().getName() +"获取到资源o2,请求o1");
                 try {
                     Thread.sleep(100);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             synchronized (o1){
                 System.out.println(Thread.currentThread().getName() +"获取到资源o2,和o1");
             }
 ​
         }
     }
 }
 ​
 class Test{
     public static void main(String[] args) {
         //创建两个对象实例
         DeadLockRunnable d1 = new DeadLockRunnable(1);
         DeadLockRunnable d2 = new DeadLockRunnable(2);
         //创建两个线程执行两个对象实例
         Thread t1 = new Thread(d1,"DeadLock1");
         Thread t2 = new Thread(d2,"DeadLock2");
         //启动线程
         t1.start();
         t2.start();
 ​
     }
 }

锁Lock

从 JDK 1.5 开始,Java 提供了更强大的线程同步机制:通过显示定义同步锁对象来实现同步。同步锁使用 Lock 对象充当。

接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象

Lock和synchronized比较

  • Lock 显示锁,手动加锁、解锁;用 Lock,JVM 将花费较少的时间来调度线程,性能更好

  • synchronized 隐式锁,出了作用域自动释放

 public class DeadLock implements Runnable {
     private int ticket = 10;
     // 可重入锁
     private final ReentrantLock lock = new ReentrantLock();
 ​
     @Override
     public void run() {
         while (true) {
             lock.lock(); //手动锁
             try {
                 if (ticket <= 0) {
                     break;
                 }
                 try {
                     Thread.sleep(1000);
                     System.out.println(ticket--);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             } finally {
                 lock.unlock();//释放锁
             }
         }
     }
 ​
 }
 ​
 class DeadLockTest {
     public static void main(String[] args) {
         DeadLock d1 = new DeadLock();
         Thread t1 = new Thread(d1);
         Thread t2 = new Thread(d1);
         Thread t3 = new Thread(d1);
 ​
         t1.start();
         t2.start();
         t3.start();
 ​
     }
 }

Object类的wait(),notify(),notifyAll()方法

方法名作用
void wait()让活动在当前对象的线程无限等待(释放之前占有的锁)
void notify()唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
void notifyAll()唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁)
  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(锁)

  • notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的

  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

说明:以上三个方法必须使用在同步代码块或同步方法中,三个方法的调用者必须是同步代码块或同步方法中的同步监视器!否则出现 IllegalMonitorStateException 异常

 public class Number implements Runnable {
     private int number = 1;
     //private Object obj = new Object();
     @Override
     public void run() {
         while (true){
 //            synchronized (obj){
             synchronized (this){
 //                obj.notify();
                 this.notify();
                 if(number <= 10) {
                     try {
                         Thread.sleep(100);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName() + "----" + number);
                     number ++;
                     try {
 //                        obj.wait();//阻塞状态,并释放锁
                         this.wait();//阻塞状态,并释放锁
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }else{
                     break;
                 }
             }
         }
     }
 }
 class NumberTest {
     public static void main(String[] args) {
         Number n = new Number();
         Thread t1 = new Thread(n);
         Thread t2 = new Thread(n);
         t1.start();
         t2.start();
     }
 }

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值