Java 多线程

多线程主要技术

  • 进程与线程
  • 线程状态
  • 阻塞状态分类
  • 线程的调度
  • 常用函数说明
  • Thread类方法
  • 创建线程
  • 线程池
  • 线程安全
  • 向线程传递数据
  • 让线程回调数据
线程的生命周期

1.进程与线程

进程:

  • 就是运行的一个程序。
  • 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销,一个进程包含1~n个线程。
  • 进程是资源分配的最小单位。
  • 多进程是指操作系统能同时运行多个进程。
  • 一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

线程:

  • 同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
  • 线程是cpu调度的最小单位。
  • 多线程是指在同一进程中有多个线程在执行。

为什么需要用到线程:

  • 如果在主线程中有比较耗时的操作:下载上传文件等,这些操作会阻塞主线程,后面的任务必须等这些任务执行完毕之后才能执行,影响用户体验。为了不阻塞主线程,需要将耗时的任务放在子线程去做。

  • 主线程被某一个必不可少的任务阻塞,又需要完成其他任务,就需要子线程完成我们想要做的事。例如网络通信中,处理服务端的数据和向服务端发送数据,其中一个必须要在子线程中做。

2.线程状态

新建状态New:使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

就绪状态Runnable:当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

运行状态Running:如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。

阻塞状态Blocked:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

终止状态Dead:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

public static enum State {
   NEW,             //创建状态
   RUNNABLE,        //就绪状态
   BLOCKED,        //阻塞状态
   WAITING,        //等待状态
   TIMED_WAITING,  //限时等待
   TERMINATED;     //死亡状态

   private State() {

   }
}

3.阻塞状态分类

等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

其他阻塞:运行的线程执行sleep()、其他线程调用join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(sleep是不会释放线程持有的锁)。

4.线程的调度

调整线程优先级:

  • Java线程有优先级,优先级高的线程会获得较多的运行机会。
  • Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量
  • Thread类的setPriority()getPriority()方法分别用来设置和获取线程的优先级。
  • 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
  • 线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级
static int MAX_PRIORITY
          线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
          线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
          分配给线程的默认优先级,取值为5。

线程睡眠:

  • Thread.sleep(long millis)方法,使线程转到阻塞状态
  • millis参数设定睡眠的时间,以毫秒为单位。
  • 当睡眠结束后,就转为就绪状态Runnable

线程等待:

  • Object类中的wait(long timeout)方法,导致当前的线程等待。
  • timeout参数设定睡眠的时间,以毫秒为单位,wait()默认一直等待。
  • 直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法或等待时间过去。

线程让步:

  • Thread.yield()方法,暂停当前正在执行的线程对象。
  • 把执行机会让给相同或者更高优先级的线程。

线程加入:

  • Thread类的join()方法,等待其他线程终止。
  • 在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

线程唤醒:

  • Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。
  • 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
  • 线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。
  • 被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
  • 类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

注意:

  • Thread中suspend()resume()两个方法在JDK1.5中已经废除,因为有死锁倾向。
  • stop()方法也被废除。

五.常用函数说明

1. join()

作用:等待该线程终止,一般指的是主线程等待子线程的终止;也就是说在主线程中子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

使用原因:在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

2.yield()

作用:暂停当前正在执行的线程对象,并执行其他线程。

使用原因:让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。让相同优先级的线程之间能适当的轮转执行。

注意:实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

sleep()yield()的区别:yield()方法执行时,是使使当前线程让出 CPU 占有权,当前线程仍处在可运行状态,很可能被线程调度程序再次选中。所以,不可能让出较低优先级的线程些时获得 CPU 占有权。sleep()方法执行时,是使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行。

3.wait()

使用位置:Obj.wait()Obj.notify必须在synchronized(Obj){...}语句块内,也就是wait,与notify是针对已经获取了Obj锁进行操作。

使用:wait()就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。

注意:notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。

sleep()wait()的区别:二者都可以暂停当前线程,释放CPU控制权,主要的区别在于wait()在释放CPU同时,释放了对象锁的控制。

