Java学习之多线程

线程、进程、协程

1、进程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

(举例来说,可以理解是电脑上的一个个程序,例如:浏览器、音乐播放器、QQ。。。)

2、线程

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

(可以理解为程序里的功能,例如音乐播放器里的播放音乐)

3、协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

(个人理解是程序中的某部分代码,某些功能之间的协调,例如点击音乐播放的时候,程序会更换背景图片。)

对比:

进程与线程:

1) 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间;
2) 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源;
3) 线程是处理器调度的基本单位,但进程不是;
4) 二者均可并发执行;
5) 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

协程与线程

1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程;
2) 线程进程都是同步机制,而协程则是异步;
3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态.

串行、并行、并发

1、串行

某一段时间内事情只能像排队一样一个一个发生。例如:先吃饭,再洗衣服。

2、并行

某个时间点多个事情可以同时发生。例如:做饭的时候,电饭锅和烧水壶可以同时工作。

3、并发

某一段时间内事情可以同时发生。例如;一边吃饭一边打电话。(实际上嘴要么在嚼东西要么在说话,只是切换的比较快,所以看起来像是同时发生。)

注意:并发的看起来是同是发生,其实是一种假象,只不过是通过CPU高速的切换来实现的。

多线程的生命周期

线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。

新建:就是刚创建,初始化好时的状态;

就绪:就是调用的线程的start()方法后,但是还没开始运行,在等待分配CPU资源时的状态;

运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;

阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态(即只是进入了就绪状态);

销毁(死亡):如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;

多线程的实现方式

1、继承Thread类

自定义类:

public class MyThread extends Thread  {
    @Override
    public void run() {
        for (int i = 0; i < 100000; i++) {
            System.out.println(i+" "+Thread.currentThread().getName());
        }
    }
}

执行:

public class main {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("线程1");
        MyThread myThread2 = new MyThread();
        myThread2.setName("线程2");
        myThread1.start();
        myThread2.start();
    }
}

结果:

 通过输出可以看出并不是先执行完“线程1”再执行“线程2",而是通过CPU的切换能使两个线程同时运行。(因电脑性能不同,有些电脑可能要多试几次才会出现这种结果)

2、实现Runnable接口

 自定义类:

public class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(i+" "+Thread.currentThread().getName());
        }
    }
}

执行:

public class mian {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable());
        thread1.setName("线程1");
        Thread thread2 = new Thread(new MyRunnable());
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}

结果:

自定义类实现Runnable接口,通过重写run()来写业务逻辑,但是需要借助Thread类来实现线程的流程。

3、实现Callable接口

 自定义类:

public class MyCallable implements Callable {
    public Object call() throws Exception {
        for (int i = 0; i < 10000; i++) {
            System.out.println(i+" "+Thread.currentThread().getName());
        }
        return null;
    }
}

执行:

public class main {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask futureTask1 = new FutureTask<Object>(myCallable);
        FutureTask futureTask2 = new FutureTask<Object>(myCallable);
        Thread thread1 = new Thread(futureTask1);
        thread1.setName("线程1");
        Thread thread2 = new Thread(futureTask2);
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
}

结果:

注意:

1、Callable接口的实现类通过call()方法来写业务逻辑;

2、Callable的实现类需要借助FutureTask类先包装一下,然后创建的FutureTask对象再借助Thread类,执行线程流程。

4、线程池

 实现Runnable接口的自定义类:

public class MyExecutor1 implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(i+" "+Thread.currentThread().getName());
        }
    }
}

执行:

public class mian {
    public static void main(String[] args) {
        //通过Executors工具类创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        //实例化实现Rannable接口的实现类
        MyExecutor1 myExecutor1 = new MyExecutor1();
        for (int i = 0; i < 6; i++) {
            //给实现类分配资源
            executorService.submit(myExecutor1);
        }
        //关闭线程池
        executorService.shutdown();
    }
}

结果:

 


实现Callable接口的自定义类:

public class MyExecutor2 implements Callable {
    public Object call() throws Exception {
        for (int i = 0; i < 1000; i++) {
            System.out.println(i+" "+Thread.currentThread().getName());
        }
        return "这里是返回结果";
    }
}

不获取返回对象时执行:

public class mian {
    public static void main(String[] args)  {

        //通过Executors工具类创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        //实例化实现Callable接口的实现类
        MyExecutor2 myExecutor2 = new MyExecutor2();

        for (int i = 0; i < 3; i++) {
            //给实现类分配资源
            executorService.submit(myExecutor2);
        }
        //关闭线程池
        executorService.shutdown();
    }
}

结果:

获取返回对象时执行:

public class mian {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //通过Executors工具类创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        //实例化实现Cannable接口的实现类
        MyExecutor2 myExecutor2 = new MyExecutor2();

        for (int i = 0; i < 3; i++) {
            //给实现类分配资源
            Future<?> future = executorService.submit(myExecutor2);
            //future.get(),可能会抛出异常,在这里不try/catch了,直接在方法出throws
            System.out.println(future.get().toString());
        }
        //关闭线程池
        executorService.shutdown();
    }
}

结果:

 补充:

1、ExecutorService、Callable、Future三个接口都是属于Executor框架(Executors 是一个工具类)。

2、可返回值的任务必须实现Callable接口,无返回值的任务必须实现Runnable接口。

3、执行Callable任务后,可以获取一个Future的对象,在该对象上调用get() 就可以获取到Callable任务返回的Object了。(get()是阻塞的,即:get方法会一直等待着一个线程执行完了才会调用。)

posted @ 2020-03-01 22:44  请别耽误我写BUG  阅读(178)  评论(0编辑  收藏  举报