Java 线程的基本使用

 

线程的2种实现方式

1、继承Thread类,重写run()方法

public class Thread1 extends Thread{

    @Override
    public void run() {
        //要执行的代码
        while (true){
            System.out.println("thread1 is running....");   
        }
    }

}
 Thread1 thread1 = new Thread1();
 thread1.start();

 

 

2、实现Runnable接口

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        //要执行的代码
        while (true) {
            System.out.println("thread2 is running....");
        }
    }

}
MyRunnable myRunnable = new MyRunnable();
Thread thread2 = new Thread(myRunnable);
thread2.start();

相比而言,第一种要简单些。

 

 

我们测试下:

public class Test {

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        thread1.start();

        MyRunnable myRunnable = new MyRunnable();
        Thread thread2 = new Thread(myRunnable);
        thread2.start();
    }

}

运行,看到thread1、thread2交替执行

 

线程的start()方法是初始化一个线程、分配一条线程所需的资源,把这条线程放到调度队列中,让线程处于ready状态。获取到cpu的使用权时自动调用线程的run()方法执行代码。

如果是直接调用run()方法,那就只是在当前线程中调用一个类的一个方法,并没有使用多线程。

 

 


 

 

 

线程的6种状态

Thread类在内部枚举类中定义了6种状态:

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
}

 

1、New  新建

new Thread()已经创建线程,但尚未调用start()启动线程

 

 

2、RUNNABLE  可运行

Java把就绪(ready)和运行(running)两种状态合并为一种状态:可运行(runnable)。

调用了start方法,线程就处于可运行状态(就绪)。此线程获取到时间片后,开始执行run()中的代码,处于运行(running)状态。

 

 

3、BLOCKED  阻塞

处于阻塞状态的线程并不会占用CPU资源。以下情况会让线程进入阻塞状态:

①等待获取锁

等待获取一个锁,而该锁被其它线程持有,则该线程进入阻塞状态。当其它线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。

②IO阻塞

线程发起了一个阻塞式IO后也会进入阻塞状态,比如等待用户输入内容然后继续执行。

IO是很广的概念,包括磁盘IO、网络IO等。

 

 

4、WAITING  无限期等待

处于此种状态的线程不会被分配 CPU 时间片,需要被其它线程显式地唤醒,才会进入就绪状态。

以下3个方法会让线程进入无限等待状态

  • Object.wait()                 结束:Object.notify() / Object.notifyAll()
  • Thread.join()                     结束:被调用的线程执行完毕
  • LockSupport.park()          结束:LockSupport.unpark(currentThread)

 

 

5、TIMED_WAITING  限时等待

处于这种状态的线程也不会被分配CPU 时间片,但在指定时间之后会被系统自动唤醒,进入就绪状态。

以下3个方法会让线程进入限时等待状态:

  • Thread.sleep(time) 方法          结束:sleep时间结束
  • Object.wait(time) 方法             结束:wait时间结束,或者调用Object.notify() / notifyAll()    
  • LockSupport.parkNanos(time)/parkUntil(time) 方法     结束:park时间结束,或者调用LockSupport.unpark(currentThread)

 

处于阻塞状态、无限期等待、限时等待的线程都会让出cpu的使用权,阻塞结束、显式唤醒后进入就绪状态,需要重新获取时间片才能接着运行。

 

 

6、TERMINATED  死亡

线程结束任务之后自动消亡,或者线程执行时发生了异常而结束。

 

 


 

 

 

 线程的生命周期

 

 

 

 

 


 

 

 

常用方法

 1、获取当前线程

 Thread thread = Thread.currentThread(); //获取当前线程

 

 

2、线程的基本信息

        Thread thread = new Thread1(); //只要是Thread类的对象即可
        System.out.println(thread.getId()); //线程id。
        System.out.println(thread.getName()); //线程名,不是线程对象名。主线程为main,其它为Thread-n,n从0开始
        System.out.println(thread.getPriority()); //优先级,未指定时默认为5
        System.out.println(thread.getState()); //线程状态。6种状态中的某一种
        System.out.println(thread.isDaemon());  //是否是守护线程,默认是false

 

        Thread thread = new Thread1(); //只要是Thread类的对象即可
     
        thread.setName("my_thread"); //设置线程名,默认主线程是main,其它线程以Thread-n的方式命名

        thread.setPriority(5); //设置线程优先级,[1,10]上的整数,默认为5。优先级高的线程优先执行
        thread.setPriority(Thread.MIN_PRIORITY); //1
        thread.setPriority(Thread.NORM_PRIORITY); //5
        thread.setPriority(Thread.MAX_PRIORITY); //10

        thread.setDaemon(true); //设置是否为守护线程,默认false
        
        thread.start(); //设置线程的基本信息要在start()启动线程之前

 

 

 

