1.复习Synchronized关键字
1.概述
Synchronized是java的关键字
2.使用
修饰一个代码块,被修饰的代码称为同步代码块
修饰一个方法
修饰一个静态方法:作用范围是这个静态方法,作用的对象是这个类的所有对象;
修饰一个类;其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象
3.注意
Synchronized的方法是不可被继承的,当子类覆盖率这个方法会导致锁失效
2.什么是Lock接口
1.概念
Lock 锁实现提供了比使用Synchronized方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。(可以手动加锁)
2.什么是可重入锁
加了可重入锁后,其他线程在外等待,等可重入锁释放。下一个线程进入再次加锁,释放锁。循环执行
3.lock和Synchronized的区别
Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个接口,通过实现类可以实现同步访问;
Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
Lock 可以提高多个线程进行读操作的效率。
从性能上讲Lock的性能高于Synchronized,竞争不激烈两者差不多。
4.ReentrantLock实现类
ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体看一下如何使用。
- 实现可重入锁
Lock lock=new ReentrantLock();
- 手动加锁
lock.lock();
- 手动释放锁
lock.unlock()
3.线程间通信
因为线程执行不一定,所以通过线程间通信让线程能有序执行。
synchronized方式加锁:使用wait() 和notifyAll()方法。注意必须要在对象加锁的情况下使用wait()等待的时候会释放锁。其他线程可以操作该对象。wait的线程进入等待池中。notifyAll唤醒所有线程。从那里唤醒就从哪里继续执行。wait()必须要和while一起使用,防止出现虚假唤醒。
lock方式手动加锁:是要实现lock接口,调用newCondition()方法,返回一个新Condition绑定到该Lock实例。调用await()方法等待和signalAll() 方法唤醒等待线程
await()的详情:相关的锁Condition(开锁钥匙)以原子方式释放,并且当前线程的线程调度目的就退出,一直处于休眠状态等待下列情况唤醒:
一些其它线程调用signal()方法或当前线程碰巧被选择作为被唤醒线程;
发生"虚假唤醒"调用interrupt()方法中断进程
代码示例
class print{
private int number =0;
Lock lock = new ReentrantLock();
/*钥匙实例化*/
Condition a=lock.newCondition();
Condition b=lock.newCondition();
Condition c=lock.newCondition();
public void printA(){
lock.lock();
try {
while (number!=0){
a.await();
}
for(int i=1;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"打印"+i);
}
number =1;
b.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number!=1){
b.await();
}
for(int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"打印"+i);
}
number =2;
c.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number!=2){
c.await();
}
for(int i=1;i<=15;i++){
System.out.println(Thread.currentThread().getName()+"打印"+i);
}
number =0;//标志位
a.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class LockPrint {
public static void main(String[] args) {
print print = new print();
new Thread(()->{
for(int j=0;j<2;j++){
print.printA();
}
},"A").start();
new Thread(()->{
for(int j=0;j<2;j++){
print.printB();
}
},"B").start();
new Thread(()->{
for(int j=0;j<2;j++){
print.printC();
}
},"C").start();
}
}
4.使用Lock实现买票实例
class Insert{
private int number =30;
Lock lock=new ReentrantLock();
/*买票*/
public void sale(){
/*手动加锁*/
lock.lock();
if(number==0){
System.out.println("买票结束");
return;
}
try {
number--;
System.out.println(Thread.currentThread().getName()+"卖出一张票");
System.out.println("剩余票"+number);
}finally {
/*手动释放锁*/
lock.unlock();
}
}
}
public class LockSaleTicket {
public static void main(String[] args) {
Insert ticket = new Insert();
Thread thread = new Thread() {
@Override
public void run() {
for(int j=0;j<15;j++){
ticket.sale();
}
}
};
Thread thread1 =new Thread(()->{
for(int j=0;j<15;j++){
ticket.sale();
}
});
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<15;j++){
ticket.sale();
}
}
},"xiao").start();
thread.start();
thread1.start();
}
}
5.集合的线程安全
java.util.ConcurrentModificationException多线程同时修改集合会出现该异常。
for(Integer i: arr){
arr.remove(i);
}
Itr.next()调用Itr.checkForComodification()时,
会发现modCount和expectedModCount两个值不相等!
因为在这个删除操作的过程中没有对expectedModCount重新赋值,所以就抛出异常了。
for(**:**)的本质上还是集合的迭代。
1.如何解决Arraylist线程(并发修改异常)不安全问题?
1.通过vector解决(不常用)
具体是vector中的方法加上了synchronized
2.使用Collections工具类(不常用)
具体是调用工具类方法。Collections.synchronizdList(new ArrayList());
3.使用CopyOnWriteArrayList类创建一个安全的集合对象(写时复制技术)
其中的add方法使用了lock手动加锁操作
2.如何解决HashSet线程不安全问题
hashset的底层也是HashMap6
答:使用CopyOnWriteArraySet创建对应的集合底层分析,也是和上面一项的写时复制技术。每次add的时候都会复制之前数组,性能不是很高。
3.如何解决HashMap线程不安全问题
1.使用ConcurrentHashMap类,创建map集合
缺点:效率比较低,但是单单只是进行遍历查找的话,效率比较高
4.CopyOnWriterArrayList的原理
通过动态数组与线程安全来分析。
“动态数组”机制 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyOnWriteArrayList 的原因由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。
5.“线程安全”机制,加锁
通过 volatile 和互斥锁来实现的。
通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看
到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读
取到的数据总是最新的”这个机制的保证。
通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,
再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥
锁”,就达到了保护数据的目的。