多线程同步问题

先看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=======================================

猜你喜欢

转载自blog.csdn.net/qq_34115899/article/details/81052568