多线程

Java中的多线程

1.什么是多线程?

多线程:某一个程序在运行的时候【进程】可能会产生多个不同的执行线索【执行轨迹】【线程】,这些多个不同的执行线索【执行轨迹】共同运行的情况就是多线程。往往我们会感觉到这些多个不同的执行线索【执行轨迹】同时执行,实际上这时一种错觉假象,实际上当这些多个不同的执行线索【执行轨迹】在运行的时候,某一个时刻只用一个执行线索【执行轨迹】在运行,只是这多个不同的执行线索【执行轨迹】快速的切换而已。

2.多线程的创建方式以及区别?

创建方式

  • 继承Thread类
  1. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
  2. 创建Thread子类的实例,即创建了线程对象。
  3. 调用线程对象的start()方法来启动该线程。
package Thread;

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
        testExtends();
    }

    public static void testExtends() throws Exception {
        Thread t1 = new MyThreadExtends();
        Thread t2 = new MyThreadExtends();
        t1.start();
        t2.start();
    }
}

class MyThreadExtends extends Thread {
    @Override
    public void run() {
        System.out.println("通过继承Thread,线程号:" + currentThread().getName());
    }
}
  • 实现Runnable接口
  1. 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动该线程。
package Thread;

import java.util.concurrent.*;
//测试类
public class TestThread {
    public static void main(String[] args) throws Exception {
         testImplents();
    }

    public static void testImplents() throws Exception {
        MyThreadImplements myThreadImplements = new MyThreadImplements();
        Thread t1 = new Thread(myThreadImplements);
        Thread t2 = new Thread(myThreadImplements, "my thread -2");
        t1.start();
        t2.start();
    }
}
//线程类
class MyThreadImplements implements Runnable {
    @Override
    public void run() {
        System.out.println("通过实现Runable,线程号:" + Thread.currentThread().getName());
    }
}
  • 实现Callable接口
  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
package Thread;

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
        testCallable();
    }

    public static void testCallable() throws Exception {
        Callable callable = new MyThreadCallable();
        FutureTask task = new FutureTask(callable);
        new Thread(task).start();
        System.out.println(task.get());
        Thread.sleep(10);//等待线程执行结束
        //task.get() 获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
        //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
        System.out.println(task.get(100L, TimeUnit.MILLISECONDS));
    }
}

class MyThreadCallable implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("通过实现Callable,线程号:" + Thread.currentThread().getName());
        return 10;
    }
}

总结:

三种方式的优缺点

  • 采用继承Thread类方式:
  1. 优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
  2. 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
  • 采用实现Runnable接口方式:
  1. 优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  2. 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
  • Runnable和Callable的区别:
  1. Callable规定的方法是call(),Runnable规定的方法是run().
  2. Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
  3. call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常
  4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

start()和run()的区别

  • start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行
  • run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)

3.线程的常用方法

线程类有好多方法,下面介绍一些常用的方法。

1.start() 实例方法

  • 启动一个线程用的是thread.start()方法,如果直接调用run方法是同步调用,相当于一个普通的方法调用。
  • start()方法使线程开始执行,JVM会自动调用线程的run方法。new出来线程,调用start()方法即处于RUNNABLE(可运行)状态了。处于RUNNABLE状态的线程可能正在Java虚拟机中运行,也可能正在等待处理器的资源,因为一个线程必须获得CPU的资源后,才可以运行其run()方法中的内容,否则排队等待。
    public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }

根据线程的状态来判断是否已经调用其start()方法, threadStatus 可以保证只调用一次start,多次调用会报错。并且在start()方法中调用了一个start0()方法,start0()是一个native方法。

    private volatile int threadStatus = 0;
    private native void start0();

两次调用start()方法会报错:

package cn.qlq.thread.one;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SubThread extends Thread {

private static final Logger log = LoggerFactory.getLogger(SubThread.class);

@Override
public void run() {
    log.debug("subThread run,threadname->{}", Thread.currentThread().getName());
}

public static void main(String[] args) {
    SubThread subThread = new SubThread();
    subThread.start();
    subThread.start();
    log.debug("运行结束,threadname->{}", Thread.currentThread().getName());
}
}

