Synchronized的使用

Synchronized

Synchronized是Java中解决并发问题的一种常用方法,也是最简单的一种方法。

Synchronized作用

  1. 确保线程互斥的访问同步代码
  2. 保证共享变量的修改能够及时可见
  3. 有效解决重排序问题

Synchronized使用

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

  1. 修饰一个代码块,被修饰的代码称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
  4. 修改一个类,其作用的范围是synchronized后面括起来的部分,作用主的对象是这个类的所有对象
修饰一个代码块
  1. 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
public class Test {
    public static void main(String[] args) {
        SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(syncThread, "thread1");
        Thread thread2 = new Thread(syncThread, "thread2");
        thread1.start();
        thread2.start();

    }

    public static class SyncThread implements Runnable {
        private static int count;

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

}

运行结果:

thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9

当两个并发线程访问同一对象中的synchronized代码时,在同一时刻只能有一个线程执行,另一个线程阻塞,必须等待当前的线程执行完这个代码块后才能执行改代码快。这两个线程是互斥的,因为在执行synchronized代码块时会锁住当前的对象,只有执行完改代码快才能释放该对象锁,下一个线程才能执行并锁定对象

将线程的调用改一下:

public class Test {
    public static void main(String[] args) {
//        SyncThread syncThread = new SyncThread();
//        Thread thread1 = new Thread(syncThread, "thread1");
//        Thread thread2 = new Thread(syncThread, "thread2");
//        thread1.start();
//        thread2.start();

        Thread thread3 = new Thread(new SyncThread(), "thread3");
        Thread thread4 = new Thread(new SyncThread(), "thread4");
        thread3.start();
        thread4.start();

    }

    public static class SyncThread implements Runnable {
        private static int count;

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

}

运行结果:

thread3:0
thread4:1
thread3:2
thread4:3
thread3:4
thread4:5
thread3:6
thread4:7
thread3:8
thread4:9

看到这里会发现这两个线程是同时执行,不是说在一个线程在执行synchronized代码块时其他线程受阻吗?这个时因为synchronized只锁定对象,每个对象只有一个锁,而修改的代码则是创建了两个对象,所以这两个对象互补干扰,会同时执行。

  1. 当一个线程访问对象的一个synchronized(this)同步代码块时,另外一个线程仍然可以访问对象整的非synchronized(this)中的同步代码块。
public class Test {
    public static void main(String[] args) {
        SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(syncThread, "thread1");
        Thread thread2 = new Thread(syncThread, "thread2");
        thread1.start();
        thread2.start();
//
//        Thread thread3 = new Thread(new SyncThread(), "thread3");
//        Thread thread4 = new Thread(new SyncThread(), "thread4");
//        thread3.start();
//        thread4.start();

    }

    public static class SyncThread implements Runnable {
        private static int count;

        private void count() {
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        private void printCount() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ":" + (count));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            if (name.equals("thread1")) {
                count();
            } else if (name.equals("thread2")) {
                printCount();
            }
        }
    }

}

从上面的结果中可以看出,一个线程访问一个对象的synchronized代码块时,别的线程可以访问改对象非synchronized代码而不受阻塞

3.指定给某个对象加锁

public class A {
    private int count;

    public A(int count) {
        this.count = count;
    }

