java多线程学习 synchronized 关键字的使用

  • synchronized是java 提供的一个并发控制的关键字,作用于对象上.
  • 每个java对象都有一个内部对象锁,通过synchronized可以向指定对象请求获取对象锁,该锁是互斥锁,一个时刻只能有一个线程能获得这把锁,其他请求获得这把锁的线程都进入阻塞状态
  • synchronized提供的锁是可重入的锁
    • 可重入锁实现原理:
      • 每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。
      • 如果同一个线程再次请求这个锁,计数器将递增;
      • 每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。
    • 可重入锁的意义之一在于防止死锁

锁代码块

  • synchronized通过锁住一个实例对象实现同步代码块的,而不是锁住代码块
public class myThread6 implements Runnable{
    private static int count=0;
    @Override
    public void run() {
        synchronized (this){
            /*
            * 这里的this指代的是调用该run方法的当前对象
            * 只有获得当前对象的对象锁,才能执行{}里的内容,也就是同步代码块的内容
            * */
            for (int i = 0; i < 10; i++) {
                try {
                    count++;
                    System.out.println(Thread.currentThread().getName()+":"+count);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        myThread6 mt = new myThread6();
        //这里使用同一个实例对象来创建线程,这样synchronized请求的才是同一把锁
        new Thread(mt,"t1").start();
        new Thread(mt,"t2").start();
    }
}

java多线程学习 synchronized 关键字的使用_第1张图片

锁非静态方法

public class myThread6 implements Runnable{
    private static int count=0;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                countAdd();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void countAdd(){
        /*
        被synchronized修饰的方法变成同步方法
        调用countAdd方法时候相当于
        synchronized (this){
            countAdd();
        }*/
        count++;
        System.out.println(Thread.currentThread().getName()+":"+count);
    }
    public static void main(String[] args) {
        myThread6 mt = new myThread6();
        new Thread(mt,"t1").start();
        new Thread(mt,"t2").start();
    }
}

  • synchronized关键字不能继承。synchronized不属于方法定义的一部分。如果父类某个方法用synchronized修饰,子类继承或者覆盖了这个方法,默认情况下,子类方法不是同步的,除非子类方法也用synchronized修饰或者在重写子类方法,并调用父类方法
public class myThread6 extends FatherThread6{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                //这里直接调用继承自父类的countAdd()方法
                //但在子类中,countAdd()方法是非同步方法
                countAdd();
                System.out.println(Thread.currentThread().getName()+":"+count);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        myThread6 mt = new myThread6();
        new Thread(mt,"t1").start();
        new Thread(mt,"t2").start();
    }
}
class FatherThread6 implements Runnable{
    public static int count=0;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                countAdd();
                System.out.println(Thread.currentThread().getName()+":"+count);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public  synchronized void countAdd(){
        count++;
    }
}
  • synchronized锁定的方法和 非synchronized锁定的方法可以同时执行
public class myThread(){
    //这里定义了同步方法A和非同步方法B
    //假设有两个线程t1,t2(通过同一个实例对象构造)
    //t1线程首先获取锁,执行方法A
    //这是t2线程如果想执行方法A需要等线程t1执行完毕并释放锁
    //但t2线程可以直接执行方法B,不需要等t1释放对象锁,因为方法B没有上锁
    public synchronized void A(){

    }
    public void B(){

    }
}

锁静态方法

  • synchronized修饰静态方法的时候,持有的锁会从对象锁变成类锁,即锁住的是整个类. 实质上synchronized持有的锁还是一个对象锁,只不过这个对象是Class对象.所以持有了Class对象的锁,也就相当于锁住了整个类
    • 关于Class对象:我们编写的每个类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象.
public class myThread6 implements Runnable{
    private static int count=0;
    @Override
    public  void run() {
        for (int i = 0; i < 10; i++) {
            try {
                countAdd();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static synchronized void countAdd(){
        /*
        被synchronized修饰的方法变成同步方法
        调用countAdd方法时候相当于
        synchronized (myThread6.class){
            countAdd();
        }*/
        count++;
        System.out.println(Thread.currentThread().getName()+":"+count);
    }
    public static void main(String[] args) {
        myThread6 mt1 = new myThread6();
        myThread6 mt2 = new myThread6();
        //这里mt1和mt2属于不同的实例对象,
        // 但调用静态同步方法countAdd()的时候,请求的是对象锁是同一把锁
        new Thread(mt1,"t1").start();
        new Thread(mt2,"t2").start();
    }
}

锁升级

为了性能的考虑,如果只有一个线程请求获得锁,synchronized会提供偏向锁,直接在对象头markword(存储对象运行时数据的地方)记录该线程id,达到上锁的目的
如果出现了多个线程争用该锁的情况,偏向锁升级成自旋锁,一个线程获得锁后,其他申请获得该锁的线程不是直接进入阻塞状态,进入自旋等待.
如果其他线程自旋次数的次数过多,自旋锁会升级成重量级锁(直接向操作系统申请一把锁),其他等待获得锁的线程进入阻塞状态,不在占用cpu
ps:hotspot实现里,锁只能升级,不能降级

你可能感兴趣的:(java学习,多线程)