多线程之syncronized关键字使用方式总结

syncronized使用说明

1、前提

首先说明下:java中有三大变量:静态变量、成员变量、局部变量

静态变量和成员变量其实都是属于成员变量,所以分成两种类型。

那么一定不会出现问题的是局部变量,因为对于局部变量来说,都是处于线程的线程栈空间中,每个线程都有自己的线程栈,所以局部变量一定不会出现线程安全问题。

使用syncronized关键字的前提就是注意共享对象是哪些线程的共享对象,如果不是多个线程的共享对象,那么多线程之间将不会同步,而是会将导致多线程仍然是异步状态。

上面的线程对象也就是上面的三个变量中的一个,可以进行合理的使用。

下面通过几个案例来进行演示:

2、同步代码块

位置一

/**
 * @Description 使用同步代码块来进行操作,注意共享变量是哪个
 * @Author liguang
 * @Date 2022/03/27/13:56
 */
public class SyncronizedTestString {
    public static void main(String[] args) {
        //创建五个线程来进行执行
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                // 字符串是存在于常量池中的,所以对于多个线程来说共享一份数据,能够保证线程安全
                synchronized ("lig"){
                    try {
                        Thread.sleep(5000);
                        System.out.println("线程睡眠五秒时间之后结束........,当前的线程是:"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }).start();
        }
        System.out.println("当前主线程正在执行......................");
    }
}

通过运行结果,可以看到多线程处于同步状态。多线程之间的共享对象是字符串对象。因为字符串对象是位于常量池中的,对于多线程来说,是可以进行共享的。

位置二

那么对这里来做一个延伸,将共享变量的位置进行提前到main线程中来:

public class SyncronizedTestStringMain {
    public static void main(String[] args) {
        String tmp = "lig";
        //创建五个线程来进行执行
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                // 字符串是存在于常量池中的,所以对于多个线程来说共享一份数据,能够保证线程安全
                synchronized (tmp){
                    try {
                        Thread.sleep(5000);
                        System.out.println("线程睡眠五秒时间之后结束........,当前的线程是:"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }).start();
        }
        System.out.println("当前主线程正在执行......................");
    }
}

位置三

那么再次做一个提升:

public class SyncronizedTestStringClass {
    private static String tmp = "lig";
    public static void main(String[] args) {
        //创建五个线程来进行执行
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                // 字符串是存在于常量池中的,所以对于多个线程来说共享一份数据,能够保证线程安全
                synchronized (tmp){
                    try {
                        Thread.sleep(5000);
                        System.out.println("线程睡眠五秒时间之后结束........,当前的线程是:"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }).start();
        }
        System.out.println("当前主线程正在执行......................");
    }
}

将共享对象提升到成员属性上来

3、同步实例方法

关于同步实例方法,对应的锁对象是当前类的对象,也就是this

demo1

public class Demo1 {

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        // 线程t1和线程t2使用的是同一个共享变量
        MyThread t1 = new MyThread(myTask);
        MyThread t2 = new MyThread(myTask);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    static final class MyThread extends Thread {

        private MyTask myTask;

        public MyThread(MyTask myTask){
            this.myTask = myTask;
        }

        @Override
        public void run() {

            if (Thread.currentThread().getName().equals("t1")){
                // 调用show方法
                myTask.show();
            }
            if (Thread.currentThread().getName().equals("t2")){
                myTask.sleep();
            }
        }
    }
    static class MyTask {
        