3、线程的优先级

不同的操作系统, 对线程优先级的支持不同,一般情况下优先级高的线程优先分配时间片,优先被执行,但并一定是如此。

写代码而定时候, 不要过度依赖线程优先级, 如果程序运行是否正确取决于是否按所设置的优先级运行, 那可能会出问题。最好加同步锁。

 

线程的优先级具有传递性:

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Thread.currentThread().setPriority(10);
        Thread thread = new Thread1();
        thread.start(); //在一个线程中start()开启另一个线程,开启的线程会自动继承开启它的线程的优先级
        System.out.println(thread.getPriority()); //10
    }

}

 

 

 

4、守护线程

线程分为2类:用户线程、守护线程。线程默认为用户线程(main线程默认也是用户线程)。

 

如果一个程序|应用中只有一个线程在运行,且这个线程是守护线程,则jvm直接退出,程序终止运行。

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        thread1.setDaemon(true); //守护线程
        thread1.start();
        
        System.out.println("main"); //至此main线程(用户线程)执行完毕,只有一个线程,且是守护线程,jvm退出,守护线程不再继续执行,程序终止
    }

}

start()开启线程后,这句代码就算执行完了,继续执行后面的代码,并不是说要等开启的线程执行完毕、这句代码才算执行完。

 

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        thread1.setDaemon(true); //守护线程
        thread1.start();

        Thread2 thread2 = new Thread2();
        thread2.start(); //用户线程

        System.out.println("main"); //至此main线程执行完毕
    }

}

thread2、main线程执行完毕后,jvm退出,thread1不再继续执行,程序终止。

 

如果没有用户线程,但是有2个及以上的守护线程在运行,则jvm不会退出,程序(守护线程)继续执行。

 

 

 

 

5、join()    Thread的实例方法,在当前线程中加入另一条线程

public class Test {

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        thread1.start();
        thread1.join(); //在当前线程(此处是主线程)中加入线程thread1(把thread1加入当前线程中)。join()要放在start()之后
        //....
    }

}

如果不使用join(),2条线程会交替执行,使用join()之后,会先把加入的线程执行完,再执行当前线程。

 

 

 

6、yield()    线程让步,Thread类的静态方法

当前线程让出cpu的使用权,进入ready状态。

这个方法是不可靠的,不一定会真的让出来,视系统当前的运行状况而定。

 

 

 

7、sleep()   Thread类的静态方法,使当前线程休眠指定时间

public class Thread1 extends Thread{

