java并发,同步synchronize和lock锁的使用方法和注意,死锁案例分析
1.什么是线程安全问题
多个线程同时共享同一个全局变量或者静态变量的时候,某个线程的写操作,可能会影响到其他线程操作这个变量。所有线程读一个变量不会产生线程安全问题。
实际场景就是火车站买票问题:剩余100张火车票,重庆火车站和杭州火车站都在售卖,两个窗口同时卖的时候,在不同步的情况下,就可能导致线程安全问题,导致多卖
代码案例:
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public void run() { while (ticketNum > 0) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重庆火车站"); thread.start(); thread1.start(); } }
输出结果:
杭州火車站购买第:9 重庆火车站购买第:8 重庆火车站购买第:7 杭州火車站购买第:6 杭州火車站购买第:5 重庆火车站购买第:4 杭州火車站购买第:3 重庆火车站购买第:3 杭州火車站购买第:2 重庆火车站购买第:2 杭州火車站购买第:1 重庆火车站购买第:0
2.如何解决线程安全问题
1.使用线程同步synchronized或者使用锁lock
synchronized和lock为什么能解决线程安全问题,是因为synchronized或者lock可以保证可能会出现线程安全问题的地方在同一时间只能允许一条线程进行操作,只有等该线程执行完毕以后,其余线程才能在执行.
类似上厕所,如果多个人同时一起去上厕所,但是厕所只有一个坑位,最后的结果就是谁也上不了。此时如果加上synchronize或者lock以后,相当于有给厕所上了一道锁,优先抢到锁的用户才能入厕。其余用户只能等待 抢到锁的用户完事以后,在去争夺锁。这样就保证了 这个厕所在同一时间只有一个用户使用。
2.什么是线程同步:
就是当多个线程共享同一个变量的时候,不会受其他线程的影响
3.关于synchronize同步在java中的用法和注意
1.同步代码块
synchronize(Object){
可能会发送线程安全的数据
}
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { synchronized (object) { while (ticketNum > 0) { try { Thread.sleep(200); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void buyTicket() { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重庆火车站"); thread.start(); thread1.start(); } }
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { synchronized (this) { while (ticketNum > 0) { try { Thread.sleep(200); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void buyTicket() { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重庆火车站"); thread.start(); thread1.start(); } }
关于synchronize(Object)中Object的注意,Object对象必须是多个线程共同拥有的数据,就是使用的同一把锁 在上面的代码中
synchronize(object) 其中object实例对象是static修饰的,表示所有的类的实例化对象都拥有该对象,所以可以作为锁
synchronize(this) this可以指的的是当前对象,代码里面两个线程都是使用的都是BuyTicketThread对象
例如:以下情况就不能再使用this对象作为锁,只能使用object作为锁 不管BuyTicketThread 类实例化多少次,Static Object object=new Object()都是所有对象共享变量
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { synchronized (this) { while (ticketNum > 0) { try { Thread.sleep(200); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public void buyTicket() { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread1, "重庆火车站"); thread.start(); thread1.start(); } }
2.同步函数在方法上加入synchronize
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重庆火车站"); thread.start(); thread1.start(); } }
在同步函数的中,synchronize使用的是什么锁。
答:同步函数synchronize使用的是this锁
案例:
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread1, "重庆火车站"); thread.start(); thread1.start(); } }
输出结果:
重庆火车站购买第:7
杭州火車站购买第:7
重庆火车站购买第:5
杭州火車站购买第:6
杭州火車站购买第:4
重庆火车站购买第:3
杭州火車站购买第:2
重庆火车站购买第:1
不同对象的时候发现synchronize已经不能保证线程安全问题了
3.静态同步函数:在方法上面使用了static修饰
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread, "重庆火车站"); thread.start(); thread1.start(); } }
上面再buyTicket上面使用了static修饰方法以后,也能保证同步,那静态同步函数使用的是什么锁呢?还是this锁吗?
观察一下情况:
package com.thread; public class BuyTicketThread implements Runnable { public static int ticketNum = 100; public static Object object = new Object(); public void run() { while (ticketNum > 0) { try { Thread.sleep(20); buyTicket(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static synchronized void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } public static void main(String[] args) { BuyTicketThread buyTicketThread = new BuyTicketThread(); BuyTicketThread buyTicketThread1 = new BuyTicketThread(); Thread thread = new Thread(buyTicketThread, "杭州火車站"); Thread thread1 = new Thread(buyTicketThread1, "重庆火车站"); thread.start(); thread1.start(); } }
输出结果:
杭州火車站购买第:22
重庆火车站购买第:21
重庆火车站购买第:20
杭州火車站购买第:19
杭州火車站购买第:18
重庆火车站购买第:17
杭州火車站购买第:16
重庆火车站购买第:15
杭州火車站购买第:14
重庆火车站购买第:13
重庆火车站购买第:12
杭州火車站购买第:11
杭州火車站购买第:10
重庆火车站购买第:9
杭州火車站购买第:8
重庆火车站购买第:7
杭州火車站购买第:6
重庆火车站购买第:5
重庆火车站购买第:4
杭州火車站购买第:3
杭州火車站购买第:2
重庆火车站购买第:1
在不同对象的情况下,静态同步函数依旧能保证线程安全问题,看来 静态函数使用的不是this锁。使用的该函数所属字节码对象,类名.class 获取Class文件对象
总结:
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件
4.lock锁的使用方法
package com.thread; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadLockDemo implements Runnable { public static int ticketNum = 100; public static Lock lock = new ReentrantLock(); public void run() { lock.lock();//获取锁 try { while (ticketNum > 0) { Thread.sleep(20); buyTicket(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock();//释放锁,程序异常的时候不会释放锁,synchronize异常以后自动释放锁 } } public void buyTicket() { if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } public static void main(String[] args) { ThreadLockDemo threadLockDemo = new ThreadLockDemo(); Thread thread = new Thread(threadLockDemo, "A窗口"); Thread thread1 = new Thread(threadLockDemo, "B窗口"); thread.start(); thread1.start(); } }
5.Lock 接口与 synchronized 关键字的区别
1.lock是接口,而synchronize是java关键字
2.synchronize在出现异常的时候会自动释放锁,而lock不会,所以在释放锁的地方需要在finally
3.https://www.cnblogs.com/dayhand/p/3610867.html
6.死锁例子
package com.thread; public class DieLock implements Runnable { public static int ticketNum = 100; public boolean flag = true; public static Object object = new Object(); public void run() { if (flag) { while (true) { synchronized (object) { buyTicket(); } } } else { while (true) { buyTicket(); } } } public synchronized void buyTicket() { synchronized (object) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } } public static void main(String[] args) throws InterruptedException { DieLock dieLock = new DieLock(); Thread thread = new Thread(dieLock, "线程1"); Thread thread1 = new Thread(dieLock, "线程2"); thread.start(); Thread.sleep(40); dieLock.flag = false; thread1.start(); } }
运行上面的代码有很大的概率出现线程死锁
分析死锁产生的原因:在main方法启动的时候,创建了两个线程thread和thread1 两个线程共享dieLock,thread.start启动以后 优先获得cpu的执行权,执行run方法里面的代码,此时flag=true 运行执行if条件,在里面有同步代码块,需要获取Object锁,该锁是static修饰,表示所有的该对象都共享该变量,thread优先获取到Object锁,此时可以执行buyTicket()方法 buyTicket()方法是一个同步函数,需要执行这个方法还需要获取到锁,同步函数使用的是this锁,如果拿到这个锁 则购买一张票成功,释放锁。此时main方法 休眠40毫秒时间到了以后,程序向下执行,dieLock.flag=false,thread1线程启动,如果抢到cpu 则执行else{}里面的方法,同样需要去抢锁 获得锁,释放锁 和线程thread的执行一样,完成以后thread1也购买成功一张票。这是在正常逻辑下的情况的执行结果。
思考下面这种情况:
if (flag) { while (true) { synchronized (object) {
//如果当前线程thread获取到Object锁的时候,失去了cpu的占用权,程序不向下执行了(并不是只有sleep才会交出cpu的执行权) buyTicket(); } } }
此时thread1.start()以后获取到了cpu的使用权,当前的flag是false 则执行else{}的代码,
else { while (true) { buyTicket(); } }
//所有调用buyTicket()方法都需要先获取锁(this锁)
public synchronized void buyTicket() { synchronized (object) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } if (ticketNum > 0) { System.out.println(Thread.currentThread().getName() + "购买第:" + ticketNum--); } } }
thread获取到了object锁,thread1 获取到buyTicket()方法的this锁,由于这两个锁不是同一个锁,所以并不影响。当thread1线程继续执行buyTicket()方法的时候。在外部获取了this锁,还要继续获取同步代码块 synchronized(object) 锁,但是 此时object锁已经在if的时候 被线程thread占用,那thread1 就需要等待 thread释放object锁,thread1阻塞。然后 线程thread 获取到cpu执行权,调用buyTicket()方法,要执行 buyTicket()方法 需要获取this锁,此时this锁又被 thread1占用。此时线程thread和thre1 都在等待对方释放锁,形成死锁
形成死锁原因 :1.必须要有多线程 2.同步嵌套,不同锁对象 3.锁的互斥