4.join()的实际使用

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {

        //子线程任务
        JoinThread thread = new JoinThread();
        thread.setName("子线程");
        thread.start();
        thread.join();

        //主线程任务
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

//运行结果
子线程任务做完了
main:0
main:1
main:2
main:3
main:4
main:5
main:6
main:7
main:8
main:9

5.wait()和notify()的实际使用

  • wait()必须在同步(Synchronized)方法/代码块中调用,调用wait()就是释放线程对锁的拥有权,释放的前提是必须要先获得锁,先获得锁才能释放锁。

  • notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用,调用notify(),notifyAll()是将锁交给含有wait()方法的线程,让其继续执行下去,如果自身没有锁,怎么叫把锁交给其他线程呢。(本质是让处于入口队列的线程竞争锁)

  • wait()notify(),notifyAll()都属于Object类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能。

  • 每个对象都可以被认为是一个"监视器monitor",这个监视器由三部分组成一个内存锁,一个入口队列,一个等待队列。任意线程都可以拥有这个内存锁。

  • 对于对象的非同步方法而言,任意时刻可以有任意个线程调用该方法。

  • 对于对象的同步方法而言,只有拥有这个对象的内存锁才能调用这个同步方法。如果这个内存锁被其他线程占用,那么另外一个调用该同步方法的线程就会处于阻塞状态,此线程进入入口队列。

  • 若一个拥有该内存锁的线程调用该对象的wait()方法,则该线程会释放内存锁,并加入对象的等待队列。释放锁的同时释放对CPU的拥有权,即wait()后面的代码停止执行,线程进入阻塞状态。

  • 若一个拥有该内存锁的线程调用该对象的notify()方法,则该线程会释放内存锁,唤醒该对象等待队列中的某一个线程。不会立即释放直到相应的synchronized(){}语句块执行结束,再自动释放锁以及对CPU的拥有权。

  • notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)。

  • notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)。

按顺序打印ABAB
三种方法

图形演示








循环结束后,即打印出ABAB后,程序不会退出,仍然被阻塞。需要唤醒阻塞线程,结束程序。

//单个Object类的wait()和notify()
public class WaitSingleTest {
    public static void main(String[] args) throws InterruptedException {

        new TestThreads("A").start();
        Thread.sleep(100);
        new TestThreads("B").start();
    }

}

class TestThreads extends Thread{
    //静态的一把锁
    public static Object obj = new Object();

    //成员变量
    public String name;

    //构造方法
    public TestThreads(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            synchronized (obj){
                //唤醒当前监视器obj等待队列中的其他线程
                obj.notify();

                //输出内容
                System.out.print(name);

                //当前线程等待-阻塞
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        //唤醒等待队列中阻塞的线程
        synchronized (obj){
            obj.notifyAll();
        }
    }
}
//两个Object类的wait()和notify()
public class WaitTest {
    public static void main(String[] args) throws InterruptedException {

        Object lockLast = new Object();
        Object lockSelf = new Object();

        new TestThread("A",lockLast,lockSelf).start();
        Thread.sleep(100);//确保执行顺序是A,B
        new TestThread("B",lockSelf,lockLast).start();

        //System.out.println("结束了吗");
    }

}

class TestThread extends Thread{
    //成员变量
    public String name;
    public Object last;
    public Object self;

    //构造方法
    public TestThread(String name,Object last,Object self){
        this.name = name;
        this.last = last;
        this.self = self;
    }

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            synchronized (last){
                synchronized (self){
                    //打印
                    System.out.print(name);

                    //唤醒
                    self.notify();
                }
                try {
                    last.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println();
        System.out.println(name+"线程已经结束循环了");
        //唤醒被阻塞的B线程
        synchronized (self){
            self.notifyAll();
        }
    }
}
//利用ReentrantLock类对象
public class LockSingleTest {
    public static void main(String[] args) {
        new LockThread("A").start();
        new LockThread("B").start();
    }
}

class LockThread extends Thread{
    //静态的一把锁
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition mCondition = lock.newCondition();

    //成员变量
    public String name;

    //构造方法
    public LockThread(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            lock.lock();
            //唤醒当前监视器等待队列中的其他线程
            mCondition.signal();
            //输出内容
            System.out.print(name);
            try {
                mCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
        //唤醒等待队列中阻塞的线程
        lock.lock();
        mCondition.signalAll();
        lock.unlock();
    }
}

六.Thread类方法

实例方法 描述
public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
public final void setName(String name) 改变线程名称,使之与参数 name 相同。
public final void setPriority(int priority) 更改线程的优先级。
public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
public void interrupt() 中断线程。
public final boolean isAlive() 测试线程是否处于活动状态。
静态方法方法 描述
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

注意:run()和start()

