线程同步的引入
之前出现的问题:
例:卖票过程中,出现了重票和错票。(线程安全问题)
出现原因:当某个线程操作车票的过程中,尚未操作完成时(被阻塞),其他线程参与进来,也操作车票。
解决方法:当一个线程在操作车票数时,其他线程不能操作进来。直到当前线程操作完成后,其他线程才能开始操作。即使当前线程出现了阻塞,也不能改变。
在Java中,我们通过同步机制,来解决线程安全问题。
synchronized实现同步
方法1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
①操作共享数据的代码,即为需要被同步的代码。
②共享数据:多个线程共同操作的变量(车票数)。
③同步监视器:俗称:锁。任何一个类的对象,都可以充当锁(多个线程必须要共用一把锁)。
④在实现Runmable 接口创建多线程时,可考虑this 为监视器;在继承 Thread类创建多线程时,可考虑当前类为监视器。
方式2:同步方法(使用synchronized修饰方法)
public synchronized void a(...){}
1、同步方法仍有同步监视器,只是不需要显式声明。
2、非静态的同步方法,同步监视器为this;静态的为当前类本身。
线程同步的优缺点:
好处:解决了线程安全的问题。
局限性:只能有一个线程参与,相当于是一个单线程的过程,效率低。
死锁
出现原因
1、不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
2、出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续执行。
解决方法
1、专门的算法,原则。
2、减少同步资源的定义。
3、避免嵌套同步。
Lock(锁)实现同步
步骤
1、实例化ReentrantLock(重进入锁)。
2、使用 try-catch-finally 将需同步的代码包起来。
3、在 try{} 中先调用对象的lock()方法进行锁定,然后再执行业务代码。
4、在finally中调用对象的unlock()方法进行解锁。
注:如果ReentrantLock的参数为true,则表示为公平的,先进入的线程出来后会被先加载。
synchronized 和 Lock的异同
同:都可以解决线程安全问题
不同:synchronized自动释放同步监视器,Lock需手动的启动和关闭同步监视器。
优先使用顺序:Lock > 同步代码块 > 同步方法