美文网首页java
Java--多线程

Java--多线程

作者: 二妹是只猫 | 来源:发表于2019-04-28 11:49 被阅读0次
进程和线程的区别
  • 进程时资源分配的最小单位,线程时CPU调度的最小单位
  • 线程不能看作独立应用,而进程可看作独立应用
  • 进程还有独立的地址空间,多进程的程序比多心啊吃程序健壮
  • 进程的切换比线程的切换开销大
Java进程和线程的关系
  • Java对操作系统提供的功能进行封装,包括进程和线程
  • 运行一个程序会产生一个进程,进程包含至少一个线程
  • 每个进程对应一个JVM实例,多线程共享JVM里的堆
  • Java采用单线程编程模式,程序会自动创建主线程
  • 主线程可以创建子线程,原则上要后于子线程完成执行
线程run()和start()的区别

start()
调用start()方法会创建一个新的子线程并启动,处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。start()不能被重复调用

run()
方法只是Thread的一个普通方法,可以被重复调用。如果直接调用run方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的

代码实例证明上面的结论

  private void runCurrentThread(){
      System.out.println("currentThread=="+Thread.currentThread().getName());
      System.out.println("over");
  }
  @Test
  public void diffRunAndStart(){
      Thread thread = new Thread(){
          @Override
          public void run() {
              super.run();
              runCurrentThread();
          }
      };
      System.out.println("current main Thread=="+Thread.currentThread().getName());
      thread.start();
  }

输出结果:

current main Thread==main
currentThread==Thread-0
over

Process finished with exit code 0

将thread.start()换为thread.run(),输出结果:

current main Thread==main
currentThread==main
over

Process finished with exit code 0

以上代码证明我们的结论,下面通过源码来查看它的实现:

    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) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    private native void start0();

最终调用了native方法start0(),查看jdk源码:

jdk源码--java.lang.Thread

start0调用了JVM_StartThread,在去看看jvm文件:

jvm.cpp
代码很多,但最终看到创建了一个新的线程,也证实了我们的结论。start()新创建线程并启动
Thead与Runnable的关系
  • Thread是实现了Runable接口的类,使得run支持多线程
  • 因类的单一继承原则,推荐多使用Runnable接口

网上有一个结论,用Runnable就可以实现资源共享,而 Thread 不可以。用来论证的代码大概是这样

  • Thread:
    @Test
    public void thread(){
        Thread thread1 = new MyThread("thread1");
        Thread thread2 = new MyThread("thread2");
        thread1.start();
        thread2.start();
    }
    class MyThread extends Thread{
        public String name;
        public int tickit = 10;
        public MyThread(String name){
            this.name = name;
        }

        @Override
        public void run() {
            super.run();
            for(int i=0;i<200;i++){
                if (0<tickit){
                    System.out.println(name+" 票号=="+tickit--);
                }
            }
        }
    }

输出:

thread2 票号==10
thread2 票号==9
thread2 票号==8
thread2 票号==7
thread2 票号==6
thread2 票号==5
thread2 票号==4
thread1 票号==10
thread2 票号==3
thread2 票号==2
thread2 票号==1
thread1 票号==9
thread1 票号==8
thread1 票号==7
thread1 票号==6
thread1 票号==5
thread1 票号==4
thread1 票号==3
thread1 票号==2
thread1 票号==1

Process finished with exit code 0
  • Runnable:
    @Test
    public void runnable(){
        Runnable myRunable = new MyRunnable();
        Thread thread1 = new Thread(myRunable,"thread1");
        Thread thread2 = new Thread(myRunable,"thread2");
        thread1.start();
        thread2.start();
    }
    class MyRunnable implements Runnable{
        public int tickit = 10;

        @Override
        public void run() {
            for(int i=0;i<200;i++){
                if (tickit>0){
                    System.out.println(Thread.currentThread().getName()+" 票号=="+tickit--);
                }
            }
        }
    }

输出:

thread1 票号==10
thread2 票号==9
thread2 票号==7
thread2 票号==6
thread2 票号==5
thread2 票号==4
thread2 票号==3
thread2 票号==2
thread2 票号==1
thread1 票号==8

Process finished with exit code 0

输出貌似验证了这个结论,其实仔细看看代码就知道,这只是两种写法的区别,根本就不是 implements Runnable 与 extends Thread 的区别。
修改代码,在此证明错误:

 @Test
    public void thread(){
        Thread thread1 = new MyThread("thread1");
        Thread thread2 = new MyThread("thread2");
        thread1.start();
        thread2.start();
    }
    static class MyThread extends Thread{
        public String name;
        public static int tickit = 10;
        public MyThread(String name){
            this.name = name;
        }

        @Override
        public void run() {
            super.run();
            for(int i=0;i<200;i++){
                if (0<tickit){
                    System.out.println(name+" 票号=="+tickit--);
                }
            }
        }
    }