    @Override
    public void run() {
        System.out.println("thread1 is running...");

        try {
            Thread.sleep(500); //使当前线程休眠500ms,会让出cpu的使用权,500ms后自动苏醒,进入ready状态
      
Thread.sleep(500,100); //精确到纳秒。参数:毫秒、纳秒 Thread.sleep(0); //触发操作系统立刻重新进行一次CPU竞争。 // Thread1继承自Thead,也可以使用Thread1来操作 } catch (InterruptedException e) { //如果在线程休眠时,此线程被中断,会抛出线程中断异常,并立即唤醒此线程,进入ready状态。因为线程的中断只能由线程本身来控制 e.printStackTrace(); } System.out.println("thread1 is running again..."); } }

 

 

 

 

8、wait()、notify()、notifyAll()

这三个方法需要控制对对象的控制权(monitor),所以属于Object类,均为Object的实例方法。

这三个方法能够被调用的前提是已经获取了相应的互斥锁,所以这三个方法都只能在同步代码块中使用。

 

wait(),把持有该对象线程的对象控制权交出去(释放锁),然后处于等待状态。

notify(),会通知某个正在等待这个对象的控制权的线程可以继续运行(获取锁)。

nofifyAll(),会通知所有等待这个对象控制权的线程继续运行,如果有多个正在等待该对象控制权时,具体唤醒哪个线程,就由操作系统进行调度。

 

 

使用示例:

public class Thread1 extends Thread {
    public static Object lock = new Object(); //锁。可以把要使用的公共资源直接作为锁,比如大家都要使用的文件,也可以把一个Object对象作为锁,获取到锁后,才可以操作公共资源

    @Override
    public void run() {
        //....
        
        //同步代码块
        synchronized (lock){  //获取锁
            System.out.println("已获取到锁,开始使用资源");
            //.....
            try {
                lock.wait(); //让出cpu使用权,释放锁,进入WAITING状态(无限期等待),如果要继续运行,需要在其它线程中显式唤醒
         //wait(1000); //进入TIMED-WAITING(限时等待状态),指定时间后会自动唤醒,进入ready状态
         //wait(1000,100); //精确到纳秒 } catch (InterruptedException e) { e.printStackTrace(); } //..... //如果同步代码块中、wait()后面还有代码,被其它线程唤醒后进入ready状态,获取到时间片后需要重新获取锁后才能继续执行后面的代码 } //..... //如果同步代码块中、wait()后面没有代码,则被其它线程唤醒后进入ready状态,获取到时间片后就继续执行,无需获取锁 } }

 

public class Thread2 extends Thread {
    public static Object lock = Thread1.lock; //锁,要是同一个对象

    @Override
    public void run() {
        //....

        //同步代码块
        synchronized (lock){
            lock.notify();
            // lock.notifyAll(); 
            System.out.println("已获取到锁,开始使用资源");
            //.....
        }

        //..... 
    }

}

wait()让出锁后,这把锁分配给等待获取这个锁的哪个线程?

如果等待这个锁的某个线程在同步代码块中使用了notify(),则唤醒该线程,直接把锁分配给这个线程。

如果等待这个锁的那些线程都使用notifyAll(),则唤醒所有等待这把锁的线程,由操作系统调度,由操作系统决定分配给哪个线程。

 

public class Test {

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        thread1.start();

        Thread2 thread2 = new Thread2();
        thread2.start();
    }

}

需要先启动wait()方法所在的线程,再启动notify()方法所在的线程

 

 

锁怎么写?

  • 写成public static的成员变量,其它线程直接获取
  • 写成private,设置一个构造方法传入锁,锁对象在父线程(比如上面的主线程)中创建

怎么写都行,只要是同一个锁对象(地址相同)。

 

 

 

 

9、线程中断

如果要在sleep()、wait()指定的时间之前就唤醒线程,或者wait()未指定等待时间、要唤醒线程,都可以使用interrupt()来唤醒线程。

sleep()、wait()都是放在try中的,如果在休眠、等待期间,在其它线程中使用interrupt()中断正在休眠、等待的线程,增在休眠|等待的线程会抛出中断异常,并立即被唤醒,进入ready状态。

其实如果wait()未指定等待时间,等其它等待这个锁的线程都用完了这个锁(没线程使用这个锁的时候),也会被自动唤醒。

 

  • interrupt()   Thread类的实例方法,中断线程。比如thread1.interrupt();中断thread1。

  只是设置一个中断标志(发送一个中断信号),由该线程自己决定怎么做(在合适的时间、代码处停下来),并不是说该线程一定会马上被中断(其实往往会继续运行)

  除了中断正在运行的线程,还可以唤醒沉睡|等待的线程。

 

  • isInterrupted()  Thread类的实例方法,判断线程是否真的被中断了
  • interrupted()   Thread类的静态方法,判断当前线程是否真的被中断了,并清除之前设置的中断标志

 

 

 

 

10、wait()和sleep()的区别

①sleep()是Thread类的静态方法,wait()是Object类的实例方法。

②sleep()不会释放锁,当前线程仍持有锁;wait()会释放锁,被唤醒后进入ready状态,获取时间片后需要重新获取锁。

③sleep()必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。

④wait()、notify()、notifyAll()必须在同步方法或同步代码块中调用(总之要先获取到锁),而sleep没有这方面的限制。

 

 

 

 

11、park()、unpark()

均为LockSupport类的静态方法。

LockSupport.park()将当前线程挂起,让出cpu的使用权。park()、parkNanos()、parkUntil(),均有多个重载方法,可以设置挂起时间、锁对象。

注意:只是将当前线程挂起,并不会释放锁。

 

LockSupport.unpark(Thread thread)将指定线程立即恢复运行(ready)状态。等park()指定的时间到了,或者等待这个锁的所有线程都执行完毕,也会重新回到运行状态。

thread.interrupt()线程中断具有unpark()同样的功能。

 

LockSupport比Object的wait()、notify()有两大优势:

①LockSupport不需要写在同步代码块里 ,所以线程间也不需要维护一个共享的同步对象了(锁),实现了线程间的解耦。

②unpark()方法可以先于park()方法调用,无需担心线程间执行的先后顺序。

 

posted @ 2020-03-21 19:43  chy_18883701161  阅读(548)  评论(0编辑  收藏  举报