1、进程和线程
1.1 进程
进程是操作系统运行程序的基本单位,是一次程序的执行。简单来说一个进程就是一个运行中的程序。
1.2 线程
线程可以认为是在进程中独立运行的子任务。一个进程会有多个线程。
1.3 进程和线程区别
进程和线程最大区别就是,各个进程是独立的,而线程却不一定,同一进程中的线程可能是相互影响的。进程属于操作心痛范围的,同一时间会运行多个程序,每个进程上的又会有多个线程在执行同个或不同的任务。
1.4 多线程
多线程就是多个线程同时运行或交替运行。单核CPU的话是顺序执行,也就是交替运行。多核CPU的话,因为每个CPU有自己的运算器,所以在多个CPU中可以同时运行。
2、使用多线程
在Java的JDK开发包中,实现多线程主要有两种,一种是继承Thread类,另一种是实现Runnable接口。其实还有很多事项多线程方法,例如:使用ExecutorService、Callable、Future实现由返回结果的多线程,这是通过线程池创建的任务,可以参考《一篇搞懂线程池》。现在我们只来具体来讲一下前两种方法的使用。
2.1 继承Thread类
在创建多线程前,我们先来看一下Thread的类结构图:
可以看出Thread实现了Runnable接口,其实Runnable和Thread的区别就在于继承Thread创建多线程不支持多继承,为了多继承完全可以使用Runnable替换,两者创建的线程本质上没有区别。@FunctionalInterface是Java8的函数式接口,方便lambda表达式使用。
我们创建一个Mythread.java继承Thread,重写run方法:
1 | package main.java.com.xiaosen.Mythread; |
运行类的代码如下:
1 | import main.java.com.xiaosen.Mythread.MyThread; |
运行结果如下:
1 | this is MyThread |
这里一低昂要注意myThread调用的是start()方法,如果调用mythread.run()那么就是单纯的方法调用,而不是创建线程去执行。关于lambda表达式只给出使用方法,暂不做介绍。
从运行结果可以看见”Hello World!”的输出在两个线程中间,说明使用多线程和代码的顺序无关。CPU会以随机的时间来调用线程中的run方法。
2.3 实现Runnable接口
创建MyRunnable实现Runnable接口:
1 | package main.java.com.xiaosen.myrunnable; |
main方法使用:
1 | // 实现Runnable |
输出内容:
1 | Hello World! |
使用Runnable方法需要用到Thread的构造方法。
3、线程安全
自定义的实例变量有共享和不共享之分,在多线程交互的时候很重要。
3.1 不共享数据
创建一个类继承Thread:
1 | public class NotShareThread extends Thread{ |
执行结果:
1 | 线程A:count=4 |
从输出结果可以看到每一个线程都有各自的count变量,彼此变量不共享。
3.2 共享数据
共享数据就是多个线程同时访问同一个变量,比如买火车票,多个线程同时操作剩余票数。
3.2.1 线程不安全
1 | public class ShareThread extends Thread { |
输出:
1 | 线程B:count=2 |
可以发现A,B,C三个线程的值都是2,产生非线程安全情况,我们应该避免这种情况发生。这是因为在执行i–操作分以下几步:
- 获取原有i值;
- 计算i-1;
- 堆i赋值。
在这三步中,如果有多线程同时访问,就会出现线程不安全情况。
3.2.2 线程安全
我们来看看线程安全的代码:
1 |
|
我们加上synchronized代码块加锁,那么只有拿到所锁的线程才可以执行代码块的内容,没拿到则不断尝试获取锁,知道拿到为止。
代码Github
欢迎关注公众号: