1. 进程与线程
1)进程是系统进行资源分配与调度的基本单位, 线程是程序执行的最小单位。
2)线程是进程中的一部分,一个进程可以包含若干个线程。线程可以拥有自己的数据栈、程序计数器,但不拥有系统资源,它与父进程的其他线程共享该进程拥有的所有系统资源。
3)线程可以理解为轻量级进程,线程切换的开销更小。
4)同一进程的线程之间可以共享内存,进程之间不能共享内存
2. 线程的创建
1)继承Thread类,确定java 不能多继承
2)继承Runnable 接口,
3)继承Callable接口(未继承Runnable,可以声明抛出异常), 然后用继承了Runnable和Future的实现类Futrue封装,可以获取返回值
注:使用2)和3)更加灵活,可以继承其他的类,并且多个线程可以共享同一target 对象(同一份资源)
3. 线程的生命周期
1) 新建: new()
2)就绪: start()
3)运行: run(), 处理器自己调度
4)运行-->阻塞: sleep() jion() 调用阻塞式IO未返回 wait() 等待通知 去获取一个已被占有的锁或者同步监视器
5)运行-->就绪:yield()
6)阻塞-->就绪:sleep()时间到 join()时间结束或者加入的线程运行结束 阻塞式IO返回 notify()/notifyAll() 获取到了锁或者同步监视器
7)运行-->死亡: stop() Error Exception或者正常结束
4. 线程安全:程序的运行的结果与预期的结果一致(当多个线程访问某个类时,这个类总能够表现出正确的行为,那么就称这个类是线程安全的)
例一:HashMap 线程不安全
线程A、B向同一个hashMap中加入主键值相等的k-v对,刚开始map中不存在主键值为key的记录,A,B插入之前都检查 到不存在主键值为key的记录,则可能产生map中存在两条主键值都为key的记录。
例二:两个线程对于同一个变量count进行 count++操作。
造成线程不安全的原因:
1)共享变量count的不可见性:工作内存的最新值不能及时写回主内存,导致同一变量不同线程的中工作内存中不一致。
2)对count操作的无序性:线程之间必须有序的访问共享变量。
解决方法:
1)volatile关键字修饰,解决了可见性问题,当对共享变量更改时能及时通知到其他线程,但加上了volatile关键词,为什么还是无法得到正确的结果?count++是非原子操作,可分为三步
a. temp = count; b. temp = count+1; c. count=temp;
当A、B在同一时刻进行count++操作时,可见性保证第一步获取到的count值一致,但结果不对,必须保证顺序性,使两个线程的count++操作先后进行。
2)对代码块加锁,解决顺序性问题。
总结:要保证线程同步必须保证共享变量的可见性和临界区代码访问的顺序性。
5. volatile 关键字
1)用法: 修饰实例变量
2)作用:保证了共享变量可见性,但不保证操作的原子性。、
3)实现原理:当对volatile 修饰的变量进行写操作时,JVM会向处理器发送一条Lock的前缀指令,Lock指令会做三件事:
a. 禁止指令重排
b. 锁住总线或者缓存来保证执行的原子性。
c. 将修改了的缓存行回写主内存,并保证其他线程中该变量的缓存失效。
6. synchronized关键字:利用java 内置锁(监视器)实现同步
1)可以用来修饰变量、对象、类、 方法。
修饰变量或对象时:synchronized(obj), 获取的是该变量或者对象的锁
修饰普通方法时:获取的是调用该方法的对象(this)的锁
修饰静态方法时:获取的是调用该方法的对象对应得类的锁
2)java中的每个对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁,这是一种互斥锁,同一时刻只有一个线程拥有
3)线程进入同步代码块(方法)之间必须获取内置锁。
4)可重入
5)隐式锁,无需显示得加锁与释放
6)使用时,应尽量将不影响共享状态且执行时间较长得操作从同步代码块中分离出去
7.volatile 与 synchronized 的区别
1) volatile 是一种比 synchronized 关键字更轻量级的同步机制,但volatile 只能用在变量级别,而synchronized 可以用在变量、方法和类的级别。
2)volatile只能保证变量的可见性, 而synchronized可以保证变量的可见性和操作的原子性。
3)volatile 不会造成线程阻塞,而synchronized 可能会造成线程阻塞。
4)volatile
标记的变量不会被编译器优化。synchronized
标记的变量可以被编译器优化。
8. 什么场景下volatile 可以代替 synchronized ?
1)1写多读
2)不与其他变量构成不变性条件(不变性条件:变量之间不是互相独立的,产生约束, 一变多变。要保证状态的 一致性,就必须在原子操作 中保证更新所有的相关变量)
3)变量新值不依赖旧值。
9. RentrantLock
1) .显示加锁与释放,提供比sychronized 更灵活的结构和更丰富的功能
2)RentrantLock 获取锁的三种方式:
a. Lock():获取锁则立即返回,否则将一直等待,线程阻塞。
b.tryLock() : 获取锁则立即返回true,否则返回true;
c.tryLock(long timeout,TimeUnit unit),如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
3) 轮询锁与定时锁
轮询锁与定时锁是由tryLock()方法来实现的,与无条件获取锁相比,它们具有更完善的错误恢复机制。如果不能获取所有的需要的锁,它会释放已经获得的锁,然后重新尝试获取所有锁。
定时锁可以设置获取锁的最大时间,如果不能在该时间内获取锁,则返回失败。
4)中断锁
lockInterruptibly(): 线程获取锁失败在等待的过程中能够保持对中断的响应,抛出中断异常;而lock()在获取锁失败等待的过程中不会对中断进行响应。
5)公平性
公平锁: 线程按它们发出请求的顺序来获取锁。
非公平锁: 当一个线程请求获取一个非公平锁时,若此时该锁空闲,则它会跳过等待队列中的所有线程获取这个锁。
注: 非公平锁性能更好,因为一个线程从挂起状态到开始运行存在着延时,RentrantLcok 默认为非公平。
6)读写锁
ReadWriteLock: 可以多个线程同时读,但只有一个线程同时写。
10. Synchronized 与 RentrantLock比较
1) Synchronized由java 内置锁实现, RentantLock由代码实现。
2)Synchronized 自动加锁与释放, 危险系数更低。
3)RentantLock功能更丰富,包括定时锁、中断锁、公平性、非块结构的加锁等
4)RentantLock锁操作不能与特定的栈帧联系起来,内置锁可以。