多线程与并发

319 阅读7分钟

一、线程和进程的区别

(进程是资源分配的最小单位,线程是CPU调度的最小单位)
在Java程序中,Java会对操作系统提供的功能进行封装,其中就有线程和进程,当我们运行一个程序的时候,就会产生一个进程,而一个进程至少包含一个线程。并且每个进程对应一个JVM实例,多个线程共享JVM的堆。Java中采用单线程编程模型,程序会自动创建主线程,并且主线程可以创建子线程,原则上要后于子线程完成执行。



二、Thread中Start()和Run()方法的区别

  • Start()方法会调用JVM的StartThread方法创建一个新的子线程,并通过JVM的Thread_run来调用run方法
  • run()方法只是Thread的一个普通方法,直接可以调用。



三、Thread和Runnable是什么关系

  • Thread只是一个类,并且实现了Runnable的接口



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

1、主线程等待法

public class CycleWait implements Runnable{
    private String value;
    public void run(){
        try{
            Thread.currentThread().sleep(5000);
        }catch(Exception e){
            e.printStackTrace();
        }
        value="we have data now";
    }
    
    public static void main(String[] args){
        CycleWait  c= new CycleWait();
        Thread t1=new Thread(c);
        t1.start();
        while(c.value=null){//此处使用while循环来等待线程的结束
           Thread.sleep(100);
        }
        System.out.println("value : "+value);
    }
}

优点:简单

缺点:需要自己书写等待循环的逻辑,并且循环多久也不确定


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

public class CycleWait implements Runnable{
    private String value;
    public void run(){
        try{
            Thread.currentThread().sleep(5000);
        }catch(Exception e){
            e.printStackTrace();
        }
        value="we have data now";
    }
    
    public static void main(String[] args){
        CycleWait  c= new CycleWait();
        Thread t1=new Thread(c);
        t1.start();
        t1.join();//使用join阻塞,来等待线程处理完毕
        System.out.println("value : "+value);
    }
}

缺点:粒度不够细。如果join()之前有多个线程start(),则会统统阻塞。

补充:join的源码

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {//如果join()没有传值
        while (isAlive()) {//isAlive()判断当前线程是否存活,如果存活就继续阻塞
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

join具体实现就是通过wait()来完成的。


3、通过Callable接口:通过FutureTask Or 线程池获取

public class MyCallable implements Callable<String> {//实现Callable接口
    @Override
    public String call() throws Exception {//该线程需要完成的任务
        String value = "test";
        System.out.println("Ready to work");
        Thread.currentThread().sleep(5000);
        System.out.println("Task done");
        return value;
    }
}

public class FutureTaskDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task=new FutureTask<String>(new MyCallable());
        Thread thread = new Thread(task);
        Thread.sleep(1000);
        thread.start();
        if(!task.isDone()){//判断任务是否完成
            System.out.println("task has not finish, please wait!");
        }
        System.out.println("task reutrn : "+ task.get());//get会等待Callable有返回值之后才会返回
    }
}

五、线程的状态

1、新建(NEW),线程创建后尚未启动的状态,即还未调用start()方法

2、运行(Runnable):包括了Running和Ready。此状态的线程有可能正在执行,也又可能正在等待CPU为它分配执行时间。

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

未被设置Timeout参数的Object.wait()和Thread.join方法

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

设置Timeout参数的Object.wait()和Thread.join方法

5、阻塞(Blocked):等待获取排它锁,例如:synchronized,当一方获取synchronized时,其他对象都要等待这个锁释放,等待的时候会进入阻塞状态。

6、结束:已终止线程的状态,线程已经结束执行


六、sleep和wait的区别

1、基本差别

  • sleep()是Thread类的方法,wait()是Object类的方法
  • sleep()方法可以在任何地方使用
  • wait()方法只能在synchronized方法或synchronized块中使用

2、最主要的本质区别

  • Thread.sleep()会让出CPU,不会导致锁行为的改变
  • Object.wait()不会让出CPU,还会释放已经占有的同步资源锁

实例一:

public class WaitSleepDemo {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread A is waiting to get lock.");
                synchronized (lock) {
                    try {
                        System.out.println("Thread A get lock");
                        Thread.sleep(20);
                        System.out.println("Thread A do wait method");
                        lock.wait(1000);//让出锁10秒,这10秒中B线程获得锁来执行其代码逻辑
                        System.out.println("Thread A is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread B is waiting to get lock.");
                synchronized (lock) {
                    try {
                        System.out.println("Thread B get lock");
                        System.out.println("Thread B is sleeping 10 ms");
                        Thread.sleep(20);
                        System.out.println("Thread B is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

实例二:

public class WaitSleepDemo {
    public static void main(String[] args) {
        final Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread A is waiting to get lock.");
                synchronized (lock) {
                    try {
                        System.out.println("Thread A get lock");
                        Thread.sleep(20);
                        System.out.println("Thread A do wait method");
                        Thread.sleep(1000);
                        System.out.println("Thread A is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread B is waiting to get lock.");
                synchronized (lock) {
                    try {
                        System.out.println("Thread B get lock");
                        System.out.println("Thread B is sleeping 10 ms");
                        lock.wait(10);
                        System.out.println("Thread B is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

此时A线程没有让出锁,则必须等待A执行完毕释放锁资源,B才会执行


七、notify和notifyall的区别

1、锁池EntryList

假如线程A已经拥有了某个对象的锁,线程B、C想要调用这个对象的synchronized方法,但是B、C线程在进入该方法之前,必须获得该对象的锁拥有权,而A又没有释放锁,此时B、C会进入阻塞状态,进入锁池来等待锁的释放。

2、等待池WaitSet

执行wait方法之后,该线程会进入等待池


notifyAll()会让等待池里所有线程进入锁池,来争夺锁资源

notify()则会随机选取一个出与等待池的线程进入锁池竞争获取锁的机会


八、yield

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


九、interrup函数

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

1、如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个interrupException异常。

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

使用方法:

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

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


十、FutureTask

1、FutureTask有两个构造器,一个接收Runnable,一个接收Callable

接收Runnable的构造器的目的只有一个,就是把入参都转化成Callable。因为Callable的功能比Runnable丰富。Callable的call方法有返回值,而Runnable的run方法没有。

public FutureTask(Runnable runnable, V result) {
    // Executors.callable 方法把 runnable 适配成 RunnableAdapter,RunnableAdapter 实现了 callable,所以也就是把 runnable 直接适配成了 callable。
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

Executor.callable方法来把Runnable转化成Callable。

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

1、首先RunnableAdapter实现了Callable,所以RunnableAdapter就是Callable,,然后就会通过这种适配模式,new RunnableAdapter(Runnable r,T)返回RunnableAdapter对象,进而也就是Runnable变成Callable

2、get方法

无参get会一直等到任务执行完毕才返回。

有参get可以设定固定的时间,如果任务还没有执行成功,直接返回异常。

3、cancel方法主要是用来取消任务,如果任务还没有执行,是可以取消的,如果任务已经在执行过程中了,你可以选择不取消,或者直接打断执行中的任务。