先看Demo1:
public class Demo1_Synchronized {
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
public void run() {
for (int i = 0; i < 100; ++i) {
p.print1(); // jdk1.8不需要显式将p用final修饰,默认用final修饰
}
}
}.start();
new Thread() {
public void run() {
for (int i = 0; i < 100; ++i) {
p.print2();
}
}
}.start();
}
}
class Printer {
// 零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:
// 生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
private byte[] lock = new byte[0]; // 特殊的锁
// 注意:匿名对象不可以当做锁对象,因为不能保证两个锁对象是同一个对象
// 非静态的同步方法,锁对象是this,锁方法和锁this是一样的效果
// 静态的同步方法,锁对象是当前类的字节码对象,锁方法和锁Printer.class是一样的
public static void print1() {
// synchronized (lock) {
synchronized (Printer.class) {
System.out.print("我");
System.out.print("最");
System.out.print("帅");
System.out.print("\r\n");
}
// }
}
public synchronized static void print2() {
// synchronized (lock) {
System.out.print("你");
System.out.print("很");
System.out.print("丑");
System.out.print("\r\n");
// }
}
}
注意:匿名对象不可以当做锁对象,因为不能保证两个锁对象是同一个对象
非静态的同步方法,锁对象是this,锁方法和锁this是一样的效果
比如public synchronized void print(){...}
就和public void print(){
synchronized(this){...}
}
效果是一样的。
静态的同步方法,锁对象是当前类的字节码对象,锁方法和锁Printer.class是一样的
public synchronized static void print(){...}
和public static void print(){
synchronized(Printer.class){...}// 类名.class
}
效果是一样的。
来看下一个Demo2
public class Demo2_Ticket {
/**
* @param args
* 模拟铁路售票, 4个窗口卖100张票
*/
public static void main(String[] args) {
for (int i = 0; i < 4; ++i) {
new TicketSeller("窗口" + i).start();
}
}
}
class TicketSeller extends Thread {
private static int tickets = 100;
public TicketSeller(String name) {
super(name);
// TODO Auto-generated constructor stub
}
public void run() {
while (true) {
synchronized (TicketSeller.class) { // ==========提问点提问点提问点======
if (tickets == 0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName() + "...这是第" + tickets-- + "号票");
}
}
}
}
模拟铁路卖票100张,肯定不能重复卖票,不然上车该争车位发生矛盾了。
看到提问点
如果这里不加synchronized,可以吗?
不可以,如果不加,可能卖出负数号票,比如线程3刚要进行下一次循环,此时tickets=1,而线程2和线程1正好在执行tickets--,结果抢先在线程3之前执行了,tickets=-1了,然后就跳过了判断==0阶段,无限循环停不下来了。
那么问题来了,我判断条件改为tickets<=0不就好了?
不可以,哪怕这么改动(为了放大现象,加上了Thread.sleep(10)睡眠10ms),比如线程3, 2, 1都在睡眠,此时tickets=1,然后线程3睡醒了,先执行tickets--,打印这是第1号票,接着下一轮循环跳出,线程3结束,然后线程2和线程1睡醒了,依然执行tickets--,依次打印这是第0号票,这是第-1号票。。???卖出负数票了??
那么问题来了,我加上synchronized (this){...}不就好了?
不可以,这里是new Thread了4次,是4个线程对象,每个对象都有自己的this,互不干扰,这种方法等于没加synchronized。
那么问题来了,我加上锁对象就好了,private Object obj = new Object();再synchronized (obj) {...}
这个可以,不过这个是成员变量,每个线程对象都有自己的成员变量obj,所以要改为共享的,加上static。
不过零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码,生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。这里改为private static byte[] lock = new byte[0];再synchronized (lock) {...}会比较好。当然,锁字节码对象也可以。synchronized(TicketSeller.class){...}// 类名.class
那么看看Demo3,用Runnable实现
public class Demo3_Ticket {
/**
* @param args
* 多次开启一条线程是非法的
*/
public static void main(String[] args) {
Ticket t = new Ticket();
new Thread(t, "窗口1").start();
new Thread(t, "窗口2").start();
new Thread(t, "窗口3").start();
new Thread(t, "窗口4").start();
}
}
class Ticket implements Runnable {
private int tickets = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (this) { // ===========提问点提问点提问点=========
if (tickets == 0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");
}
}
}
}
看到提问点
在这里用synchronized (this) {...}对吗?
是对的,这里4个线程都是用的同一个Ticket对象。里面的tickets不需要加static,因为这个代码块同时只能一个线程执行,不会有并发问题。也可以synchronized (Ticket.class) {...}。
这里的synchronized (this) {...}能和上面一句while (true) {...}交换吗?
可以,但是!就只有窗口1在售票(即线程1执行),执行完才轮到窗口2,然后发现tickets已经为0,线程2结束,同理,线程3,4结束。程序结束。
更详细的总结见此处:
Java中Synchronized的用法:https://blog.csdn.net/qq_34115899/article/details/80356581
========================================Talk is cheap, show me the code=======================================