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阻塞一下1
TimeUnit.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阻塞一下1
TimeUnit.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中官方解释: