html5商城网站模板,网站开发 职位,建设银行天津分行网站,做网站的开题报告怎么写一、线程安全
1.1 线程安全的概念
线程是随机调度执行的#xff0c;如果多线程环境下的程序运行的结果符合我们预期则说明线程安全#xff0c;反之#xff0c;如果遇到其他结果甚至引起了bug则说明线程不安全
1.2 经典例子与解释
下面举一个经典的线程不安全的例子… 一、线程安全
1.1 线程安全的概念
线程是随机调度执行的如果多线程环境下的程序运行的结果符合我们预期则说明线程安全反之如果遇到其他结果甚至引起了bug则说明线程不安全
1.2 经典例子与解释
下面举一个经典的线程不安全的例子
public class Demo2 {private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int i 0; i 50000; i) {count;}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {count;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count count);}
}上述代码中t1和t2两个线程对count进行累加操作在主线程中启动这两个线程然后通过join等待这两个线程都执行完后打印count预期结果为100000但打印结果如下 上述结果不符合我们的预期这便是产生了线程安全问题
接下来我们通过CPU指令的方式解释上述原因
count这个行代码可以看作3个CPU指令
把内存count总的值读取到CPU寄存器中 load把寄存器中的值1此时任然在寄存器中 add把上述寄存器计算后的值写回到内存count里 save
由于线程随机调度所以两个线程的CPU指令执行顺序也是随机的。
例如下图
画图时间轴。。。。。。。。。。
首先t1线程和t2线程分别将1加载到CPU寄存器中假设此时count的值为1然后在寄存器中将其加1变为2最后t1先将2加载回内存中t2也把2加载回内存中所以两次加1操作只加了一次1
当然上述执行顺序只是无数可能中的一种可能t1的一组指令还没有执行完t2就执行了好几组
下面来总结一下线程不安全的原因
1.3 线程不安全的原因
线程是随机调度抢占式执行的修改共享数据多个线程修改同一个变量多个线程修改共享数据的操作不是原子性count是3个CPU指令但是赋值操作就是原子性的内存可见性问题指令重排序
4和5后面再解释 1.4 解决线程安全问题
根据上述原因下手
原因1无法干预
原因2可以干预但并不是一个普适的做法因为有些代码就是要修改同一个变量
原因3这是一个普适的做法我们可以将一系列非原子的操作打包成一个原子性的操作-加锁
1.4.1什么是锁
锁是在多线程编程中用来控制线程对共享资源访问的一种机制
1.针对锁主要有这两个操作
加锁线程t1加上锁之后t2也尝试使用同一个锁进行加锁就会阻塞等待
问什么叫“t2也尝试使用同一个锁进行加锁”
答你可以理解为我们给t2里的操作加上了一种机制这个机制就是必须加上锁才能进行操作t1拿了一个锁加锁后进行它的操作如果t2也想拿这个锁来加锁就必须等t1操作完成解锁之后再拿这个锁进行加锁进行它的操作在此之前t2要阻塞等待当然如果t2选择拿别的锁进行加锁就不会阻塞等待假设只有t1和t2两个线程 比如A在餐厅里定了一个包间把门上锁之后来用餐这样B来了就不会影响A用餐的过程也就是t2不会对t1修改count的过程进行干扰这样就保证了操作的原子性 解锁t1解锁之后t2才有可能拿到锁因为尝试竞争锁的线程可能不只一个
2. 锁的主要特性互斥一个线程获取到锁之后另一个线程也尝试加这个锁就会阻塞等待这种现象叫锁竞争或锁冲突代码中也可以有多个锁只有多个线程竞争同一个锁才会发生锁竞争竞争不同的锁则不会发生锁竞争 1.5 synchronized关键字
1.5.1 synchronized解读
使用synchronized关键字synchronized关键字解读
synchronized (locker) {count;
}
这是一个Java的关键字不是方法synchronized后面括号里面写的是锁对象锁对象的用途用来区分两个线程是否针对同一个对象加锁如果是就会出现锁竞争/互斥就会引起阻塞等待如果不是就不会出现锁竞争也就不会阻塞等待 synchronized的{ }进入到代码块就是对上述锁对象进行加锁操作当出了代码块就是对锁对象进行解锁
我们可以让t1和t2都使用同一个锁对象locker来对count变量的修改操作进行上锁
private static int count 0;
public static void main(String[] args) throws InterruptedException {Object locker new Object(); //锁对象Thread t1 new Thread(() - {for (int i 0; i 50000; i) {synchronized (locker) {count;}}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {synchronized (locker) {count;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count count);
}
这样虽然两个线程仍然是抢占式执行的但是保证了count;这个操作的原子性结果为count 100000
Java中随便拿一个对象都可以作为加锁的对象 1.5.2 synchronized使用示例
1修饰代码块指定锁哪个对象也就是可以锁任意对象
public class SynchronizedDemo {private Object locker new Object();public void method() {synchronized (locker) {}}
}锁当前对象()里直接写this
public class SynchronizedDemo {public void method() {synchronized (this) {}}
}2修饰普通方法锁的SynchronizedDemo对象谁调用method()方法就锁谁可以有多个
public class SynchronizedDemo {public synchronized void methond() {}
}
3)修饰静态方法锁的SynchronizedDemo类对象一个java进程中一个类只有唯一一个类对象
public class SynchronizedDemo {public synchronized static void method() {}
} 1.5.3 synchronized特性
1互斥
某个线程执行到某个对象的synchronized中时其他线程如果也执行到同一个对象synchronized就会阻塞等待
2可重入
for (int i 0; i 50000; i) {synchronized (locker) {synchronized (locker) {count;}}
}上述线程先对locker进行第一次加锁在第二次加锁的时候locker对象已经被锁住了按照之前的理解尝试针对一个已经被锁的对象加锁时就会阻塞等待这种情况就叫死锁
但synchronized是可重入锁可重入锁的内部包含了线程持有者和计数器
如果某个线程加锁的时候发现这个锁已经被别人占用但是恰好占用的是自己那么仍然可以继续获取到锁并让计数器自增解锁的时候也就是每走出一个代码块计数器就会递减当减到0时才真正释放锁
这种机制就叫可重入锁 1.6 死锁
1.6.1 两个常见的场景
死锁有两个比较典型的场景
场景一不可重入锁引起的死锁
一个线程对一个线程连续加锁两次且这个锁是不可重入锁就会引起死锁
场景二两个线程两把锁 public static void main(String[] args) throws InterruptedException {Object locker1 new Object();Object locker2 new Object();Thread t1 new Thread(() - {synchronized(locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println(t1获取了两把锁);}}});Thread t2 new Thread(() - {synchronized(locker2) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1) {System.out.println(t2获取了两把锁);}}});t1.start();t2.start();System.out.println(死锁ing....);}上述代码中locker1被t1占用locker2被t2占用接下来t1需要locker2t2需要locker1这样就陷入了死锁
运行结果显示程序一直没有结束 1.6.2 如何避免死锁
死锁产生的四个必要条件
1锁具有互斥性
2锁不可抢占一个线程拿到锁之后除非它主动释放锁否则别人抢不走
以上这两点是锁的基本特性无法干预
3请求和保持一个线程拿到一把锁之后不释放这个锁的前提下在尝试获取其他锁嵌套加锁
解决方法就是不要让两个sychronized嵌套式的占用两个不同的锁对象进行加锁
4循环等待多个线程获取多个锁的过程中出现了循环等待A等待BB又等待A
这一点只要我们提前约定好获取锁的顺序即使出现了嵌套也不会引起死锁如下述代码t1和t2线程都先获取locker1再获取locker2这样就不会出现死锁 public static void main(String[] args) throws InterruptedException {Object locker1 new Object();Object locker2 new Object();Thread t1 new Thread(() - {synchronized(locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println(t1获取了两把锁);}}});Thread t2 new Thread(() - {synchronized(locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println(t2获取了两把锁);}}});t1.start();t2.start();} 任何一个死锁的场景都必须同时具备上述四点缺少一点都不会构成死锁 本篇文章到此结束下篇文章将继续对线程安全的知识进行讲解