  • start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态Runnable,什么时候运行是由操作系统决定的。
  • start()方法是在当前线程中启动一个新的线程,而新启动的线程会调用run()方法,同时该方法不能重复调用。
  • run()方法和普通的方法一样,可以重复执行,不会创建新的线程。

七.创建线程

1.实现Runnable接口

  • 创建一个类实现Runable接口,实现run()方法,可以看成实现一个任务。
  • 实例化该类并让实例化的Thread实现对应任务。

方式一:直接创建,中规中矩

//创建一个类实现Runable接口
class TestRunable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            String curentThreadName = Thread.currentThread().getName();
            System.out.println(curentThreadName+":"+i);
        }
    }
}

public class InterfaceCreat {
    public static void main(String[] args) {
        //创建一个任务
        TestRunable testRunable = new TestRunable();

        //使用Thread操作这个任务

        //普通创建方式
        Thread thread = new Thread(testRunable);
        thread.start();
    }
}

方式二:匿名实现Runnable接口的类,适用于不需要操作任务本身

public class InterfaceCreat {
    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    String curentThreadName = Thread.currentThread().getName();
                    System.out.println(curentThreadName+":"+i);
                }
            }
        });
        thread.start();
    }
}

方式三:在方式二的基础上,将Thread类匿名,适用于不需要操作线程对象本身

public class InterfaceCreat {
    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    String curentThreadName = Thread.currentThread().getName();
                    System.out.println(curentThreadName+":"+i);
                }
            }
        }).start();

    }
}

方式四:使用Lambda表达式,不建议采用

public class InterfaceCreat {
    public static void main(String[] args) {

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                String curentThreadName = Thread.currentThread().getName();
                System.out.println(curentThreadName+":"+i);
            }
        }).start();

    }
}

2. 继承Thread类

  • 创建一个类继承Thread类,重写run()方法。
  • 本质上也是实现Runnable接口的一个实例。
class TestThread extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            String curentThreadName = Thread.currentThread().getName();
            System.out.println(curentThreadName+":"+i);
        }
        super.run();
    }
}

public class ExtendsCreat {
    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.setName("子线程");
        testThread.start();
    }
}

3.实现Callable接口,FutureTask包装Callable对象

  • 创建Callable()接口的实现类,并实现call()方法。
  • 该call()方法将作为线程执行体,并且有返回值。
  • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象
  • 用FutureTask作为Thread类初始化的Target。
  • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
//创建Callable()接口的实现类,并实现call()方法
//该call()方法将作为线程执行体,并且有返回值
class CallableTest implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int i ;
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
}

public class ReturnCreat {
    public static void main(String[] args) {
        //创建Callable实现类的实例,使用FutureTask类来包装Callable对象
        //该FutureTask对象封装了该Callable对象的call()方法的返回值
        CallableTest callableTest = new CallableTest();
        FutureTask<Integer> integerFutureTask = new FutureTask<>(callableTest);

        //使用FutureTask作为Thread类初始化的Target
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
            if (i == 20){
                new Thread(integerFutureTask).start();
            }
        }

        try {
            System.out.println(integerFutureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

4.比较

采用实现 Runnable、Callable 接口的方式创建多线程

  • 优势:线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

  • 劣势:编程复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法

采用继承Thread类的方式创建多线程

  • 优势:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

  • 劣势:线程类已经继承了Thread类,所以不能再继承其他父类。

八.线程池

1.线程池是什么

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

2.为什么需要用到线程

  • 在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。

  • 除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。

  • 为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

3.线程池的作用

  • 线程池主要用来解决线程生命周期开销问题和资源不足问题。

  • 通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。

  • 这样,就可以立即为请求服务,使用应用程序响应更快;另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

4.必要的类和方法

线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。

Executors:线程池创建工厂类

public static ExecutorServicenewFixedThreadPool(int nThreads):返回线程池对象

ExecutorService:线程池类

Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

Future接口:用来记录线程任务执行完毕后产生的结果。

5.使用线程池创建线程的方式

1.使用Runnable接口创建线程池

  • 创建线程池对象
  • 创建 Runnable 接口子类对象
  • 提交 Runnable 接口子类对象
  • 关闭线程池
public class ThreadPool1 {
    public static void main(String[] args) {
        //创建线程池对象 参数5表示有5个线程
        ExecutorService service = Executors.newFixedThreadPool(5);
        //创建Runable线程任务对象
        TaskRunable task = new TaskRunable();
        //从线程池获取线程对象并执行
        service.submit(task);
        System.out.println("--------");
        //再取出来一个线程对象
        service.submit(task);
        //关闭线程池
        service.shutdown();
    }
}

//任务
class TaskRunable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("自定义线程任务:"+Thread.currentThread()+i);
        }
    }
}

