对象锁:Java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的好处,方法抛异常的时候,锁仍然可以由JVM来自动释放。
类锁:对象锁是用来控制实例方法之间的同步,类锁是来控制静态方法(或静态变量互斥体)之间的同步。其实类锁只是一个概念上的东西,并不是真实存在的,它只用来帮助我们理解锁定实例方法和静态方法的区别的。我们都知道,java类可能会有很多个对象,但是只有1个Class对象,也就说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有个互斥锁,而类的静态方法是需要Class对象。所以所谓类锁,不过是Class对象的锁而已。获取类的Class对象有好几种,最简单的就是MyClass.class的方式。类锁和对象锁不是同一个东西,一个是类的Class对象的锁,一个是类的实例的锁。也就是说:一个线程访问静态sychronized的时候,允许另一个线程访问对象的实例synchronized方法。反过来也是成立的,为他们需要的锁是不同的。
前提,synchronized 加到 static 方法前面是给class 加锁,即类锁;而synchronized 加到非静态方法前面是给对象上锁。
对象锁和类锁是不同的锁,所以多个线程同时执行这2个不同锁的方法时,是异步的。
在LockTask类中定义三个方法,task1方法和task2方法为类锁,task3方法为对象锁。
创建三个线程类,分别执行task1,task2,task3:
public class LockTask {
public synchronized static void task1() {
Log.i(TAG, Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, Thread.currentThread().getName() + "end");
}
public synchronized static void task2() {
Log.i(TAG, Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, Thread.currentThread().getName() + "end");
}
public synchronized void task3() {
Log.i(TAG, Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, Thread.currentThread().getName() + "end");
}
static class Thread1 extends Thread {
LockTask lockTask;
public Thread1(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task1();
}
}
static class Thread2 extends Thread {
LockTask lockTask;
public Thread2(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task2();
}
}
static class Thread3 extends Thread {
LockTask lockTask;
public Thread3(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task3();
}
}
}
创建三个线程,执行同一个lockTask对象的三个方法:
public class ObjectClassLock {
public static void test() {
LockTask lockTask = new LockTask();
LockTask.Thread1 thread1 = new LockTask.Thread1(lockTask);
LockTask.Thread2 thread2 = new LockTask.Thread2(lockTask);
LockTask.Thread3 thread3 = new LockTask.Thread3(lockTask);
thread1.setName("thread1");
thread2.setName("thread2");
thread3.setName("thread3");
thread1.start();
thread2.start();
thread3.start();
}
}
执行顺序为:
thread1start
thread3start
thread1end
thread3end
thread2start
thread2end
因为task1和task2方法都是类锁,所以多线程执行方法执行同一个类锁,所以task1和task2方法需要同步执行,task3与task1、task2是可以异步执行的。
修改示例改为使用对象锁:
class LockTaskTest2 {
static class Thread1 extends Thread {
LockTask lockTask;
public Thread1(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task3();
}
}
static class Thread2 extends Thread {
LockTask lockTask;
public Thread2(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task3();
}
}
public static void main(String[] args) {
LockTask lockTask = new LockTask();
LockTask lockTask2 = new LockTask();
Thread1 thread1 = new Thread1(lockTask);
Thread2 thread2 = new Thread2(lockTask2);
thread1.setName("thread1");
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
执行结果:
thread1start
thread2start
thread1end
thread2end
两个线程执行task3方法,为对象锁方法,只会锁住同一对象,lockTask与lockTask2为不同对象,所以会异步执行。
修改示例改为多线程使用类锁,只将执行task3方法改为task1方法:
class LockTaskTest2 {
static class Thread1 extends Thread {
LockTask lockTask;
public Thread1(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task1();
}
}
static class Thread2 extends Thread {
LockTask lockTask;
public Thread2(LockTask lockTask) {
this.lockTask = lockTask;
}
@Override
public void run() {
lockTask.task1();
}
}
public static void main(String[] args) {
LockTask lockTask = new LockTask();
LockTask lockTask2 = new LockTask();
Thread1 thread1 = new Thread1(lockTask);
Thread2 thread2 = new Thread2(lockTask2);
thread1.setName("thread1");
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
执行结果:
thread1start
thread1end
thread2start
thread2end
两个线程执行task1方法,为类锁方法,lockTask与lockTask2虽然为不同对象,但是类锁对该类所有对象都起加锁作用,thread1拿到LockTask类锁,执行完task1方法,thread2才能拿到类锁执行task1方法,所以会同步执行。
Java 多线程之多个窗口售票问题
public class MyRunnable implements Runnable {
private int tickets = 20;
@Override
public void run() {
while(true) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Log.i("minfo", Thread.currentThread().getName() + "售出了第" + (tickets--) + "张票");
}
}
}
}
卖票任务用3个线程同时处理:
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable, "窗口一");
Thread t2 = new Thread(myRunnable, "窗口二");
Thread t3 = new Thread(myRunnable, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
买票过程:
窗口一售出了第20张票
窗口二售出了第19张票
窗口三售出了第18张票
窗口三售出了第17张票
窗口一售出了第15张票
窗口二售出了第16张票
窗口三售出了第14张票
...
窗口二售出了第3张票
窗口三售出了第2张票
窗口一售出了第2张票
窗口二售出了第1张票
窗口三售出了第-1张票
窗口一售出了第0张票
买票出现了错乱,因为多线程对这个票或者这个runnable对象没有加锁,加锁之后:
public class MyRunnable implements Runnable {
private int tickets = 20;
@Override
public void run() {
while(true) {
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Log.i("minfo", Thread.currentThread().getName() + "售出了第" + (tickets--) + "张票");
}
}
}
}
}
结果正确:
窗口一售出了第20张票
窗口一售出了第19张票
窗口一售出了第18张票
...
窗口三售出了第4张票
窗口三售出了第3张票
窗口三售出了第2张票
窗口三售出了第1张票
总结:
1.如果多线程同时访问同一类的类锁以及对象锁,这两个方法执行是异步的,原因:类锁和对象锁是2中不同的锁,可以各自持有锁。
2.类锁对该类的所有对象都能起作用,而对象锁对该类不同对象不起作用。