多线程基础

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调度。

就算这个世道烂成一堆粪坑,那也不是你吃屎的理由
原文地址:https://www.cnblogs.com/whalesea/p/12945564.html