2.使用Callable接口创建线程池

  • 创建线程池对象
  • 创建 Callable 接口子类对象
  • 提交 Callable 接口子类对象
  • 关闭线程池
public class ThreadPool2 {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(3);
        //创建Callable线程任务对象
        TaskCallable task = new TaskCallable();
        //从线程池获取线程对象并执行
        service.submit(task);
        System.out.println("--------");
        //再取出来一个线程对象
        service.submit(task);
        //关闭线程池
        service.shutdown();
    }
}

//任务
class TaskCallable implements Callable<Object>{
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 1000; i++) {
            System.out.println("自定义线程任务:"+Thread.currentThread().getName()+i);
        }
        return null;
    }
}

3.线程池练习:返回两个数相加的结果

要求:

  • 通过线程池中的线程对象,使用Callable接口完成两个数求和操作

  • Future 接口:用来记录线程任务执行完毕后产生的结果。

public class PoolPractice {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(3);
        //创建Callable线程任务对象
        MyCallable task = new MyCallable(5, 6);
        //从线程池获取线程对象并执行
        Future<Integer> result = service.submit(task);
        //利用Future的get方法返回结果
        Integer sum = result.get();
        System.out.println(sum);
    }
}

//任务
class MyCallable implements Callable<Integer>{
    //成员变量
    public int a;
    public int b;

    //构造器
    public MyCallable(int a,int b){
        this.a = a;
        this.b = b;
    }

    @Override
    public Integer call() throws Exception {
        return a+b;
    }
}

九.线程安全/同步

1.synchronized

  • 同步代码块
synchronized (监听器/对象/锁) {
    需要锁住的代码
}
public class TicketSafe {
    public static void main(String[] args) {
        //开启一条线程
        new Thread(new GetTicketTask("上海")).start();
        //开启新的线程
        new Thread(new GetTicketTask("重庆")).start();
    }
}

class GetTicketTask implements Runnable{

    //静态变量
    //锁
    public static Object obj = new Object();
    //票数
    public static int ticketNumber = 100;

    //成员变量 售票处名字
    public String ticketOfficeName;

    //构造方法
    public GetTicketTask(String ticketOfficeName){
        this.ticketOfficeName = ticketOfficeName;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //模拟卖票
            synchronized (obj){
                if (ticketNumber > 0){
                    System.out.println(ticketOfficeName+"剩余票数:"+ticketNumber);
                    ticketNumber--;
                }
            }
        }
    }
}
  • 同步方法
//this指调用这个方法的对象
public synchronized void test()
test();
//等价
public void test()
synchronized(this){
   test();
}
public class TicketSafeMethod {
    //票数
    public static int ticketCount = 100;

    //模拟卖票 这里的锁是这个类TicketSafeMethod
    public synchronized static void sellTicket(String name){
        if (ticketCount > 0){
            System.out.println(name+"剩余票数:"+ticketCount);
            ticketCount--;
        }
    }

    public static void main(String[] args) {
        new TicketThread("重庆").start();
        new TicketThread("上海").start();
    }
}

class TicketThread extends Thread{
    //成员变量
    public String ticketOfficeName;
    //构造方法
    public TicketThread(String name){
        ticketOfficeName = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            TicketSafeMethod.sellTicket(ticketOfficeName);
        }
    }
}

2.ReentrantLock类的对象

  • public void lock()上锁
  • public void unlock()解锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
