java synchronized静态同步方法与非静态同步方法,同步语句块

对代码进行同步控制我们可以选择同步方法,也可以选择同步块,这两种方式各有优缺点。同步块不仅可以更加精确的控制对象锁,还可以控制锁的作用域,何谓锁的作用域?锁的作用域就是从锁被获取到其被释放的时间。而且可以选择要获取哪个对象的对象锁。但是如果在使用同步块机制时,如果使用过多的锁也会容易引起死锁问题,同时获取和释放所也有代价,而同步方法,它们所拥有的锁就是该方法所属的类的对象锁,换句话说,也就是this对象,而且锁的作用域也是整个方法,这可能导致其锁的作用域可能太大,也有可能引起死锁,同时因为可能包含了不需要进行同步的代码块在内,也会降低程序的运行效率。而不管是同步方法还是同步块,我们都不应该在他们的代码块内包含无限循环,如果代码内部要是有了无限循环,那么这个同步方法或者同步块在获取锁以后因为代码会一直不停的循环着运行下去,也就没有机会释放它所获取的锁,而其它等待这把锁的线程就永远无法获取这把锁,这就造成了一种死锁现象。

详细解说一下同步方法的锁,同步方法分为静态同步方法与非静态同步方法,先看代码再说理论。以下的案例,如果show方法不是静态的即使被synchronized修饰也无法解决线程问题,大家可以先试一下

class Windows implements Runnable {
     
    private static int ticketNum = 10;
  
    public static synchronized void show(){
     
        String name = Thread.currentThread().getName();
        if(ticketNum > 0) {
     
            try {
     
                //这一步是为了演示错票,原理是当前线程进入了if语句陷入沉睡的时候票被卖光,
                //然后当该线程苏醒时再来一次ticketNum--产生0号这个非法票
                Thread.sleep(100);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(name +"卖出第" + ticketNum + "张票");
            ticketNum--;
        }
    }
    @Override
    public void run() {
     
        while (true){
     
            show();
        }
    }
}

public class ThreadTest{
     
        public static void main(String[] args) {
     
            Windows windows = new Windows();
            Windows windows1 = new Windows();
            Thread thread1 = new Thread(windows);
            thread1.setName("窗口1");
            Thread thread2 = new Thread(windows);;
            thread2.setName("窗口2");
            Thread thread3 = new Thread(windows1);
            thread3.setName("窗口3");
            thread1.start();
            thread2.start();
            thread3.start();
        }
}

运行结果,没有线程安全问题
java synchronized静态同步方法与非静态同步方法,同步语句块_第1张图片
为何show必须是静态同步才能解决线程问题?

1 所有的非静态同步方法用的都是同一把锁——实例对象本身(this,本例中有windows和windows1)也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁(本例中没有体现,大家可以参考我的博客:生产者消费者案例),可是别的实例对象(windows1)的非静态同步方法因为跟该实例对象(windows)的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁,自然无法保证线程安全。

2 而所有的静态同步方法用的也是同一把锁——类对象本身(Windwos.class),这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

同理,看以下代码,我们选择使用Windows.class作为同步锁,可以保证线程安全。但是如果我们还是选用this做同步锁依然无法保证线程安全

class Windows implements Runnable {
     
    private static int ticketNum = 10;
  
    @Override
    public void run() {
     
        String name = Thread.currentThread().getName();
        while (true){
     
            //这里也推荐用this(this代表的是main中的windows对象)或Windows.class
            synchronized (Windows.class){
     
                if(ticketNum > 0) {
     
                    try {
     
                        //这一步是为了演示错票,原理是当前线程进入了if语句陷入沉睡的时候票被卖光,
                        //然后当该线程苏醒时再来一次ticketNum--产生0号这个非法票
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    System.out.println(name + "卖出第" + ticketNum + "张票");
                    ticketNum--;
                }
            }
        }
    }
}

public class ThreadTest{
     
        public static void main(String[] args) {
     
            Windows windows = new Windows();
            Windows windows1 = new Windows();
            Thread thread1 = new Thread(windows);
            thread1.setName("窗口1");
            Thread thread2 = new Thread(windows);;
            thread2.setName("窗口2");
            Thread thread3 = new Thread(windows1);
            thread3.setName("窗口3");
            thread1.start();
            thread2.start();
            thread3.start();
        }
}

对于同步块,由于其锁是可以选择的,所以只有使用同一把锁的同步块之间才有着竞态条件,这就得具体情况具体分析了,但这里有个需要注意的地方,同步块的锁是可以选择的,但是不是可以任意选择的!!!!这里必须要注意一个物理对象和一个引用对象的实例变量之间的区别!使用一个引用对象的实例变量作为锁并不是一个好的选择,因为同步块在执行过程中可能会改变它的值,其中就包括将其设置为null,而对一个null对象加锁会产生异常,并且对不同的对象加锁也违背了同步的初衷!这看起来是很清楚的,但是一个经常发生的错误就是选用了错误的锁对象,因此必须注意:同步是基于实际对象而不是对象引用的!多个变量可以引用同一个对象,变量也可以改变其值从而指向其他的对象,因此,当选择一个对象锁时,我们要根据实际对象而不是其引用来考虑!作为一个原则,不要选择一个可能会在锁的作用域中改变值的实例变量作为锁对象!!!!

你可能感兴趣的:(java核心编程,java,多线程,面试)