什么是线程安全?
线程安全就是,当多个线程共享一个变量时,某个线程对该变量做修改的时候受到其他线程干扰从而导致线程安全问题。比如
i = i + 1;
System.out.println(i);
假如i是个全局变量,当其他线程也对i进行操作的时候,就会导致i的值出现和预期不符的结果。
如何解决线程安全
使用内置锁(synchronized )或者外置锁(Lock)
synchronized :能够保证线程的原子性,是可重入锁,也是互斥锁
可重入锁:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。
synchronized 可以对方法和代码块进行上锁。
synchronized 修饰非静态方法,等于对当前调用方法的对象上锁。
public class SaleTicketThread implements Runnable{
private int ticket = 100;
public SaleTicketThread() {
}
public void run() {
while (ticket > 0) {
sale();
}
}
private synchronized void sale() {
if (ticket > 0) {//这里判断是由于多线程情况下会出现多个线程同时执行到sale()的地方,当前执行完毕释放锁之后,其他线程进入方法会继续卖票,导致出现复数。
System.out.println("窗口" + Thread.currentThread().getName() + "卖出第" + (100 - ticket + 1) + "张票" );
ticket --;
}
}
}
synchronized 修饰代码块这么写,和上面的sale方法等价
private void sale() {
synchronized (this) {// this也可以用其他实例变量代替。比如定义一个Object对象。
if (ticket > 0) {
System.out.println("窗口" + Thread.currentThread().getName() + "卖出第" + (100 - ticket + 1) + "张票" );
ticket --;
}
}
sychronized修饰静态方法就等于用当前类的字节码文件上锁,如下2块代码是等价的。
private synchronized static void sale() {
xxxx;
}
和
private static void sale() {
synchronized(SaleTicketThread.class) { //SaleTicketThread是当前类。
xxxx;
}
}
死锁的问题
死锁发生的原因是因为2个线程互相等待对方释放锁。多半都是由于在锁里面嵌套锁。所以要避免在锁里面嵌套锁。
死锁的例子:
ThreadLocal
ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal底层是个Map集合。
ThreadLocal会发生内存泄漏的问题。
Java内存模型(JMM)
JMM决定一个线程对共享变量写的时候对其他线程是否可见。
抽象概念
1. 主内存:共享变量
2. 本地内存:共享变量的副本,当前线程独有的。
注意:内存结构指的是JVM中的内存结构 堆 栈 方法区等。 内存模型是JMM。不是用一个概念。
多线程的三大特性
Volatile
- 只能保证可见性,但是不能保证线程安全。因为它没办法保证原子性。
它保证了当一个线程修改了共享变量之后,能够将共享变量的值及时刷新到主内存中。但是它无法保证线程安全,因为它不能解决原子性的问题。
项目中,只要是全局共享的变量,推荐全部都加上vlol atile关键字。 - 禁止指令重排优化
是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理。 - volatile的读性能和普通变量几乎相同,但是写稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
Synchronized和Volatile区别
volatile只能保证可见性,不能保证原子性。可以禁止重排序。
synchronized,既可以保证原子性还能保证可见性。且它会使线程阻塞。
、
wait 和 notify
都是object对象的方法,wait用于休眠当前持有锁的线程,notify用于唤醒持有对象锁的线程。
生产者和消费者
生产者负责生产数据,消费者负责消费数据,当生产到一定程序停止生产,当消费到一定程度停止消费。依靠wait 和 notify方法实现,此处省略代码。
Lock
synchronized 和 Lock的区别
- synchronized是重量锁,也是内置锁,基于java对象的moniter实现,它是自动释放的。
- Lock需要手动释放锁,是基于jdk的api实现的,使用方法更加多样更加灵活。
- Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回
Condition用法
Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能。