结果:

Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at cn.qlq.thread.one.SubThread.main(SubThread.java:18)
2020-7-25 0:58:26 [cn.qlq.thread.one.SubThread]-[DEBUG] subThread run,threadname->Thread-0

Thread.currentThread静态方法

package cn.qlq.thread.two;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 研究静态方法currentThread
 * 
 * @author Administrator
 *
 */
public class CurrentThreadMethodDemo {

    private static final Logger log = LoggerFactory.getLogger(CurrentThreadMethodDemo.class);

    public static void main(String[] args) {
        Thread currentThread = Thread.currentThread();
        log.debug("currentThread -> {}", currentThread);
        log.debug("currentThreadName -> {}", currentThread.getName());
    }
}

结果:

2020-7-25 0:58:26 [cn.qlq.thread.two.CurrentThreadMethodDemo]-[DEBUG] currentThread -> Thread[main,5,main]
2020-7-25 0:58:26 [cn.qlq.thread.two.CurrentThreadMethodDemo]-[DEBUG] currentThreadName -> main

查看Thread.toString()的源码返回的是线程的名称+优先级+所属组的名称:

public String toString() {
        ThreadGroup group = getThreadGroup();
        if (group != null) {
            return "Thread[" + getName() + "," + getPriority() + "," +
                           group.getName() + "]";
        } else {
            return "Thread[" + getName() + "," + getPriority() + "," +
                            "" + "]";
        }
    }

3.实例方法 isAlive()

    /**
     * Tests if this thread is alive. A thread is alive if it has
     * been started and has not yet died.
     *
     * @return  <code>true</code> if this thread is alive;
     *          <code>false</code> otherwise.
     */
    public final native boolean isAlive(); 

isAlive方法用于判断当前线程是否处于活动状态。什么是活动状态呢?活动状态就是已经启动尚未终止的,线程处于正在运行或者准备开始运行的状态就认为线程是"活动"的。(新建状态的线程isAlive()返回的是false)

package cn.qlq.thread.two;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 研究isAlive方法
 * 
 * @author Administrator
 *
 */
public class IsAliveMethodDemo {

    private static final Logger log = LoggerFactory.getLogger(IsAliveMethodDemo.class);

    public static void main(String[] args) {
        Thread r1 = new Thread() {
            @Override
            public void run() {
                log.debug("run isAlive->{}", this.isAlive());
            }
        };
        log.debug("begain---r1 isAlive->{}", r1.isAlive());
        r1.start();
        log.debug("end---r1 isAlive->{}", r1.isAlive());
    }
}

结果:

2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] begain—r1 isAlive->false
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] end—r1 isAlive->true
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] run isAlive->true

需要说明如下代码:

log.debug("end---r1 isAlive->{}", r1.isAlive()); 

虽然在上面打印的结果是true,但是此值是不确定的。打印true是因为r1还没有执行完毕,将上面代码修改为下面:

        log.debug("begain---r1 isAlive->{}", r1.isAlive());
        r1.start();
        Thread.sleep(1 * 1000);
        log.debug("end---r1 isAlive->{}", r1.isAlive());

结果:

2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] begain—r1 isAlive->false
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] run isAlive->true
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] end—r1 isAlive->false

再次修改代码run方法中休眠10秒钟:

package cn.qlq.thread.two;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 研究isAlive方法
 * 
 * @author Administrator
 *
 */
public class IsAliveMethodDemo {

    private static final Logger log = LoggerFactory.getLogger(IsAliveMethodDemo.class);

    public static void main(String[] args) throws InterruptedException {
        Thread r1 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1 * 10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("run isAlive->{}", this.isAlive());// F
            }
        };
        log.debug("begain---r1 isAlive->{}", r1.isAlive());// T
        r1.start();
        log.debug("end---r1 isAlive->{}", r1.isAlive());// T
        log.debug("finish");
    }
}

结果:

2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] begain—r1 isAlive->false
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] end—r1 isAlive->true
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] finish
2020-7-25 0:58:26 [cn.qlq.thread.two.IsAliveMethodDemo]-[DEBUG] run isAlive->true

我们在主线程中执行r1.isAlive的时候会等待r1线程休眠过后才打印run方法中的isAlive。也就是说当一个Thread休眠之后,会继续执行休眠之后的代码。

4.静态方法 sleep()

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

方法sleep()的作用是在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行)。 这个"正在执行的线程"是指Thread.currentThread()返回的线程。但是sleep不会释放锁。(The thread does not lose ownership of any monitors.)

sleep(long)使当前线程进入超时等待(TIMED_WAITING)状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;

sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级的线程有执行的机会;

sleep(long)是不会释放锁标志的。

会抛出中断异常

测试代码:

package cn.qlq.thread.two;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 研究sleep方法
 * 
 * @author Administrator
 *
 */
public class SleepMethodDemo {

    private static final Logger log = LoggerFactory.getLogger(SleepMethodDemo.class);

    public static void main(String[] args) throws InterruptedException {
        Thread r1 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("over sleep,{}", System.currentTimeMillis());
            }
        };
        log.debug("begain->{}", System.currentTimeMillis());
        r1.start();
        log.debug("end---r1 isAlive->{}", r1.isAlive());
        log.debug("end->{}", System.currentTimeMillis());
    }
}

结果:

2020-7-25 0:58:26 [cn.qlq.thread.two.SleepMethodDemo]-[DEBUG] begain->1544002079805
2020-7-25 0:58:26 [cn.qlq.thread.two.SleepMethodDemo]-[DEBUG] end—r1 isAlive->true
2020-7-25 0:58:26 [cn.qlq.thread.two.SleepMethodDemo]-[DEBUG] end->1544002079808
2020-7-25 0:58:26 [cn.qlq.thread.two.SleepMethodDemo]-[DEBUG] over sleep,1544002089808

Thread.sleep(0)的作用是什么?

由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

5.实例方法 getId()

    /**
     * Returns the identifier of this Thread.  The thread ID is a positive
     * <tt>long</tt> number generated when this thread was created.
     * The thread ID is unique and remains unchanged during its lifetime.
     * When a thread is terminated, this thread ID may be reused.
     *
     * @return this thread's ID.
     * @since 1.5
     */
    public long getId() {
        return tid;
    }

此方法返回线程的唯一表示,是一个long型的正数,在线程创建的时候被赋值。

其生成方式是一个静态成员变量一直在自增,并且自增方法也加了同步锁。也就是说线程的ID是0开始一直自增。

    /* For generating thread ID */
    private static long threadSeqNumber;

    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }

补充:顺便提一下name的生成方式:(Thread-0,默认是线程名称是Thread-加上一个自增的数, threadInitNumber用于name的自增)

    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

测试代码:

package cn.qlq.thread.two;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 研究getId
 * 
 * @author Administrator
 *
 */
public class GetIdMethodDemo extends Thread {

    private static final Logger log = LoggerFactory.getLogger(GetIdMethodDemo.class);

    @Override
    public void run() {
        log.debug("threadName->{},getId->{}", Thread.currentThread().getName(), Thread.currentThread().getId());
    }

    public static void main(String[] args) throws InterruptedException {
        log.debug("threadName->{},getId->{}", Thread.currentThread().getName(), Thread.currentThread().getId());
        for (int i = 0; i < 10; i++) {
            GetIdMethodDemo t1 = new GetIdMethodDemo();
            t1.start();
        }
    }
}

结果:

2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->main,getId->1
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-0,getId->9
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-1,getId->10
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-2,getId->11
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-3,getId->12
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-5,getId->14
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-4,getId->13
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-8,getId->17
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-6,getId->15
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-9,getId->18
2020-7-25 0:58:26 [cn.qlq.thread.two.GetIdMethodDemo]-[DEBUG] threadName->Thread-7,getId->16

4.线程的生命周期?

