并发编程基础03-Synchronized关键字

版权声明:博观而约取,厚积而薄发。 https://blog.csdn.net/BruceLiu_code/article/details/87953945

1.Synchronized锁重入

关键字synchronized拥有锁重入额功能,也就是说在使用synchronized关键字的时候,当一个线程得到一个锁的对象后,再次请求此对象时是课再次得到该对象的锁。如果说在执行锁内容的过程中出现了异常的话,那么锁将会被自动释放。
示例1

/**
 * synchronized的重入
 *
 * @author bruceliu
 * @create 2019-02-26 22:39
 */
public class SynchronizedDemo1 {

    public synchronized void method1() {
        System.out.println("method1.." + Thread.currentThread().getName());
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        method2();
    }

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

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

    public static void main(String[] args) {
        final SynchronizedDemo1 sd = new SynchronizedDemo1();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                sd.method1();
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                sd.method1();
            }
        });
        t1.start();
        t2.start();
    }
}

示例2

/**
 * synchronized的重入
 * @author bruceliu
 * @create 2019-02-26 22:44
 */
public class SynchronizedDemo2 {

    static class Main {
        public int i = 10;
        public synchronized void operationSup(){
            try {
                i--;
                System.out.println(Thread.currentThread().getName()+"====>Main print i = " + i);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class Sub extends Main {
        public synchronized void operationSub(){
            try {
                while(i > 0) {
                    i--;
                    System.out.println(Thread.currentThread().getName()+"====>Sub print i = " + i);
                    Thread.sleep(100);
                    this.operationSup();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        final Sub sub = new Sub();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                sub.operationSub();
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                sub.operationSub();
            }
        });

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

对于文本应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序的业务逻辑产生严重的错误,比如你现在在执行一个队列任务,很多对象都在等待第一个对象正确的执行完毕后再去释放锁,但是第一个对象由于有异常出现,导致业务逻辑没有正常的执行完,释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意和关注。在编写代码的时候,一定要考虑周全。
示例3

/**
 * @author bruceliu
 * @create 2019-02-26 22:51
 */
public class SynchronizedException {

    private int i = 0;

    public synchronized void operation() {
        while (true) {
            try {
                i++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " , i = " + i);
                if (i == 20) {
                    //Integer.parseInt("a");
                    throw new RuntimeException();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {

        final SynchronizedException se = new SynchronizedException();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                se.operation();
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                se.operation();
            }
        }, "t2");


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

2.Synchronized代码块

使用Synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个很长的业务逻辑,那么B线程就必须等待较长的时间才能执行,这样的情况下可以使用Synchronized代码块去优化代码的执行时间,也就是说减小锁的粒度。
示例1

/**
 * 使用synchronized代码块减小锁的粒度,提高性能
 *
 * @author bruceliu
 * @create 2019-02-26 23:14
 */
public class SynchronizedDemo3 {

    public void doLongTimeTask() {
        try {

            System.out.println("当前线程开始:" + Thread.currentThread().getName() + ", 正在执行一个较长时间的业务操作,其内容不需要同步");
            Thread.sleep(2000);

            synchronized (this) {
                System.out.println("当前线程:" + Thread.currentThread().getName() + ", 执行同步代码块,对其同步变量进行操作");
                Thread.sleep(1000);
            }
            System.out.println("当前线程结束:" + Thread.currentThread().getName() + ", 执行完毕");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        final SynchronizedDemo3 otz = new SynchronizedDemo3();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                otz.doLongTimeTask();
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                otz.doLongTimeTask();
            }
        }, "t2");

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

    }


}

Synchronized可以使用任意的Object进行加锁,用法比较灵活。
示例2

/**
 * @author bruceliu
 * 使用synchronized代码块加锁,比较灵活
 * @create 2019-02-26 23:17
 */
public class ObjectLock {

    public void method1(){
        synchronized (this) {	//对象锁
            try {
                System.out.println("do method1..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void method2(){		//类锁
        synchronized (ObjectLock.class) {
            try {
                System.out.println("do method2..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private Object lock = new Object();
    public void method3(){		//任何对象锁
        synchronized (lock) {
            try {
                System.out.println("do method3..");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {

        final ObjectLock objLock = new ObjectLock();
        
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                objLock.method1();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                objLock.method2();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                objLock.method3();
            }
        });

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

需要额外的注意一个问题,在Java中是有常量池缓存的功能的,就是说如果我先声明了一个String str1 = “a”; 再声明一个一样的字符串的时候,取值是从原地址去取的,也就是说是同一个对象。这也就导致了在锁字符串对象的时候,可以会取得意料之外的结果(字符串一样会取得相同锁),下面看一个例子介绍
示例3

/**
 * @author bruceliu
 * @create 2019-02-26 23:21
 * synchronized代码块对字符串的锁,注意String常量池的缓存功能
 */
public class StringLock {


    public void method() {
        //new String("字符串常量")
        synchronized ("字符串常量") {
            try {
                for (int i = 1; i <=10; i++) {
                    System.out.println("当前线程 : " + Thread.currentThread().getName() + "开始");
                    Thread.sleep(1000);
                    System.out.println("当前线程 : " + Thread.currentThread().getName() + "结束");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final StringLock stringLock = new StringLock();

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                stringLock.method();
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                stringLock.method();
            }
        }, "t2");

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

锁对象改变的问题,当时用一个对象进行加锁的时候,要注意对象本身发生改变,那么一旦对象改变,持有的锁就不同,如果对象本身没有发生改变,那么依然是同步的,及时是对象的属性发生了修改。
示例4

/**
 * 同一对象属性的修改不会影响锁的情况
 * @author bruceliu
 * @create 2019-02-26 23:39
 */
public class ModifyLock {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public synchronized void changeAttributte(String name, int age) {
        try {
            System.out.println("当前线程 : " + Thread.currentThread().getName() + " 开始");
            this.setName(name);
            this.setAge(age);

            System.out.println("当前线程 : " + Thread.currentThread().getName() + " 修改对象内容为: "
                    + this.getName() + ", " + this.getAge());

            Thread.sleep(2000);
            System.out.println("当前线程 : " + Thread.currentThread().getName() + " 结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final ModifyLock modifyLock = new ModifyLock();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                modifyLock.changeAttributte("张三", 20);
            }
        }, "t1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                modifyLock.changeAttributte("李四", 21);
            }
        }, "t2");

        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }

}

死锁问题:线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

当然死锁的产生是必须要满足一些特定条件的
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
示例5

/**
 * 死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况
 * @author bruceliu
 * @create 2019-02-26 23:41
 */
public class DeadLock implements Runnable{

    private String tag;
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public void setTag(String tag){
        this.tag = tag;
    }

    public void run() {
        if(tag.equals("a")){
            synchronized (lock1) {
                try {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
                }
            }
        }
        if(tag.equals("b")){
            synchronized (lock2) {
                try {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
                }
            }
        }
    }

    public static void main(String[] args) {

        DeadLock d1 = new DeadLock();
        d1.setTag("a");

        DeadLock d2 = new DeadLock();
        d2.setTag("b");

        Thread t1 = new Thread(d1, "t1");
        Thread t2 = new Thread(d2, "t2");

        t1.start();
        
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

猜你喜欢

转载自blog.csdn.net/BruceLiu_code/article/details/87953945