    public void addCount(int add) {
        count += add;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void reduceCount(int reduce) {
        count -= reduce;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public int getCount() {
        return count;
    }
}

public class SyncThread implements Runnable{
    private A a;

    public SyncThread(A a) {
        this.a = a;
    }

    @Override
    public void run() {
        synchronized (a) {
            a.addCount(2);
            a.reduceCount(6);
            System.out.println(Thread.currentThread().getName() + ":" + a.getCount());
        }
    }
}


public class Test1 {
    public static void main(String[] a) {
        SyncThread syncThread = new SyncThread(new A(8));
        for (int i = 0; i < 6; i++) {
            new Thread(syncThread, "Thread" + i).start();
        }
    }
}

运行结果:

第一次运行:

Thread1:4
Thread5:0
Thread4:-4
Thread3:-8
Thread2:-12
Thread0:-16

第二次

Thread0:4
Thread5:0
Thread4:-4
Thread1:-8
Thread3:-12
Thread2:-16

在这个SyncThread中run方法里,用synchronized个A对象加了锁。这是当一个线程访问A对象时,其他试图访问的A对象的线程将会阻塞,知道该线程结束,其他线程才可以执行。也就是谁拿到锁谁就可以运行那个加锁的代码。

当有一个明确的对象作为锁时,就可以用类似的方式写。

当没有明确的对象作为锁时,想一段代码同步时,可以创建一个特殊的对象来充当锁:

class Test implements Runnable{
    private byte[] lock = new byte[0];  // 特殊的instance变量
      public void method(){
         synchronized(lock) {
         // todo 同步代码块
        }
    }

    @Override
    public void run() {
        
    }
}
修饰一个方法

synchronized修饰一个方法很简单,就是在方法的前面加synchronized;synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块时大括号括起来的范围,而修饰方法范围时整个函数。

public void run(){
    synchronized(this){
        
    }
}

public synchronized void run(){
  
}

写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一跟写法二是等价的,都是锁定了整个方法的内容

在用synchronized修饰方法是需要注意:
synchronized关键字不能继承;虽然可以使用synchronized来定义方法,但是synchronized并不属于方法定义的一部分,因此synchronized不能被继承。如果在弗雷方法中使用了synchronized关键字,而在子类中覆盖了这个方法,崽子的这个方法默认是不同步的,而必须显示的在子类的这个方法中加synchronized关键字才可以。不过可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了弗雷的同步方法,因此子类也相当于同步了

//在子类方法中加上synchronized关键字
class Parent{
    publice synchronized void method(){}
}

class Child extends Parent{
    publice synchronized void method(){}
}

//在子类方法中调用父类的同步方法
class Parent{
    publice synchronized void method(){}
}
class Child extends Parent{
    publice void method(){
        super.method();
    }
}
修饰一个静态方法

静态方法是属于类的而不是属于对象的。同样,synchronized修饰的静态方法锁定的是这个类的对象

public class SyncThread1 implements Runnable {
    public static int count;

    public SyncThread1() {
        count = 0;
    }

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

    @Override
    public void run() {
        method();
    }
}

public class Test3 {
    public static void main(String[] a) {
        SyncThread1 syncThread1 = new SyncThread1();
        SyncThread1 syncThread2 = new SyncThread1();
        Thread thread1 = new Thread(syncThread1, "Thread1");
        Thread thread2 = new Thread(syncThread2, "Thread2");
        thread1.start();
        thread2.start();
    }
}

运行结果:

Thread1:1
Thread1:2
Thread1:3
Thread1:4
Thread1:5
Thread2:6
Thread2:7
Thread2:8
Thread2:9
Thread2:10

在这里分别创建了SyncThread的两个对象,但是在两个线程并发执行的时候却保持的线程同步。这是应为在run方法中调用了静态方法,而静态方法是属于类的,所以这两个SyncThread相当一同一把锁。

修饰一个类
//用法
class  clasName{
    public void method(){
        synchronized(className.class){
            
        }
    }
}

将上面的例子做个修改

public class SyncThread1 implements Runnable {
    public static int count;

    public SyncThread1() {
        count = 0;
    }
//    public static synchronized void method() {
//
//        for (int i = 0; i < 5; i++) {
//            try {
//                Thread.sleep(1000);
//                System.out.println(Thread.currentThread().getName() + ":" + (++count));
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
//    }
    public static void method() {
        synchronized (SyncThread1.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ":" + (++count));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    @Override
    public void run() {
        method();
    }
}

public class Test3 {
    public static void main(String[] a) {
        SyncThread1 syncThread1 = new SyncThread1();
        SyncThread1 syncThread2 = new SyncThread1();
        Thread thread1 = new Thread(syncThread1, "Thread1");
        Thread thread2 = new Thread(syncThread2, "Thread2");
        thread1.start();
        thread2.start();
    }
}

运行结果:

Thread1:1
Thread1:2
Thread1:3
Thread1:4
Thread1:5
Thread2:6
Thread2:7
Thread2:8
Thread2:9
Thread2:10

我们会发现这个跟修饰静态方法是一样的。synchronized作用于一个类是,是给这个类加锁,类的所有对象用的是同一把锁。

总结

  1. 无论synchronized关键字加载方法还是对象上,如果它的作用是非静态的,那么它取得的锁是对象;如果synchronized作用的对象是一个静态方法或者一个类,则它取得的锁是类,该类所有的对象都是同一把锁。
  2. 每个对象只有一个锁与之相关联,谁拿的锁谁就可以运行锁住的那段代码
  3. 实现同步时要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值