Java中线程的生命周期

  • 线程的生命周期就是线程从一开始创建,到run方法执行完毕以后的状态变化。[状态之间的切换]

  • 线程的生命周期几种状态【1、新建状态 2、就绪状态 3、运行状态 4.阻塞状态 5.死亡状态】

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3d79WGYq-1595611341292)(C:\Users\18373\Desktop\Java基础\java基础 第十章 多线程\img\生命周期.png)]

  • 创建状态:通过new的方式创建出线程对象,此时线程就进入到创建状态【新建状态】。

    ​ 新建状态的线程是不能运行。

  • 就绪状态:新建状态的线程调用strat方法之后就会进入就绪状态。

    就绪状态的线程具备执行能力,但是没有cpu资源。【万事具备只差cpu】.

  • 运行状态:就绪状态的线程得到cpu资源开始执行run方法,此时这个线程就是运行状态。

    运行状态的线程当cpu切换到其他线程时候,本线程就再一次进入就绪状态。

  • 阻塞状态:运行状态的线程调用sleep/wait方法…此时线程进入阻塞状态。

  • 处于阻塞状态的线程的休眠时间到/调用notify方法/notifyAll方法在此时线程进入就绪状态,从就绪状态中得到cpu资源从而进入运行状态。

  • 死亡状态:运行状态的线程run方法运行结束/线程执行过程中遇到异常/调用stop方法此时线程就进入到死亡状态。

    ​ 死亡状态的线程是不能运行,除非再一次使用strat方法重新启动运行。

5.为什么需要线程同步/线程安全?什么是线程同步/线程安全?线程同步/线程安全实现方式有几种,它们有什么区别?

  • 原因是因为CPU在执行某个线程的时候,并没有将线程的任务全部执行完成就切换到其他的线程上导致数据有误。
  • 原因:多条线程去访问同一个资源的时候,可能会出现资源访问数据不一致的情况,为了避免这种情况的出现,就需要使用线程安全【线程同步】。
  • 多条线程去访问同一个资源的时候,可能会出现资源访问数据不一致的情况,为了避免这种情况的出现,就需要使用线程同步【线程安全】。
  • 线程安全问题发生本质:多个线程他们在操作共享的数据(资源)。而CPU在执行线程的过程中操作共享资源的代码还没有彻底执行完,CPU就切换到其他线程上,导致数据不一致

**解决安全问题:线程的同步技术。**

  • `【线程安全/线程同步】:多条线程去访问同一个资源的时候,每一次保证一条线程正常访问资源,当前该线程访问资源的时候,其他的线程就需要等待,当前该线程访问资源结束之后,允许另一条线程去访问资源,其他线程继续等待。```

Java****中实现线程同步的方式

怎么做?

1.同步代码块

同步代码块的书写格式:

synchronized(任意的对象[锁] ){

​ 书写的被同步的代码(操作共享数据的代码);

}

例如:

package com.click369.test1;
/**
 \* 实现买票程序的线程类
 \* @author Administrator
 *
 */
public class MyThread implements Runnable{
     //定义一个变量,来保存票数
     //假设我们现在有5张票可以卖出
     private int piao=5;
     @Override
     public void run() {
         //1.通过while循环控制买票的持续性
         //定义一个变量,来控制while循环
         boolean flag=true;
         while(flag){
             /**
              \* 同步代码块格式
              \* synchronized(任意的对象[锁] ){
                      书写的被同步的代码(操作共享数据的代码);
                  }
              *synchronized---同步关键字
              *(同步对象)--需要被锁定的资源所在类的对象
              *{}---【块】
              *将需要同步的Java程序写上面的{}块中
              */
             synchronized(this){
                  //2.判断是否有票
                  //如果票数大于0,就表是有票,可以卖出
                  if(piao>0){
                      //3.线程休眠模拟出收钱,打票,找钱的过程
                      try {
                           Thread.sleep(1000);
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                      //得到当钱包线程的名称
                      String name=Thread.currentThread().getName();
                      //4.卖出第几张票
                      //5.票数减1
                      System.out.println(name+",卖出第1张票,还剩"+(--piao)+"张票");
                  }else{
                      //如果票数小于/等于0,就是已经无票可卖
                      flag=false;
                  }
             }
         }
     }
}

上面我们通过同步代码块实现线程同步,同步代码块在编写的时候,需要设置一个同步对象,很多人都不明白这个同步对象都是是谁,不容易被判定。

能不能有一个不需要判定同步对象的这个线程同步的实现方式-----同步方法

