• 多线程基础


    java多线程基础整理

    一、线程和进程

    二、Thread和Runnable,创建线程

    三、Thread类中的Run和Start方法的区别

    四、如何处理线程的返回结果

    五、线程的状态

    六、线程相关的常用方法

    七、关于线程状态的补充

    一、线程和进程

    一般都知道,线程是被包含在进程里的,一个进程可以有多个线程同时存在。

    进程是资源分配的最小空间,线程是cpu调度的最小单位。

    进程和线程的区别:
    1、线程不能看做独立应用,而进程可看做独立应用。
    2、进程有独立的地址空间,互相不影响,线程只是进程的不同执行路径。
    3、线程没有独立的地址空间,多进程的程序比多线程程序健壮。
    4、进程的切换比线程的切换开销大。

    java中线程和进程的关系:
    1、java对操作系统提供的功能进行封装,包括进程和线程。
    2、运行一个java程序会产生一个进程,进程包含至少一个进程。
    3、每个进程对应一个JVM实例,多个线程共享JVM里的堆。
    4、java采用单线程编程模型,程序会自动创建主线程。
    5、主线程可以创建子线程,原则上要后于子线程完成执行。

    二、Thread和Runnable,创建线程

    Runnable是一个函数式接口,内部只有一个public abstract void run()的抽象方法,所以它本身是不带有多线程的功能的。Thread是实现了Runnable接口的类,它的start方法才使得run()里的代码支持多线程特性。

    由于java的单一继承原则,推荐使用Runnable。例如可以将业务类实现Runnable接口,将业务逻辑封装在run方法里,将给业务类作为参数传递给Thread类,可以实现多线程的特性。但是,如果通过继承Thread类的方式实现多线程,那么业务类将无法继承其他类。

    另外:Runnable里的资源可以共享,下面使用代码比较说明:

    使用Thread:

    public class MyThread extends Thread {
        int count = 5;
    
        @Override
        public void run() {
            while (count > 0){
                System.out.println(Thread.currentThread().getName()+ ":" + count);
                count--;
            }
        }
    }
    public class ThreadDome {
        public static void main(String[] args){
            Thread thread1 = new MyThread();
            Thread thread2 = new MyThread();
            Thread thread3 = new MyThread();
    
            thread1.start();
            thread2.start();
            thread3.start();
    
        }
    }

    运行结果:

    Thread-0:5
    Thread-0:4
    Thread-0:3
    Thread-0:2
    Thread-1:5
    Thread-1:4
    Thread-0:1
    Thread-1:3
    Thread-1:2
    Thread-1:1
    Thread-2:5
    Thread-2:4
    Thread-2:3
    Thread-2:2
    Thread-2:1

    使用Runnable:

    public class MyRunnable implements Runnable {
        private int count = 5;
    
        @Override
        public void run() {
            while (count > 0){
                System.out.println(Thread.currentThread().getName() +":"+ count);
                count--;
            }
        }
    }
    public class RunnableDome {
        public static void main(String[] args){
            MyRunnable myRunnable1 = new MyRunnable();
    
            Thread thread1 = new Thread(myRunnable1);
            Thread thread2 = new Thread(myRunnable1);
            Thread thread3 = new Thread(myRunnable1);
    
            thread1.start();
            thread2.start();
            thread3.start();
    
        }
    }

    结果:

    Thread-0:5
    Thread-1:5
    Thread-0:4
    Thread-0:2
    Thread-1:3
    Thread-0:1

    上述比较主要是说明,通过Runnable参数创建的Thread,Runnable里的变量是共享的。但是同时也需要注意线程安全了。

    三、Thread类中的Run和Start方法的区别

    先说一下结论:它们都可以实现启动run方法里的逻辑,区别是,run方法调用的主线程,start方法调用的是新创建的线程。

    1、代码说明:

    先是run方法:

    public class ThreadTest {
        public static void attack(){
            System.out.println("Current thread is :" + Thread.currentThread().getName());
        }
    
        public static void main(String[] args){
            Thread r = new Thread(){
                @Override
                public void run() {
                    attack();
                }
            };
            System.out.println("Current main thread is :" + Thread.currentThread().getName());
            r.run();
        }
    }

    结果:

    Current main thread is :main
    Current thread is :main

    可以发现,当前线程是main

    start方法:

    public class ThreadTest {
        public static void attack(){
            System.out.println("Current thread is :" + Thread.currentThread().getName());
        }
    
        public static void main(String[] args){
            Thread r = new Thread(){
                @Override
                public void run() {
                    attack();
                }
            };
            System.out.println("Current main thread is :" + Thread.currentThread().getName());
            r.start();
        }
    }

    结果:

    Current main thread is :main
    Current thread is :Thread-0

    可以发现,当前线程是新创建的Thread-0.

    2、为什么会这样?

    首先,run方法是Runnable的一个抽象方法,Thread类重写这个方法的源码如下:

        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }

    target就就是当做参数传入的Runnable接口。如果是通过参入Runnable 参数实现的Thread,就调用Runnable的run方法;而如果没有传入Runnable接口,那么targer会为空,这时意味着,run方法被Thread的继承类重写或是被匿名内部类重写,则会调用相应的重写run方法。

    但是,通过上述代码没有找到新线程的创建的逻辑,是因为,新线程的创建在Thread类的start方法里,代码:

       public synchronized void start() {
            /**
             * This method is not invoked for the main method thread or "system"
             * group threads created/set up by the VM. Any new functionality added
             * to this method in the future may have to also be added to the VM.
             *
             * A zero status value corresponds to state "NEW".
             */
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
    
            /* Notify the group that this thread is about to be started
             * so that it can be added to the group's list of threads
             * and the group's unstarted count can be decremented. */
            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 */
                }
            }
        }

    继续查看start0的方法:

        private native void start0();

    这里是调用了外部的源码,继续查找(查看源码网址:链接):

     {"start0",           "()V",        (void *)&JVM_StartThread},

    调用jvm包里的JVM_StartThread方法(链接):

    其中关键:

    native_thread = new JavaThread(&thread_entry, sz);

    继续查看:thread_entry方法:

    static void thread_entry(JavaThread* thread, TRAPS) {
      HandleMark hm(THREAD);
      Handle obj(THREAD, thread->threadObj());
      JavaValue result(T_VOID);
      JavaCalls::call_virtual(&result,
                              obj,
                              KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                              vmSymbols::run_method_name(),
                              vmSymbols::void_method_signature(),
                              THREAD);
    }

    最终在创建线程之后,调用run方法(vmSymbols::run_method_name())。

    所以start的逻辑就是:先调用底层代码创建线程,然后返回调用run方法。

     四、如何处理线程的返回结果

    再多线程的运行环境中,代码并非顺序执行了,当线程运行后的代码跟线程的处理结果有关时,该如何准确的得到呢?

    这里给出三种处理方式:1、循环等待 2、join方法阻塞等待 3、使用Callable接口,并用FutureTask或线程池处理。

    1、循环等待

    import lombok.SneakyThrows;
    
    public class CycleWait {
        static String value;
    
        @SneakyThrows
        public static void main(String[] args){
            Thread thread = new Thread(){
                @SneakyThrows//lombok注解,直接抛出非运行时异常
                @Override
                public void run() {
                    System.out.println("run start!");
                    Thread.currentThread().sleep(3000);
                    value = "success!";
                    System.out.println("run done!!");
    
                }
            };
            thread.start();
            while(value == null){
               Thread.currentThread().sleep(100);
            }
            System.out.println(value);
        }
    }

    如果没有进行循环等待,最终打印出的value会是null

    结果:

    run start!
    run done!!
    success!

    2、使用join方法

    import lombok.SneakyThrows;
    
    public class CycleWait {
        static String value;
    
        @SneakyThrows
        public static void main(String[] args){
            Thread thread = new Thread(){
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println("run start!");
                    Thread.currentThread().sleep(3000);
                    value = "success!";
                    System.out.println("run done!!");
    
                }
            };
            thread.start();
            thread.join();
            System.out.println(value);
        }
    }

    结果:

    run start!
    run done!!
    success!

    3、使用Callable接口,并用FutureTask或线程池处理。

    使用Callable接口并用FutureTask处理,这里的FutureTask实现了Runnable,相当于使用Runnable创建线程类。

    先创建继承Callable的实现类

    import java.util.concurrent.Callable;
    
    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("call start...");
            Thread.currentThread().sleep(1000);
            System.out.println("call over!!");
            return "Callable success!!";
        }
    }

    a、FutureTask调用:

    import java.util.concurrent.FutureTask;
    
    public class FutureTaskDemo {
    
        @SneakyThrows
        public static void main(String[] args){
            FutureTask futureTask = new FutureTask(new MyCallable());
            new Thread(futureTask).start();
    
            System.out.println("return:" + futureTask.get());
        }
    }

    结果:

    call start...
    call over!!
    return:Callable success!!

    这里FutureTask中的get方法,实现自Future接口,是一个阻塞方法,知道线程处理完成才会结束该方法调用。

    b、线程池调用

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class ThreadPoolDemo {
    
        @SneakyThrows
        public static void main(String[] args){
            ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
            Future<String> future = newCachedThreadPool.submit(new MyCallable());
    
            System.out.println(future.get());
        }
    }

     五、线程的状态

    在Thread类中的State枚举类下,线程有6个状态:(新建)new、(运行)Runnable、(阻塞)blocked、9(无限期等待)waiting、(限期等待)timed_waiting、(结束)terminated

    1、New——创建后尚未启动的线程状态,start之间的线程状态。

    2、Runnable——包含操作系统线程状态中的Running和Ready,start之后,线程进入线程池等待cpu调用,在等待cpu调用时就是Ready状态,cpu调用时就是Running状态。

    3、Waiting——不会被cpu调用,需要被其他线程显示的唤醒

        会造成waiting状态的方法:

           没有设置等待时间的Object.wait()方法

           没有设置等待时间的Thread.join()方法

           LockSupport.park()方法

    4、timed_waiting——在一定时间后会由系统自动唤醒

        会造成timed_waiting状态的方法:

           设置了等待时间的Object.wait()方法

           设置了等待时间的Thread.join()方法

           LockSupport.parkNames()方法

           LockSupport.parkUnitil()方法

    5、Blocked——等待获取排他锁

    6、Terminated——线程已经结束执行

     六、线程相关的常用方法

    1、sleep

    public static native void sleep(long millis) throws InterruptedException;

    是线程类的静态方法,让线程进入一定时间的等待状态。

    只会让出cup运行时间,不会放弃同步锁。

    2、wait、notify和notifyAll

        public final void wait() throws InterruptedException {
            wait(0);
        }
        public final native void wait(long timeout) throws InterruptedException;
    
        public final native void notify();
    
        public final native void notifyAll();

    Object类的实例方法。

    调用有参数的wait方法的线程会等待一定时间。

    调用没有参数的wait方法的线程会进入等待池中无限等待,直到被notify或notifyAll唤醒。

    wait方法会释放同步锁,带不带参数都会。

    wait和notify方法只能在书写在synchronized方法(块)中,而且只能被当前synchronized锁对象调用。

    notify和notifyAll的区别是,notifyAll会唤醒所有等待池中的线程进入锁池,而notify只会随机选中一个唤醒。

    首先介绍两个概念:锁池和等待池

    锁池:假设线程A已经占用了某个实例对象的锁,线程B、C在此时想调用该对象的synchronized方法或块,那么B、C会被阻塞,进入锁池,等待锁的释放。

    等待池:假设线程A调用了某个对象的wait方法,线程A就会释放该对象的锁,并进入等待池,进入等待池的线程不会去竞争该对象的锁。直到被notify方法调用,进入锁池。

    所以,当线程调用wait方法时,该线程会进入等待池中,直到被其他线程的notify或notifyAll方法唤醒,进入锁池,等待cpu调用。

    调用wait或notify的书写方式:

    根据synchronazied的加锁对象,调用该锁的方法。

    实例:

                    synchronized (object){
                        System.out.println("wait start ....");
                        object.wait();
                        System.out.println("wait over!!");
                    }

    如果加的是锁是this,可直接书写方法:

                    synchronized (this){
                        System.out.println("wait start ....");
                        wait();
                        System.out.println("wait over!!");
                    }

    注意事项:

    a、在某个锁的synchronized方法(块),只能调用该锁的wait和notify方法。

    如:错误实例

                    synchronized (object){
                        System.out.println("wait start ....");
                        this.wait();
                        System.out.println("wait over!!");
                    }

    object锁中,只能调用object的wait方法,不能调用this的wait方法。该调用会报异常:java.lang.IllegalMonitorStateException

    b、一个锁的wiat方法只能用同一锁的notify方法唤醒。

    即:只有object.notify()方法才能唤醒,object.wait()方法挂起的线程。

    c、使用notify方法唤醒其他线程时,notify所在线程会先执行完毕,wait方法所在线程才会执行,并按照进入等待池中的先后顺序执行。

    实例:

    import lombok.SneakyThrows;
    
    public class WaitTest {
        static Object object = new Object();
    
        @SneakyThrows
        public static void main(String[] args){
    
    
            Runnable waitRunnable = new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println(Thread.currentThread().getName()+" start ....");
                        object.wait();
                        System.out.println(Thread.currentThread().getName()+" over!!");
                    }
    
                }
            };
    
            Runnable notifyRunnable = new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    synchronized (object){
                        System.out.println("notify start ....");
                        object.notifyAll();
                        Thread.sleep(3000);
                        System.out.println("notify已阻塞.....");
                        System.out.println("notify over!!");
                    }
    
                }
            };
    
            Thread wait = new Thread(waitRunnable,"wait");
            Thread wait1 = new Thread(waitRunnable,"wait1");
            Thread wait2 = new Thread(waitRunnable,"wait2");
            Thread notify = new Thread(notifyRunnable);
    
            wait1.start();
            wait2.start();
    
            wait.start();
    
            Thread.sleep(100);
            notify.start();
    
    
    
        }
    }

    结果:

    wait1 start ....
    wait2 start ....
    wait start ....
    notify start ....
    notify已阻塞.....
    notify over!!
    wait over!!
    wait2 over!!
    wait1 over!!

    以上结果可知:

    notify执行完毕,被wait的线程才执行,并且按照执行wait的先后顺序再次执行。

    3、yield

        public static native void yield();

    当调用Thread.yield方法时,会给线程调度器一个当前线程愿意让出cpu使用的暗示,但是线程调用可能会忽略。如果让出,会重写等待cpu调度。

    yield方法,不会释放同步锁。

    4、interrupt

    interrupt()是线程中断方法,该方法调用时不会立即中断线程,而是将中断标记更改为true,之后会有两种情况。

      1.当线程状态变为阻塞时,该线程会抛出InterruptedException异常,线程借助异常中断。

      2.若线程标记更改为中断后没有遇到阻塞的情况,不会对线程造成影响。

    该方法是Thread类的实例方法。

    5、join

      A线程执行过程中调用B线程的join方法,A线程会阻塞,等待B线程执行完毕。

      该方法是Thread的实例方法,不会释放对象锁。
      有3种重载的形式: 

        ——join()  :  
          等待被join的线程执行完成

        ——join(long millis)  :  
          等待被join的线程的时间最长为millis毫秒,若在millis毫秒内,被join的线程还未执行结束,则不等待。

        ——join(long millis , int nanos)  :  
          等待被join的线程的时间最长为millis毫秒加nanos微秒,若在此时间内,被join的线程还未执行结束,则不等待。

    实例:

    import lombok.SneakyThrows;
    
    public class CycleWait {
        static String value;
    
        @SneakyThrows
        public static void main(String[] args){
            Thread thread = new Thread(){
                @SneakyThrows
                @Override
                public void run() {
                    System.out.println("run start!");
                    Thread.currentThread().sleep(3000);
                    value = "success!";
    
                    System.out.println("run done!!");
    
                }
            };
            thread.start();
    
            thread.join();
            System.out.println(value);
        }
    }

    结果:

    run start!
    run done!!
    success!

    主线程等待子线程执行完毕。

    两个已经淘汰的方法

    a.shop(),现使用interrupt()方法

      该方法用于终止线程,被调用之后会立即终止该线程。并同时释放对象锁。

      弊端:

      1.当shop()不是本线程调用时,其他线程调用该方法,可能导致原有代码未得到执行,从而导致会业务逻辑不完整。

      2.破坏原子逻辑,既破坏了加锁的初衷

      该方法是Thread的实例方法,会释放对象锁。

    b.suspend(),resume(),先使用wait(),notify(),notityAll()方法

      该方法用于挂起,恢复线程。

      弊端:由于该方法不会释放对象锁,所以很容易造成死锁

      该方法是Thread的实例方法,不会释放对象锁。

    七、关于线程状态的补充

    这里要补充的一点是,调用wait方法后线程进入等待池,调用notify之后进入锁池,拿到对象锁之后并不是直接进入运行状态,而是进入ready状态,等待cpu调度。

    就算这个世道烂成一堆粪坑,那也不是你吃屎的理由
  • 相关阅读:
    Java知识点:javac命令
    Java知识点:内部类
    证明:寝室分配问题是NPC问题
    Java知识点:Object类
    Java 知识点:序列化
    Python3玩转儿 机器学习(2)
    Python3玩转儿 机器学习(1)
    python re模块findall使用
    百度URL 部分参数
    python datetime模块
  • 原文地址:https://www.cnblogs.com/whalesea/p/12945564.html
Copyright © 2020-2023  润新知