java并发(二)-java线程基础

2. Java线程基础

一、 创建新线程

1.1 继承Thread类

Thread t1 = new Thread();
t1.start();

start()方法可以创建一个新的线程,并让这个线程执行run()方法

Thread t2 = new Thread();
t2.run()

这种直接用run() 方法,不会创建一个新的线程,只是在当前线程中串行的执行run方法。


1.2 通过Runnable接口实现

public class CreateThread implements Runnable(){
  public static void mian(String args[]){
    Thread t1 = new Runnable(new CreateThread());
    t1.start();
  }
  @Override
  public void run(){
    System.out.println("Runnable Thread");
  }
}

二、 线程终止

Thread提供了stop() 方法,但是这是已经被废弃的方法。因为stop()方法会强行将执行一半的线程终结,可能会引

数据不一致的情况。

数据不一致

所以停止线程的方法,可以定义一个标记变量,用于指示线程是否需要退出。


三、 线程中断

与线程中断有关的三个方法

public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted //判断是否被中断,并清除当前中断状态       

Thread.interrupt()方法是一个实例方法。他通知目标线程中断,也就是设置中断标志。中断标志表示当前线程已经被中断了。

Thread.isInterrupted() 方法也是实例方法,它判断当前线程是否有被中断(通过检查中断标志位)。

Thread.interrupted() 也是用来判断当前线程的中断状态,但同时会清楚线程的中断标志位状态。


Thread.sleep()方法会让当前线程休眠若干时间,它会抛出一个InterruptedException 中断异常。
InterruptedException 不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep() 休眠时,如果被中断,这个异常就会产生。

public static void main(String args[]) throws InterrupterException{
  Thread t1  = new Thread();
  @Override
  public void run(){
    while(true){
      if(Thread.currentThread().isInterrupted()){
        System.out.println("Interruted");
        break;
      }
      try{
        Thread.sleep(2000); 
      }catch(InterruptedException e){
        System.out.println("Interrupted When Sleep");
        //设置中断状态
        Thread.currentThread().interrupte();
      }
      Thread.yield();
    }
  };
  t1.start();
  Thread.sleep(2000);
  t1.interrupt();
}

执行sleep()方法,会抛出异常,被catch捕获。可以立即退出线程了,但是为了保证数据的完整性,我们可以在

catch中重新设置中断状态(sleep()由于线程中断,会清除中断标记状态),继续后续的处理。


四、wait() 和 nodify()

一个线程调用了object.wait() 就进入了等待队列,直到其他线程调用了object.nodify()方法。当

object.nodify()被调用时,会随机从等待队列中选择一个线程,将其唤醒。nodifyAll(),唤醒等待队列中

的所有线程。


Object.wait() 方法不是可以随便调用的。它必须包含在对应的 synchronized 语句中,无论是wait() 或者 notify() 都需要首先获得目标对象的一个 监视器。而wait() 方法执行后,会释放这个监视器。这样能使其他线程不至于因为该线程的休眠而全部无法正常执行


五、挂起(suspend)和继续执行(resume)线程(已被废弃,不推荐使用)

一个线程执行suspend方法是会挂起,但是不释放锁,导致其他线程必须等到resume被执行,才能得到锁。如果

resumesuspend前不幸被执行,可能导致线程被永久挂起。



六、 等待线程join

两个join方法

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

第一个join() 方法表示无限等待,它会一直阻塞当前线程,知道目标线程执行完毕((别的线程得等调用了join的线程执行完)。第二个方法给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。

public class JoinMain{
    public volatile static int i = 0;
    public static class AddThread extends Thread{
        @Override
        public void run(){
            for(i=0;i<10000000;i++);
        }
    }
    public static void main(String[] args) throws InterruptedException{
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

如果不使用join(),可能输出的 i 的值很小,因为AddThread() 没来得及全部执行完,就已经输出了。使用了join(),只有等AddThread() 执行完,才执行其他线程。

join方法本质是让调用线程wait() 在当前线程的实例上。

join方法的核心代码:

while(isAlive){
  wait(0);
}

七、谦让yield() 方法:

定义如下:

public static native void yield();

调用该方法后,线程会让出CPU,但不意味着不进行CPU的竞争。让出CPU后,还是会进行竞争的。如果认为一

个线程不是那么重要,优先级不是那么高,那么可以适当调用Thread.yield(),让其他线程有更多机会。


八、守护线程Daemon

守护线程是一种特殊的线程,就和它的名字一样,它是系统的守护者,在后台默默地守护一些系统服务,比如垃圾

回收线程,JIT线程就可以理解守护线程。与之对应的就是用户线程,用户线程就可以认为是系统的工作线程,它

会完成整个系统的业务操作。用户线程完全结束后就意味着整个系统的业务任务全部结束了,因此系统就没有对象

需要守护的了,守护线程自然而然就会退。当一个Java应用,只有守护线程的时候,虚拟机就会自然退出。下面以

一个简单的例子来表述Daemon线程的使用。

public class DaemonDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        System.out.println("i am alive");
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        System.out.println("finally block");
                    }
                }
            }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();
        //确保main线程结束前能给daemonThread能够分到时间片
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

i am alive finally block i am alive

上面的例子中daemodThread run方法中是一个while死循环,会一直打印,但是当main线程结束后

daemonThread就会退出所以不会出现死循环的情况。main线程先睡眠800ms保证daemonThread能够拥有一次

时间片的机会,也就是说可以正常执行一次打印“i am alive”操作和一次finally块中"finally block"操作。紧接着

main 线程结束后,daemonThread退出,这个时候只打印了"i am alive"并没有打印finnal块中的。因此,这里需

要注意的是守护线程在退出的时候并不会执行finnaly块中的代码,所以将释放资源等操作不要放在finnaly块中执

行,这种操作是不安全的

线程可以通过setDaemon(true)的方法将线程设置为守护线程。并且需要注意的是设置守护线程要先于start()方

法,否则会报

Exception in thread "main" java.lang.IllegalThreadStateException at java.lang.Thread.setDaemon(Thread.java:1365) at learn.DaemonDemo.main(DaemonDemo.java:19)

这样的异常,但是该线程还是会执行,只不过会当做正常的用户线程执行。


九、线程状态转化图



参考

《实战Java高并发程序设计》

线程状态及基本操作

posted @ 2020-10-10 12:49  面壁者逻辑  阅读(114)  评论(0)    收藏  举报