        // 加上了synchronized关键字
        public synchronized void show() {
            System.out.println("show method running.............");
            try {
                Thread.sleep(5000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        // 没有synchronized关键字
        public void sleep() {
            System.out.println("sleep method running.................");
        }
    }
}

分析:因为show方法加上了同步关键字,而sleep方法没有同步关键字,所以将会导致对象调用sleep方法不会有同步效果。

demo2

public class Demo2 {

    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        // 线程t1和线程t2使用的是同一个共享变量
        MyThread t1 = new MyThread(myTask);
        MyThread t2 = new MyThread(myTask);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    static final class MyThread extends Thread {

        private MyTask myTask;

        public MyThread(MyTask myTask){
            this.myTask = myTask;
        }

        @Override
        public void run() {

            if (Thread.currentThread().getName().equals("t1")){
                // 调用show方法
                myTask.show();
            }
            if (Thread.currentThread().getName().equals("t2")){
                myTask.sleep();
            }
        }
    }


    static class MyTask {
        public synchronized void show() {
            System.out.println("show method running.............");
            try {
                Thread.sleep(5000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public synchronized void sleep() {
            System.out.println("sleep method running.................");
            try {
                Thread.sleep(2000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

show方法和sleep方法都加上了同步关键字,那么对应的效果就是会导致多线程会有同步效果

demo3

这里情况将不会有同步效果,因为这里的共享对象已经更换了,有了两个MyTask对象,但是线程不共享,所以不会有同步效果:

public class Demo3 {

    public static void main(String[] args) {
        // 两个线程共享对象都不一样,所以不能够来使用同一个对象来进行使用!
        // 这里是不能够这么样来进行操作的
        MyTask myTask1 = new MyTask();
        MyTask myTask2 = new MyTask();
        // 线程t1和线程t2使用的是同一个共享变量
        MyThread t1 = new MyThread(myTask1);
        MyThread t2 = new MyThread(myTask2);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    static final class MyThread extends Thread {

        private MyTask myTask;

        public MyThread(MyTask myTask){
            this.myTask = myTask;
        }

        @Override
        public void run() {

            if (Thread.currentThread().getName().equals("t1")){
                // 调用show方法
                myTask.show();
            }
            if (Thread.currentThread().getName().equals("t2")){
                myTask.sleep();
            }
        }
    }


    static class MyTask {

        public synchronized void show() {
            System.out.println("show method running.............");
            try {
                Thread.sleep(5000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        public synchronized void sleep() {
            System.out.println("sleep method running.................");
            try {
                Thread.sleep(2000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4、同步静态方法

Demo4

那么拿上面的demo3来举例子,那么下面将方法上加上static关键字来进行更换,就又可以导致线程同步:

public class Demo4 {

    public static void main(String[] args) {
        // 两个线程共享对象都不一样,所以不能够来使用同一个对象来进行使用!
        // 这里是不能够这么样来进行操作的
        MyTask myTask1 = new MyTask();
        MyTask myTask2 = new MyTask();
        // 线程t1和线程t2使用的是同一个共享变量
        MyThread t1 = new MyThread(myTask1);
        MyThread t2 = new MyThread(myTask2);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    static final class MyThread extends Thread {

        private MyTask myTask;

        public MyThread(MyTask myTask){
            this.myTask = myTask;
        }

        @Override
        public void run() {

            if (Thread.currentThread().getName().equals("t1")){
                // 调用show方法
                myTask.show();
            }
            if (Thread.currentThread().getName().equals("t2")){
                myTask.sleep();
            }
        }
    }


    static class MyTask {

        public static synchronized void show() {
            System.out.println("show method running.............");
            try {
                Thread.sleep(5000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


        public static synchronized void sleep() {
            System.out.println("sleep method running.................");
            try {
                Thread.sleep(2000);
                System.out.println("show method finished...............");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

5、生产者消费者案例

public class ProducterConsumerTest {

    public static void main(String[] args) {
        List list = new ArrayList();
        new Thread(new Producer(list)).start();
        new Thread(new Consumer(list)).start();
    }


   static class Producer extends Thread{
        private List list;

        public Producer(List list) {
            this.list = list;
        }

        @Override
        public void run() {
            while (true){
                synchronized (list){
                    if (list.size()>0){
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 程序能够执行到这里,那么就一定是被激活了的
                    list.notify();
                    Object obj = new Object();
                    list.add(obj);
                    try {
                        System.out.println("Producer已经投递............"+obj);
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }


    static class Consumer extends Thread{
        private List list;

        public Consumer(List list) {
            this.list = list;
        }

        @Override
        public void run() {
            while (true){
                synchronized (list){
                    if (list.size()==0){
                        try {
                            // 被notify的时候,肯定会来进行唤醒,然后代码继续会向下来进行运行!
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 程序能够执行到这里,那么就一定是被激活了的
                    list.notify();
                    Object remove = list.remove(0);
                    System.out.println("消费者已经消费------>:"+remove);
                    try {
                        // 不会释放当前线程拥有锁的权利
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

6、总结:

怎么样才能够造成线程安全问题?三大条件:多线程、数据共享、多线程修改共享资源

共享数据指的是什么?成员变量:静态变量、成员变量,不可以进行修改的

怎么来进行解决?线程保证同步,常用的使用方式:syncronized和lock锁实现等等。

使用syncronzied关键字关键就在于共享对象是谁?如果和wait已经notify(nofitfyAll)方法结合使用的时候,要使用共享对象来进行通知,才能够达到对应的效果。

posted @ 2021-08-01 18:46  写的代码很烂  阅读(77)  评论(0编辑  收藏  举报