JAVA多线程锁
线程的生命周期
总共六种状态,可归结为五种,线程的最终是死亡,阻塞不是最终状态,只是一个临时状态。只有调用了start方法,线程才进入就绪阶段。
//新生 NEW,
//运行 RUNNABLE,
//阻塞 BLOCKED,
//等待,死死地等 WAITING,
//超时等待 TIMED_WAITING,
//终止 TERMINATED;
Wait/sleep的区别
1、来自于不同的类
wait—>object
Sleep—>Thread//一般不使用,企业一般使用TimeUnit.time.sleep( )
2、关于锁的释放
wait会释放锁,sleep不会释放锁,必须sleep足够的时间
3、适用范围是不同的
Wait:必须在同步代码块中使用
sleep:可以在任何地方使用
4、是否需要捕获异常
wait:不需捕获异常
sleep:需要捕获异常——如果等待过久
需要使用锁的原因:当多个线程操作一个共同资源(共享数据)的时候,可能会出现错误异常。
举例:售票员问题,一共一百张票,三个售票窗口进行售票,错误展示:
//多线程模拟售票问题
import java.util.concurrent.TimeUnit;public class Windows {public static void main(String[] args) {//创建三个线程,线程操纵资源类Ticket ticket = new Ticket();for(int i=0;i<3;i++){new Thread(()->{try {ticket.sell();} catch (InterruptedException e) {e.printStackTrace();}},"售票员"+String.valueOf(i)).start();}}
}
//资源类
class Ticket{private int num=100;//总共100张票public void sell() throws InterruptedException {while(num>0){System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+(num--));//为了创造一些异常,让线程到此处sleep阻塞一下1TimeUnit.SECONDS.sleep(1);}}
}
如何解决此类,多线程操作同一数据的问题?
解决办法①synchronized关键字②lock锁
Synchronized关键字
①synchronized关键字修饰方法②synchronized关键字修饰代码块
synchronized关键字修饰方法
代码中只需要将资源类被调用的方法前加上修饰的关键字:synchronized
使用条件:如果操作共享数据的代码完整的声明在一个方法中,可以使用此方法,将方法声明为同步的
关于同步方法的总结:
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
- 非静态的同步方法,其同步监视器是:this(当前类的对象)
静态同步方法:同步监视器是:当前类本身(类.class( ))
//资源类
class Ticket{private int num=100;//总共100张票public synchronized void sell() throws InterruptedException {//此时用synchronized修饰方法sell(),其同步监视器是:this(当前类的对象),由于使用的是while(num>0){System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+(num--));//为了创造一些异常,让线程到此处sleep阻塞一下TimeUnit.MILLISECONDS.sleep(10);}}
}
此时不会出现重票问题!
synchronized关键字修饰代码块
使用格式:
synchronized(同步监视器){
//需要被同步的代码
}
说明:1. 操作共享数据的代码,即为需要被同步的代码——>此时注意不可包含多了,也不可包含少了
- 共享数据:多个线程共同操作的变量—例子中的票数num
- 同步监视器:俗称:锁,可以为任何一个类的对象。要求:多个线程必须要共用同一把锁
//资源类
class Ticket{private int num=100;//总共100张票public void sell() throws InterruptedException {synchronized (this){//此时使用synchronized修饰代码块while (num > 0) {System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (num--));//为了创造一些异常,让线程到此处sleep阻塞一下1TimeUnit.MILLISECONDS.sleep(10);}}}
}
需要注意的点:
死锁:
1:不同线程分别占用对方需要的同步资源不放,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
2:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态无法继续
使用同步时,要避免出现死锁
解决办法:
1:专门的算法、原则
2:尽量减少同步资源的定义
3:尽量避免嵌套同步
Lock锁
1:从JDK5开始,Java提供了更加强大的同步机制——通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当
2:java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程和lock对象加锁,线程开始访问共享资源之前应当先获得Lock对象
3:ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
lock锁操作三部曲:
1、newReentrantLock();
2、lock.lock();//加锁(try内部)
3、finally=>lock.unlock();//解锁
API中官方解释:
公平锁:十分公平;可以先来后到
非公平锁:十分不公平,可以插队(默认)
Lock锁的使用代码展示:
//资源类
class Ticket{private int num=100;//总共100张票Lock lock=new ReentrantLock();//ReentrantLock:lock接口的实现类之一public void sell() throws InterruptedException {try {lock.lock();while (num > 0) {System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (num--));//为了创造一些异常,让线程到此处sleep阻塞一下TimeUnit.MILLISECONDS.sleep(10);}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}
//此处使用Lock锁对操作进行了上锁,保证多线程操作下数据仍然安全
相比较于synchronized关键字,lock锁的优势在哪里?使用生产者消费者问题进行判断比较
线程之间的通信问题:生产者与消费者,要求:线程交替执行 A B操作同一个变量num=0;
可能出现的异常:IllegalMonitorStateException - 如果当前线程不是此对象的监视器的所有者。 抛出以表示线程已尝试在对象的监视器上等待或通知其他线程等待对象的监视器,而不拥有指定的监视器。
/** 线程之间的通信问题:生产者,消费者问题* 线程交替执行 A B操作同一个变量num=0;* A num+1;* B num-1;* */
public class ProductQuestion {public static void main(String[] args) {//创造两个线程,分别进行商品的生产和商品的消费Product product = new Product();new Thread(()->{for(int i=0;i<10;i++) {try {product.produce();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(()->{for(int i=0;i<10;i++) {try {product.buy();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}
}
//资源类:
class Product{private int num=0;//等待-业务-通知//不使用synchronized修饰的话,会报错,IllegalMonitorStateException - 如果当前线程不是此对象的监视器的所有者。 抛出以表示线程已尝试在对象的监视器上等待或通知其他线程等待对象的监视器,而不拥有指定的监视器。 public synchronized void produce() throws InterruptedException {if(num!=0){//商品个数不为0,当前线程需要等待this.wait();}//为0则操作num++;System.out.println(Thread.currentThread().getName()+"生产商品,当前剩余商品:"+num);//通知可以进行消费this.notifyAll();//通知其他线程可以进行商品的消费}public synchronized void buy() throws InterruptedException {if(num==0){//此时没有商品,需要等待this.wait();}//不为0则可以进行操作num--;System.out.println(Thread.currentThread().getName()+"消费商品,当前剩余商品:"+num);this.notifyAll();//通知其他线程可以生产商品}
}
结果:
思考:如果还是此题的背景,换为四个线程,两个生产,两个消费还可行吗?
public static void main(String[] args) {//创造两个线程,分别进行商品的生产和商品的消费Product product = new Product();new Thread(()->{for(int i=0;i<10;i++) {try {product.produce();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();new Thread(()->{for(int i=0;i<10;i++) {try {product.buy();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();new Thread(()->{for(int i=0;i<10;i++) {try {product.produce();} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();new Thread(()->{for(int i=0;i<10;i++) {try {product.buy();} catch (InterruptedException e) {e.printStackTrace();}}},"D").start();}
结果:
产生错误原因分析:
调整方式,将被调用方法中的if判断改为while即可
//资源类:
class Product{private int num=0;//等待-业务-通知public synchronized void produce() throws InterruptedException {while (num!=0){//此时将判断语句改为while,而不是if,因为if只判断一次,有可能会造成虚假唤醒。//商品个数不为0,当前线程需要等待this.wait();}//为0则操作num++;System.out.println(Thread.currentThread().getName()+"生产商品,当前剩余商品:"+num);//通知可以进行消费this.notifyAll();//通知其他线程可以进行商品的消费}public synchronized void buy() throws InterruptedException {while (num==0){//此时没有商品,需要等待this.wait();}//不为0则可以进行操作num--;System.out.println(Thread.currentThread().getName()+"消费商品,当前剩余商品:"+num);this.notifyAll();//通知其他线程可以生产商品}
结果如下:
使用JUC解决生产消费者问题
synchronized中使用wait( )和notifyAll( )来实现此问题
在Lock中使用condition接口的实现类来取得相同的效果
condition接口的实例——一个实例本质上绑定一个锁!
//资源类:
class Products{private int num=0;Lock lock=new ReentrantLock();Condition notNull=lock.newCondition();//此时condition取代了同步监视器//等待-业务-通知public void produce() {lock.lock();try {while (num!=0){//商品个数不为0,当前线程需要等待notNull.await();}//为0则操作num++;System.out.println(Thread.currentThread().getName()+"生产商品,当前剩余商品:"+num);//通知可以进行消费notNull.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void buy() {lock.lock();try {while (num==0){//此时没有商品,需要等待notNull.await();}//不为0则可以进行操作num--;System.out.println(Thread.currentThread().getName()+"消费商品,当前剩余商品:"+num);notNull.signalAll();//通知其他线程可以生产商品} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}
此时结果仍然是0和1交替进行生产和消费,Lock和synchronized的区别,有一些操作lock可以做到而synchronized无法做到——精确唤醒线程,此时需要创建多个condition对象!
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*
* 要求:执行完A操作——>执行B操作——>执行C操作
* */
public class ConditionTest {public static void main(String[] args) {Something something = new Something();new Thread(()->{for(int i=0;i<5;i++) {try {something.printA();} catch (InterruptedException e) {e.printStackTrace();}}},"1").start();new Thread(()->{for(int i=0;i<5;i++) {try {something.printB();} catch (InterruptedException e) {e.printStackTrace();}}},"2").start();new Thread(()->{for(int i=0;i<5;i++) {try {something.printC();} catch (InterruptedException e) {e.printStackTrace();}}},"3").start();}}class Something{private int num=1;Lock lock=new ReentrantLock();Condition conditionA=lock.newCondition();Condition conditionB=lock.newCondition();Condition conditionC=lock.newCondition();//判断,等待,执行,通知public void printA() throws InterruptedException {lock.lock();try {while(num!=1){conditionA.await();}System.out.println(Thread.currentThread().getName()+":"+"AAAAA");num=2;//指定num为2conditionB.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void printB() throws InterruptedException {lock.lock();try {while(num!=2){conditionB.await();}System.out.println(Thread.currentThread().getName()+":"+"BBBBB");num=3;//指定num为3conditionC.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}} public void printC() throws InterruptedException {lock.lock();try {while(num!=3){conditionC.await();}System.out.println(Thread.currentThread().getName()+":"+"CCCCC");num=1;//指定num为1conditionA.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}
synchronized 和 Lock的区别
- synchronized 内置的Java关键字,lock是一个Java类
- synchronized无法判断获取锁的状态,lock可以判断是否获取到了锁
- synchronized会自动释放锁,lock必须要手动释放锁!如果不释放锁——死锁
- synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等待),lock锁就不一定会等待下去
- synchronized 可重入锁,不可以中断的,非公平;lock 可重入锁,可以判断锁,非公平(可以自己设置为公平锁,非公平是默认状态)
- synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码