美文网首页
Java多线程

Java多线程

作者: 卡路fly | 来源:发表于2020-04-22 20:40 被阅读0次

    线程状态

    1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。

      例如,Thread thread = new Thread()。

      处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的(not alive)。

    2. 就绪状态(Runnable): 也被称为“可执行状态”。thread.start(),处于就绪状态的线程,随时可能被CPU调度执行,

      此时线程是活着的。

    3. 运行状态(Running) : 一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程的run()方法才开始被执行。

      在运行状态的线程执行自己的run()方法中的操作,直到调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;

      如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的等待状态;

      此时线程是活着的(alive),线程只能从就绪状态进入到运行状态

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

       阻塞的情况分三种:
       (01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
       (02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
       (03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
      
    5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

      对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime exception)的异常;处于Dead状态的线程不是活着的(not alive)。


    Thread.join()等待线程

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

    Java Thread.Sleep()暂停当前线程 (Running -> Runnable)

    • Thread.sleep()被用来暂停当前线程的执行,会通知线程调度器把当前线程在指定的时间周期内置为wait状态。

      当wait时间结束,线程状态重新变为Runnable并等待CPU的再次调度执行。所以线程sleep的实际时间取决于线程调度器,而这是由操作系统来完成的。

    • 在windows环境下,进程调度是抢占式的。

      一个进程在运行态时调用sleep(),进入等待态,睡眠结束以后,并不是直接回到运行态,而是进入就绪队列,要等到其他进程放弃时间片后才能重新进入运行态。所以sleep(1000),在1000ms以后,线程不一定会被唤醒。sleep(0)可以看成一个运行态的进程产生一个中断,由运行态直接转入就绪态。这样做是给其他就绪态进程使用时间片的机会。总之,还是操作系统中运行态、就绪态和等待态相互转化的问题。


    Java Thread synchronized同步锁

    当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。不同线程对同步锁的访问是互斥的

    用法

    1. 修饰普通方法,对普通方法同步

    修饰一个普通方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象

    public class SynchronizedTest {
        public synchronized void method1(){
            System.out.println("Method 1 start");
            try {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 1 end");
        }
    
        public synchronized void method2(){
            System.out.println("Method 2 start");
            try {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest test = new SynchronizedTest();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    

    线程2需要等待线程1的method1执行完成才能开始执行method2方法。执行结果如下:

    Method 1 start
    Method 1 execute
    Method 1 end
    Method 2 start
    Method 2 execute
    Method 2 end
    
    

    2. 修饰静态方法,静态方法(类)同步

    修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象

    public class SynchronizedTest {
         public static synchronized void method1(){
             System.out.println("Method 1 start");
             try {
                 System.out.println("Method 1 execute");
                 Thread.sleep(3000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("Method 1 end");
         }
     
         public static synchronized void method2(){
             System.out.println("Method 2 start");
             try {
                 System.out.println("Method 2 execute");
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("Method 2 end");
         }
     
         public static void main(String[] args) {
             final SynchronizedTest test = new SynchronizedTest();
             final SynchronizedTest test2 = new SynchronizedTest();
     
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     test.method1();
                 }
             }).start();
     
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     test2.method2();
                 }
             }).start();
         }
    }
    

    对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。执行结果如下:

    Method 1 start
    Method 1 execute
    Method 1 end
    Method 2 start
    Method 2 execute
    Method 2 end
    

    3. 修饰代码块,代码块同步

    修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象

    public class SynchronizedTest {
        public void method1(){
            System.out.println("Method 1 start");
            try {
                synchronized (this) {
                    System.out.println("Method 1 execute");
                    Thread.sleep(3000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 1 end");
        }
    
        public void method2(){
            System.out.println("Method 2 start");
            try {
                synchronized (this) {
                    System.out.println("Method 2 execute");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest test = new SynchronizedTest();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    

    虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。执行结果如下:

    Method 1 start
    Method 1 execute
    Method 2 start
    Method 1 end
    Method 2 execute
    Method 2 end
    

    4. 修饰一个类,对类同步

    修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象

    /**
     * 同步线程
     */
    class SyncThread implements Runnable {
       private static int count;
    
       public SyncThread() {
          count = 0;
       }
    
       public static void method() {
          synchronized(SyncThread.class) {
             for (int i = 0; i < 5; i ++) {
                try {
                   System.out.println(Thread.currentThread().getName() + ":" + (count++));
                   Thread.sleep(100);
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
             }
          }
       }
    
       public synchronized void run() {
          method();
       }
    }
    

    synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。


    Java Thread.yield() 线程协作让步(Running -> Runnable)

    Thread yield()方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态。

    1. 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
    
    2. 用了yield方法后,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
    
    3. 通过yield方法来实现两个线程的交替执行。不过请注意:这种交替并不一定能得到保证。
    
    4. yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会
    

    Java Thread sleep(),wait()区别详解

    对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep用于线程控制,而wait用于线程间的通信。

    sleep()简介

    • sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

    • sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。

    • 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

    wait()简介

    • wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;

    • wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

    • wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

    总结

    1. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
    
    2. 调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
    

    Java Thread类实现多线程方法

    主要方法 优点 缺点
    Thread start(),线程处于就绪状态,start()方法启动线程将自动调用 run()方法 编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。 因为线程类已经继承了Thread类,所以不能再继承其他的父类。
    Runnable(推荐) run(),普通方法 线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

    守护线程

    在虚拟机的离开,当JVM中所有的线程都是守护线程的时候,JVM就可以退出了。

    public class DaemonThreadTest {
    
        public static void main(String[] args) {
            Thread t = new Thread1();
            // 如果注释下一行,Thread1不会停止。
            t.setDaemon(true);
            t.start();
            System.out.println("main: wait 3 sec...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
            System.out.println("main: end.");
        }
    
    }
    
    class Thread1 extends Thread {
    
        public void run() {
            for (; ; ) {
                System.out.println("Thread-1: running...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
        }
    }
    

    整理自,吹爆,里面有更加详细。

    相关文章

      网友评论

          本文标题:Java多线程

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