zoukankan      html  css  js  c++  java
  • 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()方法调用,无需担心线程间执行的先后顺序。

  • 相关阅读:
    .NET破解之太乐地图下载器【非暴破】
    DevExpress中透明玻璃效果
    mysql实时同步到mssql的解决方案
    求点云的边界的方法小结
    ArcEngine中License权限等级更改的问题
    汉化入门之ExplorerControls
    spring cloud ribbon和feign的区别
    IntelliJ IDEA 运行 Maven 项目
    Python中操作SQLAlchemy
    MySQL 可以用localhost 连接,但不能用IP连接的问题,局域网192.168.*.* 无法连接mysql
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/12515922.html
Copyright © 2011-2022 走看看