  1. 同步方法

同步方法格式:

访问限制修饰符 synchronized 返回值类型 方法名称(参数列表){

书写的被同步的代码(操作共享数据的代码);

}

synchronized 关键字修饰的方法就是同步方法

例如:

package com.click369.test1;
/**
 \* 实现买票程序的线程类
 \* @author Administrator
 *
 */
public class MyThread implements Runnable{
     //定义一个变量,来保存票数
     //假设我们现在有5张票可以卖出
     private int piao=5;
     //定义一个变量,来控制while循环
     private boolean flag=true;
     @Override
     public void run() {
         //1.通过while循环控制买票的持续性
         while(flag){
             //调用同步方法的执行
             seller();  
         }
     }
     /**
      \* 创建买票的同步方法
      \* 同步方法格式:
         访问限制修饰符 synchronized 返回值类型 方法名称(参数列表){
             书写的被同步的代码(操作共享数据的代码);
         }
      */
     private synchronized void seller(){
         //2.判断是否有票
         //如果票数大于0,就表是有票,可以卖出
         if(piao>0){
             //3.线程休眠模拟出收钱,打票,找钱的过程
             try {
                  Thread.sleep(1000);
             } catch (InterruptedException e) {
                  e.printStackTrace();
             }
             //得到当钱包线程的名称
             String name=Thread.currentThread().getName();
             //4.卖出第几张票
             //5.票数减1
             System.out.println(name+",卖出第1张票,还剩"+(--piao)+"张票");
         }else{
             //如果票数小于/等于0,就是已经无票可卖
             flag=false;
         }
     }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zg5WzvIH-1595611341295)(C:\Users\18373\Desktop\Java基础\java基础 第十章 多线程\img\买票.png)]

6.sleep 与wait的区别?

区别:

  1. sleep()方法,是属于Thread类中的;

    wait()方法,则是属于Object类中的。

  2. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。(在调用sleep()方法的过程中,线程不会释放对象锁。)

    wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。(获取对象锁进入运行状态。)

  3. wait()方法需要释放锁,前提条件是它已经持有锁。所以wait和notify(或者notifyAll)方法都必须被包裹在synchronized语句块中,并且synchronized后锁的对象应该与调用wait方法的对象一样。否则抛出IllegalMonitorStateException

    sleep()方法告诉操作系统至少指定时间内不需为线程调度器为该线程分配执行时间片,并不释放锁(如果当前已经持有锁)。实际上,调用sleep方法时并不要求持有任何锁。

7.notify 与notifyAll的区别?

锁池:

假设线程A已经拥有对象锁,线程B、C想要获取锁就会被阻塞,进入一个地方去等待锁的等待,这个地方就是该对象的锁池;

等待池:

假设线程A调用某个对象的wait方法,线程A就会释放该对象锁,同时线程A进入该对象的等待池中,进入等待池中的线程不会去竞争该对象的锁。

notify和notifyAll的区别:

1、notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会;

与wait的区别?

区别:

  1. sleep()方法,是属于Thread类中的;

    wait()方法,则是属于Object类中的。

  2. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。(在调用sleep()方法的过程中,线程不会释放对象锁。)

    wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。(获取对象锁进入运行状态。)

  3. wait()方法需要释放锁,前提条件是它已经持有锁。所以wait和notify(或者notifyAll)方法都必须被包裹在synchronized语句块中,并且synchronized后锁的对象应该与调用wait方法的对象一样。否则抛出IllegalMonitorStateException

    sleep()方法告诉操作系统至少指定时间内不需为线程调度器为该线程分配执行时间片,并不释放锁(如果当前已经持有锁)。实际上,调用sleep方法时并不要求持有任何锁。

7.notify 与notifyAll的区别?

锁池:

假设线程A已经拥有对象锁,线程B、C想要获取锁就会被阻塞,进入一个地方去等待锁的等待,这个地方就是该对象的锁池;

等待池:

假设线程A调用某个对象的wait方法,线程A就会释放该对象锁,同时线程A进入该对象的等待池中,进入等待池中的线程不会去竞争该对象的锁。

notify和notifyAll的区别:

1、notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会;

2、notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值