需要锁住的代码
lock.unlock();
public class TicketSafeLock {
        public static void main(String[] args) {
            //开启一条线程
            new Thread(new GetTicketTask("上海")).start();
            //开启新的线程
            new Thread(new GetTicketTask("重庆")).start();
        }
    }

    class GetTicketTask implements Runnable{

        //静态变量
        //锁
        public static ReentrantLock lock = new ReentrantLock();
        //票数
        public static int ticketNumber = 100;

        //成员变量 售票处名字
        public String ticketOfficeName;

        //构造方法
        public GetTicketTask(String ticketOfficeName){
            this.ticketOfficeName = ticketOfficeName;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                //模拟卖票

                lock.lock();//上锁
                if (ticketNumber > 0){
                    System.out.println(ticketOfficeName+"剩余票数:"+ticketNumber);
                    ticketNumber--;
                }
                lock.unlock();//解锁
            }
        }
}

十.向线程传递数据

1.通过构造方法传递数据

  • 通过线程类Thread的构造方法将数据传入线程。
  • 用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。
public class Test{
   public static void main(String[] args) { 
       Thread thread = new MyThread("world"); 
       thread.start(); 
   } 
}

public class MyThread extends Thread { 
   private String name; 

   public MyThread(String name) { 
       this.name = name; 
   } 
   
   @Override
   public void run() { 
       System.out.println("hello " + name); 
   } 
}

2.通过变量和方法传递数据

在类中定义一系列的public的方法或变量(也可称之为字段),在建立完对象后,通过对象实例逐个赋值。

public class Test{
   public static void main(String[] args) { 
       Runnable task = new MyTask("world"); 
       task.setName("world"); 
       Thread thread = new Thread(task);
       thread.start(); 
   } 
}

public class MyTask implements Runnable { 
   private String name; 

   public setNamed(String name) { 
       this.name = name; 
   } 
   
   @Override
   public void run() { 
       System.out.println("hello " + name); 
   } 
}

3.通过回调函数传递数据

  • 上面两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。

  • 通过回调函数在线程运行的过程中动态地获取数据。

public class Test {
    public static void main(String[] args) {
        Thread thread = new MyThread(new Work());
        thread.start();
    }
}

class Data {
    public int value = 0;
}

class Work {
    public void process(Data data, Integer... numbers) {
        for (int n : numbers) {
            
            data.value += n;
        }
    }
}

class MyThread extends Thread {
    private Work work;

    public MyThread(Work work) {
        this.work = work;
    }

    @Override
    public void run() {
        java.util.Random random = new java.util.Random();
        Data data = new Data();
        int n1 = random.nextInt(1000);
        int n2 = random.nextInt(2000);
        int n3 = random.nextInt(3000);
        work.process(data, n1, n2, n3); // 使用回调函数
        System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"  + String.valueOf(n3) + "=" + data.value);
    }
}

十一.让线程回调数据

  • 1.实现Callable接口,FutureTask包装Callable对象,作为线程构造方法的参数创建线程。

  • 2.线程结合代理模式回调数据。

//Person类
//线程中:Agent类
public class AllLineInterfaceToBlockTest {
    public static void main(String[] args) {
        Person person = new Person();
        person.name = "小王";
        person.needHouse();
    }

}

class Person implements Agent.AgentInterface {

    String name;

    public void needHouse(){
        System.out.println(Thread.currentThread().getName());
        Agent agent = new Agent();
        agent.start();
        agent.target = this;
    }

    @Override
    public void callback(String desc) {
        System.out.println(name+"的房子:"+desc);
    }
}

class Agent extends Thread{

    AgentInterface target;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
        System.out.println("开始找房子");
        System.out.println("---------");
        System.out.println("房子找到了,返回数据");

        target.callback("西南大学");

        super.run();
    }

    public interface AgentInterface{
        //统一通过打电话的方式告诉人房子的信息
        void callback(String desc);
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 林炳文Evankaka原创作品。转载自http://blog.csdn.net/evankaka 本文主要讲了ja...
    ccq_inori阅读 643评论 0 4
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,436评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,948评论 1 18
  • 本文主要介绍线程的定义,创建,使用,停止,状态图和常用方法。主要用于概念扫盲和梳理。多进程是指操作系统能同时运行多...
    stoneyang94阅读 1,180评论 2 5
  • 一扩展javalangThread类二实现javalangRunnable接口三Thread和Runnable的区...
    和帅_db6a阅读 481评论 0 1