一、互斥锁
- java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应与一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有 一个线程访问该对象。
- 关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
- 同步的局限性:导致程序的执行效率降低。
- 同步方法(非静态)的锁可以是this,也可以是其他对象(保证是同一个对象)。
- 同步方法(静态)的锁为当前类本身,即Dog.class
1.1 同步机制
同步机制是指在对某些共享数据进行访问时,不允许多个线程同时访问导致数据错误,而要求同一时刻只能有一个线程访问共享数据的方法。一般我们通过给访问共享数据区的部分代码上锁的方式来实现同步。上锁之前需要先寻找访问共享数据的那一部分代码,尽量让上锁的代码尽可能的少,提高代码的执行效率。
1.2 生命周期
在线程的生命周期中,官方文档中记录了六个状态。分别是:
状态名 | 说明 |
---|---|
NEW | 尚未启动的线程 |
RUNNABLE | 正在java虚拟机中执行的线程 |
BLOCK | 受到阻塞并等待某个监视器锁的线程 |
WAITING | 无限期等待另一个线程来执行某一特定操作的线程 |
TIMED WAITING | 等待另一个指定时间内操作的线程 |
TERMINATED | 线程已经结束执行。 |
其中Runnable状态有很多人又将其分成了正在被CPU执行和等待CPU调度两个状态。当线程由于在等待锁的释放而无法执行时,该线程就处于BLOCK状态。使用下面这段代码可以查看线程的各个状态:
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() +" 状态 "+ t.getState());
t.start();
while(Thread.State.TERMINATED != t.getState()){
System.out.println(t.getName() +" 状态 "+ t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() +" 状态 "+ t.getState());
}
}
class T extends Thread{
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
1.3 synchronized 关键字
互斥锁是通过synchronized来修饰方法和代码块来实现,通过使用该关键字修饰方法时有默认的锁对象。当修饰静态方法时,默认的锁对象为当前类.class。当修饰非静态方法时,默认的锁对象是this。使用该关键字修饰代码块时,格式为:
synchronized(锁对象){
…
}
在选择修饰方法还是修饰代码块时,建议选择修饰代码块,以便提供代码的执行效率。但是使用修饰代码块来上锁时,需要手动选择锁对象。选择锁对象的原则就是要保证多个线程在执行过程中的锁对象必须是同一个对象实例。
常用的锁对象分别有:this对象、Object对象、当前类.class。同一个类的不同实例调用的当前类.Class对象是唯一的。而前面两种对象相信大家都比较熟悉,在通过继承Thread类实现多线程时,由于在main方法中会创建多个子线程的实例,因此无法使用synchronized(this)来实现线程的同步。这里通过代码来演示一下该情况无法锁住的情形,其中使用this对象和Object对象都无法锁住,出现了票数超买的情况。
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
//使用synchronize(this), 仍然会出现票数超买现象
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
}
}
//使用Thread方式
class SellTicket01 extends Thread{
private static int ticketNum = 100;
Boolean loop = true;//线程标志
public void sell(){
synchronized (this){//上锁
if(ticketNum <= 0){
System.out.println("售票结束...");
loop = false;
return;
}
//休眠50ms
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName()
+ " 售出1张票" + "剩余票数 = " + (--ticketNum));
}
}
public void run() {
while (loop){
sell();
}
}
}
而使用当前类.Class则没有出现该情况。当然如果你使用实现Runnable接口的方式来实现多线程,由于只创建了一个线程实例,前面两个锁对象也可以实现同步的效果。