面试复习————JAVA多线程与并发

进程和线程的区别

进程和线程的由来

面试复习————JAVA多线程与并发_第1张图片

进程是资源分配的最小单位,线程是CPU调度的最小单位

1.所有与进程相关的资源,都被记录在PCB中(Process Control Block 进程管理块)

2.进程是抢占处理机的调度单位,线程属于某个进程,共享其资源

3.线程只由相关堆栈寄存器,程序计数器和TCB组成

面试复习————JAVA多线程与并发_第2张图片

面试复习————JAVA多线程与并发_第3张图片

总结

1.线程不能看做独立应用,而进程可以看做独立应用

2.进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径

3.线程没有独立的地址空间,多进程程序比多线程程序健壮

4.进程的切换比线程的切换开销大

 

Java进程和线程的关系

1.java对操作系统提供的功能进行封装,包括进程和线程

2.运行一个程序会产生一个进程,进程包含至少一个线程

3.每个进程对应一个JVM实例,多个线程共享JVM里的堆

4.java采用单线程编程模型,程序会自动创建主线程

5.主线程可以创建子线程,原则上要后于子线程完成执行

 

 

Thread的start和run方法的区别

/*
举例start和run的区别
* */
public class ThreadTest {

    private static void attack(){
        System.out.println("Fight");
        System.out.println("Current Thread is : " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                attack();
            }
        };

        System.out.println("Current Main Thread is " + Thread.currentThread().getName());
        thread.run();  //输出  Current Thread is : main
        //thread.start();  //Current Thread is : Thread-0
    }
}

调用run的时候,会沿用主线程来执行方法。调用start则会使用非main线程来执行attack方法。

在线查看jdk1.8源码

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/3560e0ebe876

Thread属于java.lang包里 

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/3560e0ebe876/src/share/native/java/lang

查看start源码之后,发现调用的是 

private native void start0();  
 通过查看Thread.c源码,发现start0 调用的是 
 {"start0",           "()V",        (void *)&JVM_StartThread},

然后再去查看jvm.cpp源码

http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/3ece33697a35/src/share/vm/prims/jvm.cpp

在方法中

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))

发现  语句会创建一个新的线程

native_thread = new JavaThread(&thread_entry, sz);

查看   thread_entry方法 中发现 会call虚拟机 并且传入执行方法的名字

最终会创建一个线程 然后去执行run方法里面的内容

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);
}

JAVA线程创建调用的关系图

面试复习————JAVA多线程与并发_第4张图片

总结

1.调用start()方法会创建一个新的子线程并且启动

2.run()方法只是Thread的一个普通方法的调用,还是在主线程中执行

 

 

Thread和Runnable是什么关系

Thread是一个类,Runnable是一个接口

查看Thread源码 发现

class Thread implements Runnable

并且 Runnable 接口中只有一行 抽象方法 

public abstract void run();

使用Thread 实现多线程

public class MyThread extends Thread {
    private String name;
    public MyThread(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for(int i = 0;i < 5; i ++){
            System.out.println("Thread start : " + this.name + " i = " + i);
        }
    }
}

public class ThreadDemo {

    public static void main(String[] args) {
        MyThread thread = new MyThread("Thread1");
        thread.start();
    }
}

结果
Thread start : Thread1 i = 0
Thread start : Thread1 i = 1
Thread start : Thread1 i = 2
Thread start : Thread1 i = 3
Thread start : Thread1 i = 4

使用Runnable实现多线程

public class MyRunnable implements Runnable {
    private String name;
    public MyRunnable(String name){
        this.name = name;
    }
    @Override
    public void run() {
        for(int i = 0;i < 5; i ++){
            System.out.println("Thread start : " + this.name + " i = " + i);
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable("Runnable1");
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

结果
Thread start : Runnable1 i = 0
Thread start : Runnable1 i = 1
Thread start : Runnable1 i = 2
Thread start : Runnable1 i = 3
Thread start : Runnable1 i = 4

总结

1.Thread是实现Runnable接口,使得run支持多线程

2.因为单一继承原则,推荐多使用Runnable接口

 

 

如何给run()方法传参

实现的方式有主要三种

1.构造函数传参

2.成员变量传参

3.回调函数传参

 

 

如何实现处理线程的返回值

1.主线程等待法 

优点:实现简单。缺点:需要自己实现循环等待的逻辑,当需要等待的变量变多,整个代码块很显得很臃肿,并且不能做到精准控制

2.使用Thread类的join()阻塞当前线程以等待子线程处理完毕

优点:比主线程等待法更精准的控制,并且实现更简单。缺点:力度不够细

3.通过Callable接口实现:通过FutureTask获取 或者 线程池获取

使用线程池可以提交多个Callable方法的类,让线程池并发的处理结果,这样可以对Callable的执行方式做统一的管理

 

主线程等待法  使用Thread类的join()阻塞当前线程以等待子线程处理完毕代码实现

public class CycleWait implements Runnable {
    private String value;
    @Override
    public void run() {

        try {
            Thread.currentThread().sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        value = "我是value,我现在有数据了";
    }

    public static void main(String[] args) {
       CycleWait cycleWait = new CycleWait();
       Thread thread = new Thread(cycleWait);
       thread.start();
        //实现处理线程的返回值 方法一  主线程等待法
       while (cycleWait.value == null){  //核心代码 循环等待
           try {
               Thread.currentThread().sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

        //实现处理线程的返回值 方法二 使用Thread类的join()阻塞当前线程以等待子线程处理完毕
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(cycleWait.value);
    }
}

通过Callable接口实现:通过FutureTask获取 或者 线程池获取 代码实现

public class MyCallable implements Callable {
    @Override
    public String call() throws Exception {
        String value = "test";
        System.out.println("开始工作");
        Thread.currentThread().sleep(5000);
        System.out.println("任务完成");
        return value;
    }
}

//方式1 通过FutureTask获取

public class FutureTasDemo {
    public static void main(String[] args) {
        FutureTask task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        if(!task.isDone()){
            System.out.println("任务还没做完,再等待");
        }
        try {
            System.out.println("返回值是:" + task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


//方式2 通过线程池获取

public class ThreadPoolDemo {

    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        Future future = newCachedThreadPool.submit(new MyCallable());
        if(!future.isDone()){
            System.out.println("还没做完,再等待");
        }

        try {
            System.out.println("返回值是" + future.get());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            newCachedThreadPool.shutdown();
        }
    }
}

 

 

线程的状态

public static enum ThreadState
extends Enum

A thread state. A thread can be in one of the following states:

NEW
A thread that has not yet started is in this state.

RUNNABLE
A thread executing in the Java virtual machine is in this state.

BLOCKED
A thread that is blocked waiting for a monitor lock is in this state.

WAITING
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.

TIMED WAITING
A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.

TERMINATED
A thread that has exited is in this state.

A thread can be in only one state at a given point in time. These states are virtual machine states which do not reflect any operating system thread states

公共静态枚举线程状态

扩展枚举

线程状态。线程可以处于以下状态之一:

新建

尚未启动的线程处于此状态。

可运行的

Java虚拟机中执行的线程处于此状态。

阻塞

等待监视器锁定被阻止的线程处于此状态。

等待

无限期等待另一线程执行特定操作的线程处于此状态。

定时等待

在指定等待时间内等待另一线程执行操作的线程处于此状态。

结束

已退出的线程处于此状态。

一个线程只能在给定的时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态

六个状态

1.新建(NEW):创建后尚未启动的线程状态

2.运行(Runnable):包含Running和Ready

3.无限期等待(Waiting):不会被分配CPU执行时间,需要显示被唤醒

以下方法都会使线程进入Waiting状态

没有设置Timeout参数的Object.wait()方法

没有设置Timeout参数的Thread.join()方法

LockSupport.part()方法

4.限期等待(Timed Waiting):在一定时间后会由系统自动唤醒

以下方法会使线程进入Time Waiting状态

Thread.sleep()方法

设置了Timeout参数的Object.wait()方法

设置了Timeout参数的Thread.join()方法

LockSupport.parkNanos()方法

LockSupport.parkUntill()方法

5.阻塞(Blocked):等待获取排它锁

6.结束(Terminated):已终止线程的状态,线程已经结束执行

thread.join();  thread.start(); 调用join方法之后再调用start方法会抛出 IllegalThreadStateException 异常。已经结束的线程不能在复活了

 

 

线程状态以及状态之间的转换

通过实现Runable接口或者继承 Thread类 可以创建一个线程

新建——>可运行

调用线程的start方法,线程就会进入到 可运行(runable) 状态

可运行——>运行

可运行状态的线程 被OS选中 并且获得时间片之后 就会进入到运行(running)状态

运行——>可运行

如果运行(running)状态 调用yield 方法 会让出CPU 回到 可运行(runnable)状态 (取决于操作系统的调度 yield只是起到建议的作用)

如果时间片已经用完 运行状态 也会进入到 可运行状态

运行——>阻塞

如果处于运行状态的线程,如果要等待用户的输入I/O输入,或者调用sleep()或者join()方法 会进入到阻塞的状态。此时会让出CPU。如果当前线程获得锁,上述情况并不会对锁的占用会有任何影响。即不会释放已经获得的锁。

运行,可运行——>阻塞

处于 运行(running)状态 或者 可运行(runnable) 状态的线程,执行 synchronized 方法 或者 synchronized 方法块的时候。发现并未获取到相应的锁,也会进入到阻塞状态。同时会被放入对象的锁池当中。

运行——>等待队列

如果处于运行(running)状态的线程,调用wait方法。就会进入限期或者非限期等待状态。同时还会被放入锁对象的等待队列当中。

等待队列——>锁池

如果处于等待队列中的线程,wait时间到或者被其他线程调用notify 或者 notifyAll 唤醒的话,就会被放入锁池当中。

锁池——>可运行

处于锁池当中的线程获得了锁,就会转为可运行(runnable)当中。

可运行——>运行

等待OS选中后,就会回到运行(runable)状态。

运行——>死亡

最后处于运行(running状态)线程,等待方法执行完毕或者异常退出之后。线程就会结束,进入死亡状态。

 

 

sleep和wait的区别

基本差别

1.sleep是Thread类的方法,wait是Object类中定义的方法

2.sleep()方法可以在任何地方使用

3.wait()方法只能在synchronized方法或者synchronized块使用

最主要本质的区别

1.Thread.sleep只会让出CPU,不会导致锁行为的改变

2.Object.wait不仅让出CPU,还会释放已经占有的同步锁资源

 

用代码来证明本质区别

public class WaitSleepDemo {
/*    lock.wait(1000);  获取同步锁只会 会wait 1s 在这段时间
* 第二个线程(sleep)开始执行
* 如果线程1没有释放lock 线程2就会被进入阻塞状态 不能执行线程2 synchronized 里面的逻辑
* 如果wait可以释放锁 那么 线程2 synchronized 将会被执行
* 如果将sleep放在线程1 而 wait放在线程2  那么线程2 synchronized 的代码不会被执行*/
    public static void main(String[] args) {
        final Object lock = new Object();
        //wait逻辑
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程 A 正在等待得到lock");
                synchronized (lock){
                    try {
                        System.out.println("线程 A 得到lock");
                        Thread.sleep(20);
                        System.out.println("线程 A 等待方法");
                        lock.wait(1000); //如果不传入时间 线程就会进入Waiting(无限期等待)状态
                        System.out.println("线程 A 已经执行完成");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }

                }
            }
        }).start();


        //使wait方法先执行
        try{
            Thread.sleep(10);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        //sleep逻辑
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程 B 正在等待得到lock");
                synchronized (lock){
                    try {
                        System.out.println("线程 B 得到lock");
                        System.out.println("线程 B 休眠 10 ms");
                        Thread.sleep(10);
                        System.out.println("线程 B 已经执行完成");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }

                }
            }
        }).start();
    }
}

结果
线程 A 正在等待得到lock
线程 A 得到lock
线程 B 正在等待得到lock
线程 A 等待方法
线程 B 得到lock
线程 B 休眠 10 ms
线程 B 已经执行完成
线程 A 已经执行完成

 

 

notify和notifyAll的区别

当线程进入到 无限等待状态(Waiting) 时 可以使用notyfy和notyfyAll 唤醒线程

了解两个概念

锁池EntryList

 

假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池

等待池WaitSet

假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。

总结

notifyAll会让所有处于等待池的线程全部j进入锁池去竞争获取锁的机会

没有获取到锁,或者已经呆在锁池中的只能等待其他机会去获取锁,而不能主动回到等待池当中

notify只会随机选取一个处于等待池中的线程进入锁池去竞争取锁的机会

 

 

yield函数

JDK介绍yield 

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

向调度程序提示当前线程愿意放弃当前对处理器的使用。调度程序可以忽略此提示。

概念

当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。

yield并不会使线程让出锁

public class YieldDemo {

    public static void main(String[] args) {
        Runnable yieldTask = new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i <= 10; i ++){
                    System.out.println(Thread.currentThread().getName() + i);
                    if(i == 5){
                        Thread.yield();
                    }
                }
            }
        };
        Thread threadA = new Thread(yieldTask,"A");
        Thread threadB = new Thread(yieldTask,"B");
        threadA.start();
        threadB.start();
    }
}

 

 

如何中断线程

已经被抛弃的方法

通过调用stop()方法停止线程

这个方法太过于暴力,并且不安全。比如说线程A调用线程B的stop方法,去停止线程B。调用这个方法的时候,线程A并不知道线程B执行的具体情况,这种突然的停止,会导致线程B的清理工作无法完成。还有一种情况,执行stop方法之后线程B,会马上释放锁,会有可能引发数据不同步的问题。

通过调用suspend()和resume()方法

 

目前使用的方法

调用interrupt(),通知线程应该中断了

1.如果线程处于被阻塞状态,那么线程将立即退出阻塞状态(sleep,wait,join),并抛出y一个InterruptedException异常

2.如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志饿线程将继续正常运行,不受影响。

 

需要被调用的线程配合中断

1.在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程

2.如果线程处于正常活动的状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响

 

 

下面代码展示使用interrupt()停止线程

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable interruptTask = new Runnable() {
            @Override
            public void run() {
                int i = 0;
                try{
                    //在正常运行任务时,经常检查本线程的中断标志,如果被设置了中断标志j就自行停止线程
                    while (!Thread.currentThread().isInterrupted()){
                        Thread.sleep(100);//休眠一会
                        i ++;
                        System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + " ) loop " + i);
                    }
                }catch (InterruptedException e){
                   //在调用阻塞方法时正确处理 InterruptedException 异常。(例如catch 异常后就结束线程)
                    System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + " )  catch InterruptedException" );
                }
            }
        };

        Thread thread1 = new Thread(interruptTask,"A");
        System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 已经被创建");

        thread1.start();
        System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 已经开始运行");

        //主线程休眠 然后主线程给thread1 发"中断指令"
        Thread.sleep(300);
        thread1.interrupt();
        System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 发出中断指令");

        //主线程休眠 然后查看thread1状态
        Thread.sleep(300);
        System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 已经被中断");
    }
}

结果
A (NEW ) 已经被创建
A (RUNNABLE ) 已经开始运行
A (RUNNABLE ) loop 1
A (RUNNABLE ) loop 2
A (TIMED_WAITING ) 发出中断指令
A (RUNNABLE )  catch InterruptedException
A (TERMINATED ) 已经被中断

 

你可能感兴趣的:(JAVA)