输出:

thread2 票号==10
thread2 票号==8
thread2 票号==7
thread1 票号==9
thread1 票号==5
thread1 票号==4
thread1 票号==3
thread2 票号==6
thread2 票号==1
thread1 票号==2

Process finished with exit code 0
由此Runnable相比Thread的优势:
  • 适合多个相同的程序代码的线程去处理同一个资源。
  • 可以避免java中的单继承的限制。
  • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
如何给run()方法传参
  • 构造参数传参
  • 成员变量传参
  • 回调函数传参

如何实现处理线程的返回值

1.主线程等待法
    class WaiRunnable implements Runnable{
        public String value ;
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                value = "it's Lillard time";
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    @Test
    public void mainWait() throws InterruptedException {
        WaiRunnable waitRunnable = new WaiRunnable();
        Thread t = new Thread(waitRunnable);
        t.start();
        //等待value赋值
        while (null==waitRunnable.value){
            Thread.sleep(1000);
        }
        System.out.println("value=="+waitRunnable.value);
    }
value==it's Lillard time

Process finished with exit code 0

缺点:

  • 需要自己去实现等待的逻辑
  • 需要等待多久是不确定的
2.使用Thread类的join()阻塞当前线程以等待子线程处理完毕

只需将上方的while循环改为t.join();

    @Test
    public void mainWait() throws InterruptedException {
        WaiRunnable waitRunnable = new WaiRunnable();
        Thread t = new Thread(waitRunnable);
        t.start();
        t.join();
        System.out.println("value=="+waitRunnable.value);
    }
value==it's Lillard time

Process finished with exit code 0

优点:

  • 实现简单
  • 控制精准

缺点:

  • 粒度不够细,我们上方票号的例子,如果当thread1卖出第5张时需要进行其他操作。通过join阻塞是无法实现的
3.通过Callable接口实现:通过FutureTask或线程池获取

FutureTask:

    class MyCallable implements Callable<String>{
        @Override
        public String call() throws Exception {
            System.out.println("ready");
            Thread.sleep(3000);
            return "it's Lillard time";
        }
    }

    @Test
    public void callable() throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyCallable());
        new Thread(futureTask).start();
        if (!futureTask.isDone()) {
            System.out.println("task not finish,please wait");
        }
        System.out.println("task return:"+futureTask.get());
    }
task not finish,please wait
ready
task return:it's Lillard time

Process finished with exit code 0

