多线程学习笔记(四)--synchronized同步关键字

1.前言

当多个线程同时访问某一个共享资源时,可能会出现执行结果与期待结果不一致的情况,这时候就是"非线程安全"的。解决"非线程安全"可以采用synchronized关键字锁类或锁对象,本文大部分实例来自于《Java多线程编程核心技术》

2.访问对象中实例变量造成"非线程安全"

class ThreadA extends Thread {
    private HasSelfPrivateNum numRef;

    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}

class ThreadB extends Thread {
    private HasSelfPrivateNum numRef;

    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

public class HasSelfPrivateNum {
    private int num = 0;  //②实例变量,存在非线程安全问题

    public synchronized void addI(String username) {
//        int num = 0;  //①方法内的私有变量,线程安全的
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num =" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef);
        athread.start();
        ThreadB bthread = new ThreadB(numRef);
        bthread.start();
    }
}

  • 当我们解开①处注释,将②处注释掉,执行结果如下,证明当变量属于方法中的私有变量时,多个线程同时去访问一个没有同步的方法,不存在线程安全问题

  • 当我们注释掉①处代码,将②处代码解开,执行结果如下,证明当变量属于对象的实例变量,多个线程同时去访问一个没有同步的方法,则会出现线程安全的问题

3.类锁和对象锁

java提供了一种内置的锁机制来支持原子性,每一个java对象都可以用作一个实现同步的锁,称为内置锁,内置锁是互斥锁,即线程A获得锁后,线程B想要获得锁,就需等待线程A释放锁后才能获得,该过程是阻塞的。

加static关键字修饰的方法或变量属于类级别的,当我们在加了static关键字修饰的方法或变量上加synchronized锁,即是类锁,在非静态方法前面是给对象加锁。本实例来自https://blog.csdn.net/zhujiangtaotaise/article/details/55509939

class Task2{
    public synchronized static void doLongTimeTaskA() {
        System.out.println("name = " + Thread.currentThread().getName() + ", begain");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("name = " + Thread.currentThread().getName() + ", end");
    }

    public synchronized static void doLongTimeTaskB() {
        System.out.println("name = " + Thread.currentThread().getName() + ", begain");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("name = " + Thread.currentThread().getName() + ", end");
    }

    public synchronized void doLongTimeTaskC() {

        System.out.println("name = " + Thread.currentThread().getName() + ", begain");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("name = " + Thread.currentThread().getName() + ", end");

    }
}

class Thread1 extends Thread{

    private Task2 mTask2;

    public Thread1(Task2 tk){
        mTask2 = tk;
    }

    @Override
    public void run() {
        mTask2.doLongTimeTaskA();
    }
}

class Thread2 extends Thread{

    private Task2 mTask2;

    public Thread2(Task2 tk){
        mTask2 = tk;
    }

    @Override
    public void run() {
        mTask2.doLongTimeTaskB();
    }
}

class Thread3 extends Thread{

    private Task2 mTask2;

    public Thread3(Task2 tk){
        mTask2 = tk;
    }

    @Override
    public void run() {
        mTask2.doLongTimeTaskC();
    }
}

public class SynObjectOrClass {
    public static void main(String[] args) {
        Task2 mTask2 = new Task2();
        Thread1 ta = new Thread1(mTask2);
        Thread2 tb = new Thread2(mTask2);
        Thread3 tc = new Thread3(mTask2);

        ta.setName("A");
        tb.setName("B");
        tc.setName("C");

        ta.start();
        tb.start();
        tc.start();
    }
}

执行结果:

name = A, begain
name = C, begain
name = A, end
name = B, begain
name = C, end
name = B, end

 多执行几次,出现的结果依旧是A先执行完毕后,B才能执行,本实例中方法A和方法B都是类锁,方法C是对象锁。方法A和方法B是同一种锁--类锁,类锁是对该类中的所有变量和方法都起作用的,因此虽然线程ta和tb执行的是不同的方法,但是因为是类锁,所以需要等待,出现了阻塞,线程tc调用的是对象锁,与方法A和方法B的锁类型是不一样的,因此可以异步执行。

4.多个对象多个锁

 多个对象多个锁针对的是多个线程来讲,当多个线程去访问同一个加锁的方法时,synchronized取得的锁是对象锁,而不是把一段代码或方法当做锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁,其他线程只能等待已经获得锁的对象释放锁。如果多个线程访问的是多个对象,那么JVM会创建多个锁。如下实例:

/**
 * 多个对象,多把锁
 */
public class MultiThread {
    private static int num = 0;

    /**
     * 不加static修饰,则会出现多个对象多把锁
     * 加上static修饰,则将锁加在了类级别上,同一个时刻只有一个对象进入临界区
     * @param tag
     */
    public synchronized void printNum(String tag){
        try{
            if(tag.equals("a")){
                num =100;
                System.out.println("tag a ,set num over!");
                Thread.sleep(1000);
            }else {
                num =200;
                System.out.println("tag b ,set num over!");
            }
            System.out.println("tag " + tag + ", num = " + num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final MultiThread m1 = new MultiThread();
        final MultiThread m2 = new MultiThread();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                m1.printNum("a");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m2.printNum("b");
            }
        });

        t1.start();
        t2.start();
    }
}

执行结果:

tag a ,set num over!
tag b ,set num over!
tag b, num = 200
tag a, num = 200

虽然线程t1和线程t2最终调用的都是方法printNum,但此时,printNum是对象锁,因此线程t1和线程t2中分别获得是两个对象的锁,所以执行结果是异步的,如果printNum方法加上static关键字修饰,则线程t1和线程t2获取的将是类锁,结果只能同步,执行结果如下:

tag a ,set num over!
tag a, num = 100
tag b ,set num over!
tag b, num = 200

5.重入锁

 当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。锁重入的前提是对一个线程来讲。

/**
 * synchronized锁重入
 */
class Service{
    synchronized public void service1(){
        System.out.println(Thread.currentThread().getName() + "service1");
        service2();
    }

    synchronized public void service2(){
        System.out.println(Thread.currentThread().getName() + "service2");
        service3();
    }

    synchronized public void service3(){
        System.out.println(Thread.currentThread().getName() + "service3");
    }
}

public class synLockIn extends Thread{
    @Override
    public void run(){
        Service service = new Service();
        service.service1();
    }

    public static void main(String[] args) {
        synLockIn t = new synLockIn();
        //synLockIn t2 = new synLockIn();   //1
        t.start();
        //t2.start();    //2
    }

}

 执行结果如下:

Thread-0service1
Thread-0service2
Thread-0service3
证明t线程获得service对象的锁后,访问代码块中其他加锁方法时依旧可以得到对象锁,继续执行,这是针对一个线程来讲的,当多个线程时,一个线程获得锁,其余线程就需等待了。

6.同步代码块

 当我们锁整个方法时,假设在共享区上需要执行一个很长时间的业务,这时候锁住方法会降低效率,一个线程获得对象锁后其他都都需要等待。这时候能提高效率的简单方法是减小锁的粒度,将锁整个方法减小到锁一个代码块。


7.锁任意对象

 上述举的实例都是锁定对象或类的,即synchronized(this),将任意对象作为对象监视器,定义一个类变量即可。synchronized(非this)

总结

  •  synchronized同步方法对其他同步方法或synchronized(this)同步代码块调用时阻塞状态
  • 同一时间只有一个线程可以执行synchronized同步方法的代码
  • 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
  • 在多个线程持有"对象监视器"为同一个对象的前提下,同一时间只有一个线程可执行synchronized(非this)同步代码块中的代码

猜你喜欢

转载自blog.csdn.net/zh15732621679/article/details/80159821