Synchronized

一. 引言

因代码中从来没有写过该类代码,但是又不能不了解,更不能不会写,为了梦想还是得学下.

--每个人都知道怎样做才是最正确的,但是很少有人去那样做,因为那样做太TM辛苦了!

二. 介绍

synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,,其作用的范围是大括号{}括起来的代码们,作用对象是调用这个代码的对象;

  2. 修饰一个方法,被修饰的方法被称为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象

  3. 修改一个静态的方法,其作用的范围是整个静态方法,其作用的对象是这个类的所有对象

  4. 修改一个类,其作用范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象

三. 修饰一个代码块

1. 一个线程访问一个对象中的synchronized(this)同步代码块时,其他世玉访问的线程将被阻塞.

public class MainTest {

    public static void main(String[] arg) {
        System.out.println("使用关键字");
        SyncThread s = new SyncThread();
        Thread thread1 = new Thread(s,"thread1");
        Thread thread2 = new Thread(s,"thread2");
        thread1.start();
        thread2.start();

    }
}

class SyncThread implements Runnable {

    private static int num;

    public SyncThread() {
        num = 0;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        synchronized (this){
            for (int i = 0 ; i < 5; i++){
                try {
                    System.out.println("线程:"+ Thread.currentThread().getName()+":"+ (num++));
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果

img

可以看出线程是一个先运行,另一个处于阻塞状态,等第一个运行完毕,再开始第二个共10次

再把synchronized关键字这行注释,运行结果

img

可以看两个并发出线程顺序是混乱的,可以得出结论synchronized关键字为了保证代码有序,逐一执行

当两个并发线程thread1和thread2访问同一个对象(SyncThread)中的synchronized代码块时,同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块之后才能执行该代码块.thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象

再来!之前new一个对象现在new两个对象看看

记得

public static void main(String[] arg) {
//        SyncThread s = new SyncThread();
        // 注意这里创建的对象是两个不同的对象
        Thread thread1 = new Thread(new SyncThread(),"thread1");
        Thread thread2 = new Thread(new SyncThread(),"thread2");
        thread1.start();
        thread2.start();
    
    }

运行结果

img

有的guy就发现了。因为创建了两个SyncThread对象syncThread1和syncThread2,线程thread1执行的是thread1对象中的代码,而线程thread2执行的是thread2对象中的代码;我们知道synchronized锁定的是对象,这是会有两把锁分别锁定thread1和对thread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程同时执行

2. 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

重构下代码

多个线程访问synchronized和非synchronized代码块

public class MainTest {

    public static void main(String[] arg) {
        SyncThread s = new SyncThread();
        Thread thread1 = new Thread(s,"thread1");
        Thread thread2 = new Thread(s,"thread2");
        thread1.start();
        thread2.start();

    }
}

class SyncThread implements Runnable {

    private static int num;

    public SyncThread() {
        num = 0;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        if (name.equals("thread1")){
            syncAdd();
        }else {
            add();
        }
    }

    public void syncAdd(){
        synchronized (this){
            // synchronized线程自增
            for (int i = 0 ; i < 5; i++){
                try {
                    // 
                    System.out.println("线程名:"+ Thread.currentThread().getName()+":"+ (num++));
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public void add(){
//        synchronized (this) {
        	// 只做输出
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("线程名:" + Thread.currentThread().getName() + ",num:" + num);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
//        }
    }
}

运行结果

img

一个由synchronized和一个非synchronized可以看出线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞

将add()方法的synchronized打开

img

运行结果

img

偶吼,是不是就懂了

四. 修饰在方法上

Synchronized修饰一个方法很简单,就是在方法返回值类型前加上synchronized即可,和上面说的修饰代码块作用相同,只是作用范围不一样,修饰代码块时大括号括起来的范围,而修饰方法范围是整个函数

比如之前的

    @Override
    public void run() {
        synchronized (this){
            for (int i = 0 ; i < 5; i++){
                try {
                    System.out.println("线程:"+ Thread.currentThread().getName()+":"+ (num++));
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

可以写成,效果是一样的

    @Override
    public synchronized void run() {
        for (int i = 0 ; i < 5; i++){
            try {
                System.out.println("线程:"+ Thread.currentThread().getName()+":"+ (num++));
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

在使用synchronized修饰方法时需要注意以下几点:

(1)synchronized关键字不能继承

虽然可以用synchronized来定义方法,但是syncchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承.如果在父类中的某个方法使用了syncchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以.当然,还可以在子类方法中调用父类中相应的方法,这样子虽然子类中的方法不是同步的,但是子类调用了父类的同步方法,因此子类的方法也就相当于同步了,这两种方式的例子代码如下:

在子类方法中加上synchronized关键字

class TestParent{
	public synchronized void method() { }
}
// 显式的加上synchronized才能同步
class TestChild extends TestParent{
	public synchronized void method() { }
}

在子类方法中调用父类的同步方法

class TestParent {
	public synchronized void method() { }
}
//调用父类的synchronized方法
class TestChild extends TestParent{
	public void method() {
    	super.method();
    }
}

注意: 在定义接口方法时补鞥呢使用synchronized关键字

构造方法不能使用synchronized关键字,但是可以使用synchronized代码块来进行同步

五. 修饰静态方法

synchronized也可修饰一个静态方法,用法如下:

public synchronized static void method(){
	
}

我们知道静态方法是属于类而不属于对象的.同样的, synchronized修饰的静态方法锁定的是这个类的所有对象

public class MainTest {

    public static void main(String[] arg) {
        SyncThread s = new SyncThread();
        Thread thread1 = new Thread(s, "thread1");
        Thread thread2 = new Thread(s, "thread2");
        thread1.start();
        thread2.start();

    }
}

class SyncThread implements Runnable {

    private static int num;

    public SyncThread() {
        num = 0;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        method();
    }

    public synchronized static void method() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println("线程名:" + Thread.currentThread().getName() + ":" + (num++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果

img

虽然是两个线程,但是run中调用了静态方法method(),而静态方法是属于类的,所以两个线程相当于用了同一把锁.这与使用synchronized效果相同

修饰一个类

public class MainTest {

    public static void main(String[] arg) {
        SyncThread s = new SyncThread();
        Thread thread1 = new Thread(s, "thread1");
        Thread thread2 = new Thread(s, "thread2");
        thread1.start();
        thread2.start();

    }
}
class SyncThread implements Runnable {

    private static int num;

    public SyncThread() {
        num = 0;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        method();
    }

    public static void method() {
        // 修饰在类的.class
        synchronized(SyncThread.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("线程名:" + Thread.currentThread().getName() + ":" + (num++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果

img

效果是完全一样的

六. 总结

  1. 无论synchronized关键字加在方法上还是对象上,如果它的作用的对象是非静态的,则它去的的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁

  2. 每个对象只有一个锁阈值想关联谁拿到锁就可以运行他所控制的代码

  3. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以避免无为的同步控制

posted @ 2021-11-08 15:44  Patrick&Star  阅读(62)  评论(0编辑  收藏  举报