进程是程序的运行实例。
进程是程序向操作系统申请资源的基本单位,线程是进程中可独立执行的最小单位。
一个进程至少包含一个线程,可以包含多个线程。
线程是进程的一条执行路径。
Linux中的JVM是基于pthread实现的,现在的Java中线程的本质,其实就是封装的操作系统中的线程。通过jstack也可以看到,每个java线程对应了一个操作系统线程。
方法一:定义Thread子类,重写run方法。调用start方法启动线程。
方法二:定义Runnable实例类,实现run方法,将Runnable实例传入Thread构造器创建线程。调用start方法启动线程。
线程属于一次性用品,start方法只能调用一次,线程运行结束后所占资源会被JVM回收。
如果在当前线程直接调用线程实例的run方法,run方法里的代码仍然是在当前线程运行,只有执行start才会创建新线程运行run方法里的代码。
static Thread currentThread();
返回当前代码的执行线程对象
void start();
启动相应线程
void join();
A线程中调用线程B的join方法,A线程会等B线程执行完然后再继续执MultiThread3类
static void sleep(long millis)
当前线程暂停指定时间
static void yield()
当前线程主动放弃对处理器的占用,但是此方法可能不生效,不可靠
什么是竞态?
多线程之间线程交错,导致结果有时正确有时错误的现象叫做竞态。
竞态产生的模式
(1) 读改写;
(2) 检查而后行动
保障原子性、可见性、有序性
同步机制包括锁(内部锁、显式锁)、volatile等
锁可以理解为共享数据的许可证,一个线程在获取了锁后才能对共享变量进行访问操作,一般的锁一次只能被一个线程持有,这样将多个线程对共享变量的并发访问变成了串行访问,避免了竞态的产生。
锁可以保证指定代码集合执行的原子性、可见性、有序性。即同步在同一个锁上的不同代码集合不会并行执行,进入同步代码之前的代码的结果对同步代码可见,同步代码的结果对后续代码可见,同步代码内部的代码不会被重排序到同步代码外部。
锁分为内部锁和外部锁:
volatile的作用:保证可见性、有序性、long/double变量的读写原子性
有序性:
禁止Volatile写操作与该操作之前的任何读写操作进行重排序,保证Volatile写操作之前的任何读写操作都会先于volatile写操作被提交,即其他线程看到写线程对volatile变量的更新时,写线程在更新volatile变量之前所执行的内存操作结果对于读线程均是可见的。保障了读线程对写线程在更新volatile变量前对共享变量所执行的更新操作的感知顺序与相应的源代码一致,即保障了有序性。
禁止volatile读操作之后的任何读写操作和volatile读操作进行重排序,保障了volatile读操作之后的任何操作开始执行之前,写操作对相关共享变量的更新已经对当前线程可见。
举例:
以下操作中,操作1和操作2不能被重排序到Volatile写操作之后,操作5和操作6不能被重排序到Volatile读操作之前,由此保证了有序性。
操作1 操作2 Volatile写操作 操作3 操作4 Volatile读操作 操作5 操作6
可见性:
当一个线程对volatile变量修改后,另一个线程可以及时看到volatile的修改后的新值;
long/double变量的读写原子性:
JVM不保障long/double变量的写原子性,在多线程环境下可以用volatile关键字保障。
场景:
volatile最常用于保障可见性和修饰状态标志
例如AtomicInteger、AtomicLong等
public static AtomicInteger count = new AtomicInteger(0); count.incrementAndGet();
执行incrementAndGet()方法可以实现线程安全的自增,不用在外部加锁
常用的集合并不是线程安全的,我们可以借助一些特定的集合保证线程安全性。
例如采用ConcurrentHashMap代替HashMap,利用CopyOnWriteArrayList或者Collections.synchronizedList(new ArrayList())代替ArrayList,前者适合读场景,后者适合其他场景。其他线程安全集合可以自行查阅资料。