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

大纲:java线程知识体系

对代码进行同步控制我们可以选择同步方法,也可以选择同步块,这两种方式各有优缺点。同步块不仅可以更加精确的控制对象锁,还可以控制锁的作用域,何谓锁的作用域?锁的作用域就是从锁被获取到其被释放的时间。而且可以选择要获取哪个对象的对象锁。但是如果在使用同步块机制时,如果使用过多的锁也会容易引起死锁问题,同时获取和释放所也有代价,而同步方法,它们所拥有的锁就是该方法所属的类的对象锁,换句话说,也就是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();
        }
}

运行结果,没有线程安全问题
在这里插入图片描述
为何show必须是静态同步才能解决线程问题?

1 所有的非静态同步方法用的都是同一把锁——实例对象本身(this,本例中有windows和windows1),Synchronized修饰非静态方法,实际上是对调用该方法的对象加锁,俗称“对象锁”。也就是说如果一个实例对象的非静态同步方法获取锁(windows对象的锁)后,该实例对象的其他非静态同步方法的执行或者同类的线程(由windows构造的线程)在此执行该同步方法时必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象(windows1)的非静态同步方法因为跟该实例对象(windows)的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁,也就是说windows的锁只对windows(通过windows对象构造的线程)有效而不会影响windows1),自然无法保证线程安全。

2 而所有的静态同步方法用的也是同一把锁——类对象本身(Windwos.class),Synchronized修饰静态方法,实际上是对该类对象加锁,俗称“类锁”。两个不同的对象(windows和windows1)。一旦一个静态同步方法获取锁(Windows.class)后,其他的静态同步方法的执行或者其它类型的线程(由windows1构造的线程)再次调用该静态同步方法时都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

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

class Windows implements Runnable {
    private static int ticketNum = 10;
    private Object obj = new Object();//obj也可以用作同步锁
    @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对象加锁会产生异常
②对不同的对象加锁也违背了同步的初衷!一个经常发生的错误就是选用了错误的锁对象,因此必须注意:同步是基于实际对象而不是对象引用的!不要选择一个可能会在锁的作用域中改变值的实例变量作为锁对象!本例中的obj用作锁对象的话对于整个类的声明周期来说都不会发生改变,synchronized (obj){}等价于synchronized (this){}
③无论是synchronized (obj){}还是synchronized (this){}都只能保证同一个Runnable实现类的对象构造的形参保证线程安全,如果要不同类型(windows1和windows2)的线程保证线程安全,需要使用synchronized (Windwos.class){}来锁住整个类

练习题

题目一

public class Phone {

    public synchronized void sendEamil() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":send email");
    }


    public synchronized void sendCall() {
        System.out.println(Thread.currentThread().getName() + ":send call");
    }

}


class LockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendEamil();
        },"aThread").start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.sendCall();
        },"bThread").start();
    }

}

结果

aThread:send email
bThread:send call

结果分析

执行synchronized同步方法需要从phone对象中获取对象锁,首先aThread线程获取到锁后陷入睡眠,睡眠期间不释放锁,所以bThread去执行sendCall方法时获取不到锁,无法执行,只能等sendEmail方法执行完毕才可继续执行

题目二

public class Phone {

    public synchronized void sendEamil() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":send email");
    }


    public synchronized void sendCall() {
        System.out.println(Thread.currentThread().getName() + ":send call");
    }

    public  void hello() {
        System.out.println(Thread.currentThread().getName() + ":send hello");
    }

}


class LockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendEamil();
        },"aThread").start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone.hello();
        },"bThread").start();
    }

}

结果

bThread:send hello
aThread:send email

结果分析

同理,aThread进入睡眠,期间bThread执行的是普通方法,不受锁的限制

题目三

public class Phone {

    public synchronized void sendEamil() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":send email");
    }


    public synchronized void sendCall() {
        System.out.println(Thread.currentThread().getName() + ":send call");
    }

    public  void hello() {
        System.out.println(Thread.currentThread().getName() + ":send hello");
    }

}


class LockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendEamil();
        },"aThread").start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendCall();
        },"bThread").start();
    }

}


结果

bThread:send call
aThread:send email

结果分析

aThread持有phone对象的对象锁进入睡眠,bThread尝试获取到phone2的对象锁,可获取成功

题目四

public class Phone {

    public static synchronized void sendEamil() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":send email");
    }


    public static synchronized void sendCall() {
        System.out.println(Thread.currentThread().getName() + ":send call");
    }

    public  void hello() {
        System.out.println(Thread.currentThread().getName() + ":send hello");
    }

}


class LockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendEamil();
        },"aThread").start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendCall();
        },"bThread").start();
    }

}


结果

aThread:send email
bThread:send call

结果分析

静态方法的执行需要从class对象中获取对象锁,并非从普通对象获取对象锁,而任何的类的class对象只有一个

题目五

public class Phone {

    public static synchronized void sendEamil() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":send email");
    }


    public static void sendCall() {
        System.out.println(Thread.currentThread().getName() + ":send call");
    }

    public  void hello() {
        System.out.println(Thread.currentThread().getName() + ":send hello");
    }

}


class LockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            phone.sendEamil();
        },"aThread").start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            phone2.sendCall();
        },"bThread").start();
    }

}


结果

bThread:send call
aThread:send email

结果分析

aThread执行静态方法的需要从class对象中获取对象锁,bThread执行普通方法需要从对象phone中获取普通对象获取对象锁,二者锁的来源不会有冲突

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值