Java处理线程安全问题
- Java处理线程安全问题
- 方法1:同步代码块
- 同步代码块演示1
- 同步代码块演示2
- 同步监视器总结
- 方法2:同步方法
- 代码演示
- 总结
- 方法3:Lock锁
- Lock锁引入
- 代码演示
- Lock和synchronized的区别
- 优先使用顺序
- 线程同步的优缺点
- 对比:
- 可能造成死锁
- 代码演示
Java处理线程安全问题
方法1:同步代码块
同步代码块演示1
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
@Override
public void run() {
for (int i = 1; i <= 100 ; i++) {
synchronized (this){//把具有安全隐患的代码锁住即可,如果锁多了就会效率低 --》this就是这个锁
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
}
}
同步代码块演示2
public class BuyTicketThread extends Thread {
public BuyTicketThread(String name){
super(name);
}
//一共10张票:
static int ticketNum = 10;//多个对象共享10张票
//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
@Override
public void run() {
//每个窗口后面有100个人在抢票:
for (int i = 1; i <= 100 ; i++) {
synchronized (BuyTicketThread.class){//锁必须多个线程用的是同一把锁!!!
if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
}
}
同步监视器总结
- 总结1:认识同步监视器(锁子) ----- synchronized(同步监视器){ }
- 1)必须是引用数据类型,不能是基本数据类型
- 2)也可以创建一个专门的同步监视器,没有任何业务含义
- 3)一般使用共享资源做同步监视器即可
- 4)在同步代码块中不能改变同步监视器对象的引用
- 5)尽量不要String和包装类Integer做同步监视器
- 6)建议使用final修饰同步监视器
- 总结2:同步代码块的执行过程
- 1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
- 2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
- 3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
- 4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
- 5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)
- 强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)
- 总结3:其他
- 1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块
- 2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块
方法2:同步方法
代码演示
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
@Override
public void run() {
for (int i = 1; i <= 100 ; i++) {
buyTicket();
}
}
public synchronized void buyTicket(){//锁住的是this
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
public class BuyTicketThread extends Thread {
public BuyTicketThread(String name){
super(name);
}
//一共10张票:
static int ticketNum = 10;//多个对象共享10张票
//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
@Override
public void run() {
//每个窗口后面有100个人在抢票:
for (int i = 1; i <= 100 ; i++) {
buyTicket();
}
}
public static synchronized void buyTicket(){//锁住的 同步监视器: BuyTicketThread.class
if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
System.out.println("我在"+Thread.currentThread().getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
总结
- 总结1:
- 多线程在争抢资源,就要实现线程的同步(就要进行加锁,并且这个锁必须是共享的,必须是唯一的。
- 锁一般都是引用数据类型的。
- 目的:解决了线程安全问题。
- 总结2:关于同步方法
- 不要将run()定义为同步方法
- 非静态同步方法的同步监视器是this
静态同步方法的同步监视器是 类名.class 字节码信息对象 - 同步代码块的效率要高于同步方法
- 原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
- 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块
方法3:Lock锁
Lock锁引入
- JDK1.5后新增新一代的线程同步方式:Lock锁
- 与采用synchronized相比,lock可提供多种锁方案,更灵活
- synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。
- 但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。
代码演示
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
//拿来一把锁:
Lock lock = new ReentrantLock();//多态 接口=实现类 可以使用不同的实现类
@Override
public void run() {
//此处有1000行代码
for (int i = 1; i <= 100 ; i++) {
//打开锁:
lock.lock();
try{
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}catch (Exception ex){
ex.printStackTrace();
}finally {
//关闭锁:--->即使有异常,这个锁也可以得到释放
lock.unlock();
}
}
}
}
Lock和synchronized的区别
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序
- Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)
线程同步的优缺点
对比:
- 线程安全,效率低
- 线程不安全,效率高
可能造成死锁
- 死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
代码演示
public class TestDeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object(),o2 = new Object();
public void run(){
System.out.println("flag=" + flag);
// 当flag==1锁住o1
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
// 只要锁住o2就完成
synchronized (o2) {
System.out.println("2");
}
}
}
// 如果flag==0锁住o2
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
// 只要锁住o1就完成
synchronized (o1) {
System.out.println("3");
}
}
}
}
public static void main(String[] args) {
// 实例2个线程类
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
// 开启2个线程
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
- 解决方法: 减少同步资源的定义,避免嵌套同步