线程池:

    @Test
    public void executor(){
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future future = executorService.submit(new MyCallable());
        if (!future.isDone()) {
            System.out.println("task not finish,please wait");
        }
        try {
            System.out.println("task return:"+future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }
    }
task not finish,please wait
ready
task return:it's Lillard time

Process finished with exit code 0

Thread的join方法

join() method suspends the execution of the calling thread until the object called finishes its execution.

t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待子线程完成再结束main()主线程

    System.out.println("MainThread run start.");

    //启动一个子线程
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("threadA run start.");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("threadA run finished.");
        }
    });
    threadA.start();

    System.out.println("MainThread join before");
    try {
        threadA.join();    //调用join()
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("MainThread run finished.");

MainThread run start. 
threadA run start. 
MainThread join before 
threadA run finished. 
MainThread run finished.

sleep和wait的区别(两者都能使线程进行等待状态)

基本的差别
  • sleep是Thread类的方法,wait是Object类中的方法
  • sleep()方法可以在任何地方使用
  • wait()方法只能在synchronized方法或synchronized块中使用
最重要的本质区别
  • Thread.sleep只会让出CPU,不回导致锁行为的改变
  • Object.wait不仅让出CPU,还会释放已经占有的通过资源锁

代码实例:

@Test
    public void diffSleepAndWait() {
        final Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
1               System.out.println("Thread A is waiting to get lock");
                synchronized (lock) {
                    try {
2                       System.out.println("Thread A get lock");
                        Thread.sleep(20);
3                       System.out.println("Thread A do wati");
                        lock.wait(1000);
4                       System.out.println("Thread A is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
5               System.out.println("Thread B is waiting to get lock");
                synchronized (lock) {
                    try {
6                       System.out.println("Thread B get lock");
7                       System.out.println("Thread B is sleeping 10 ms");
                        Thread.sleep(10);
8                       System.out.println("Thread B is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
Thread A is waiting to get lock
Thread A get lock
Thread B is waiting to get lock
Thread A do wati
Thread B get lock
Thread B is sleeping 10 ms
Thread B is done
Thread A is done

Process finished with exit code 0

开启线程A并上锁(输出1.2),休眠20毫秒这样只休眠了10毫秒的主线程就会启动线程B(输出5,但由于现场A此时占有对象锁,所以不回往下执行),线程A的20秒休眠结束(输出3)调用wait休眠1秒,按照上方关于wait的理论此时释放线程A释放对象锁,线程B继续执行--输出67(Thread.sleep(10))只休眠10毫秒远比线程A的一秒短--输出8,最好线程A休眠时间到--输出4

将Thread A 标记3的wait改为sleep,然后运行同样也可以证明sleep只会让出cpu不回释放锁的结论

线程的状态

  • 新建(New):创建后未启动的线程状态
  • 运行(Runnable):包含Running和Ready
  • 无限期等待(Waiting):不回被分配CPU执行时间,需要显示被唤醒
    1.没有设置Timeout参数的Object.wait()
    2.没有设置Timeout参数的Thread.join()
    3.LockSupport.park()
  • 限期等待(Time Waiting):在一定时间后会由系统自动唤醒
    1.设置了Timeout参数的Object.wait()
    2.设置了Timeout参数的Thread.join()
    3.Thread.sleep(long time) 方法
    4.LockSupport.parkNanos(time)
    5.LockSupport.parkUntil(time)
  • 阻塞(Blocked):等待获取排它锁
  • 结束(Terminated):已终止线程的状态,线程已经结速执行

notify和notifyall的区别(都能唤醒等待状态的线程)

  • notifyall会让所有处于等待池中的线程全部进入锁池去竞争获取锁的机会
  • notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
两个概念
  • 锁池EntryList:
    线程A已经拥有了某个对象(不是类)的锁,而线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized(或者块)之前必须先获取该对象锁的拥有权,而恰巧该对象的锁目前正被线程A占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

  • 等待池WaitSet
    假设线程A调用了某个对象的waiit()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不回去竞争该对象的锁

yield

A hint to the scheduler that the current thread is willing to yield
its current use of a processor. The scheduler is free to ignore this
hint

  • 当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出cpu(不是锁)使用的暗示,但是线程调度器可能会忽略这个暗示
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for(int i= 0;i<6;i++){
                    System.out.println(Thread.currentThread().getName()+"  "+i);
                    if (i==2){
                        Thread.yield();
                    }
                }
            }
        };
        Thread threadA = new Thread(runnable,"A");
        Thread threadB = new Thread(runnable,"B");
        threadA.start();
        threadB.start();
A  0
A  1
A  2
B  0
B  1
B  2
A  3
A  4
A  5
B  3
B  4
B  5

Process finished with exit code 0

输出结果证实了结论,但由于线程调度器可能会忽略这个提示,所以上面这个输出不是100%复现

如何中断线程

已经被抛弃的方法

  • 通过调用stop()方法停止线程
  • 通过suspend和resume方法

目前使用的方法
调用interrupt(),通知线程应该中断了:

  • 如果线程处于被阻塞状态(sleep、wait、join),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
  • 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,被设置中断标志的线程将继续正常运行,不受影响

coding:

  Runnable interruptTask = new Runnable() {
      @Override
      public void run() {
          int i = 0;
          try {
              //在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
              while (!Thread.currentThread().isInterrupted()) {
                  Thread.sleep(100); // 休眠100ms
                  i++;
                  System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") loop " + i);
              }
          } catch (InterruptedException e) {
              //在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
              System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") catch InterruptedException.");
          }
      }
  };
      Thread t1 = new Thread(interruptTask, "t1");
      System.out.println(t1.getName() +" ("+t1.getState()+") is new.");

      t1.start();                      // 启动“线程t1”
      System.out.println(t1.getName() +" ("+t1.getState()+") is started.");

  // 主线程休眠300ms,然后主线程给t1发“中断”指令。
      Thread.sleep(300);
      t1.interrupt();
      System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");

  // 主线程休眠300ms,然后查看t1的状态。
      Thread.sleep(300);
      System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.

Process finished with exit code 0

由于intterrupt不会真的停止线程,所以需要检查中断标志来进行中断处理

相关文章

网友评论

    本文标题:Java--多线程

    本文链接:https://www.haomeiwen.com/subject/rhvggqtx.html