java线程同步以及对象锁和类锁解析,多线程synchronized关键字

一、关于线程安全

1.是什么决定的线程安全问题?

  线程安全问题基本是由全局变量静态变量引起的。

  若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

2.可以解决多线程并发访问资源的方法有哪些?

  主要有三种方式:分别是同步代码块 、同步方法和锁机制(Lock)

  其中同步代码块和同步方法是通过关键字synchronized实现线程同步

本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁

二、synchronized关键字各种用法与实例

  事实上,synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。

synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。

因此,事实上synchronized关键字可以细分为上面描述的五种用法。

1.同步块synchronized (this)

public class Ticket1 extends Thread{
    
    private int nums = 0; //出票数
    private int count =20; //剩余

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if(count <= 0) {
                    break;
                }
                nums++;
                count--;
                try {
                    Thread.sleep(550);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("显示出票信息:"+Thread.currentThread().getName()+
                "抢到第"+nums+"张票,剩余"+count+"张");
            }
        }
    }
    

    public static void main(String[] args) {
        Ticket1 ticket1 = new Ticket1();
        Thread anni = new Thread(ticket1,"安妮");
        Thread jack = new Thread(ticket1,"jack");
        anni.start();
        jack.start();

    }

}

这样是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是Ticket1 实例对象的对象锁了。

需要注意的是synchronized (){}的{}前后的代码依旧是异步的

2.synchronized (非this对象)的用法锁的是对象

例1:

public class Ticket2 implements Runnable{

        private int nums = 0; //出票数
        private int count =25; //剩余
        //实现线程安全三种方法
        private final Object lock = new Object();//1.使用私有不变对象锁,使得攻击者无法获取到锁对象(推荐使用)
        
        @Override
        public void run() {
                while (true) {
                        //2.this 使用对象自身的锁(隐式锁)--对象锁
                        //3.Ticket2.class 给Ticket2加锁 --类锁->使用说明:静态方法则一定会同步,非静态方
                        //法需在单例模式才生效(本例为单例),但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。
                        synchronized (lock) {
                                if(count <= 0) {
                                        break;
                                }
                                nums++;
                                count--;
                                try {
                                        Thread.sleep(750);
                                } catch (Exception e) {
                                        e.printStackTrace();
                                }
                                System.out.println("显示出票信息:"+Thread.currentThread().getName()+
                                "抢到第"+nums+"张票,剩余"+count+"张");
                        }
                }
        }
        
        public static void main(String[] args) {
                Ticket2 ticket2 = new Ticket2();
                Thread zhangsan = new Thread(ticket2,"张三");
                Thread zhaoyun = new Thread(ticket2,"赵云");
                zhangsan.start();
                zhaoyun.start();
        }

}

例2:

public class Run2 {

    public static void main(String[] args) {

        Service service = new Service("xiaobaoge");

        ThreadA2 a = new ThreadA2(service);
        a.setName("A");
        a.start();

        ThreadB2 b = new ThreadB2(service);
        b.setName("B");
        b.start();

    }

}

class Service {

    String anyString = new String();

    public Service(String anyString){
        this.anyString = anyString;
    }

    public void setUsernamePassword(String username, String password) {
        try {
            synchronized (anyString) {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入同步块");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开同步块");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

class ThreadA2 extends Thread {
    private Service service;

    public ThreadA2(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("a", "aa");

    }

}


class ThreadB2 extends Thread {

    private Service service;

    public ThreadB2(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.setUsernamePassword("b", "bb");

    }

}

3.synchronized (class)

public class Run {

    public static void main(String[] args) {

        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();

    }

}
class Service {

    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "进入printA");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void printB() {
        synchronized (Service.class) {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "进入printB");
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "离开printB");
        }
    }
}

4.静态synchronized同步方法  

public class Run {

    public static void main(String[] args) {

        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();

    }

}

class Service {

    synchronized public static void printA() {
        try {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + "在" + System.currentTimeMillis() + "离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
                + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
                + System.currentTimeMillis() + "离开printB");
    }

}


class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }

}


class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}

5.synchronized修饰非静态方法

public class Run {

    public static void main(String[] args) {

        HasSelfPrivateNum numRef = new HasSelfPrivateNum();

        ThreadA athread = new ThreadA(numRef);
        athread.start();

        ThreadB bthread = new ThreadB(numRef);
        bthread.start();

    }

}
class HasSelfPrivateNum {

    private int num = 0;

    synchronized public void addI(String username) {
        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) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
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");
    }

}

实验结论:两个线程访问同一个对象中的同步方法是一定是线程安全的。本实现由于是同步访问,所以先打印出a,然后打印出b

这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。