• 线程同步


    1.synchronized

    下面代码,启动了2个线程,对同一个实例syntest的age变量进行自增操作

    package com.ljj.study;
    
    public class SynTest {
        private int age = 0;
    
        public void add() {
            age++;
        }
    
        public void remove() {
            age--;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        // private Object o = new Object();
    
        private static class SynAddThread implements Runnable {
    
            private SynTest synTest;
            private String name;
    
            public SynAddThread(SynTest synTest, String name) {
                this.synTest = synTest;
                this.name = name;
            }
    
            public void run() {
    
                for (int i = 0; i < 10000; i++) {
                    synTest.add();
    
                }
                System.out.println(name + " addThread age " + synTest.getAge());
    
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            SynTest syntest = new SynTest();
            SynAddThread sat = new SynAddThread(syntest, "t0");
            Thread t0 = new Thread(sat);
    
            SynAddThread sat1 = new SynAddThread(syntest, "t1");
            Thread t1 = new Thread(sat1);
            t0.start();
            t1.start();
        }
    }

    t1 addThread age 148130
    t0 addThread age 156546
    

    多次执行,每一次的结果都不同,而且不符合预期结果。原因和cpu时间片轮转机制,线程工作内存,主内存有关吧,准确原因还不知道。

    为了获得预期结果,我想着给add方法加上synchronized修饰符 多次执行 结果如下

    t0 addThread age 18741
    t1 addThread age 20000

    t1的值符合预期,t0值不符合。原因是synchronized方法一旦被某个线程执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,所以t0线程执行了一次 synTest.add()返回后,就释放了该锁,然后t0线程阻塞在那,2个线程重新争夺该锁。

    所以可以用一个20000大小的数组描述执行顺序[t0,t0,t1,......],10000个t0和10000个t1往这个数组里填充,每执行一次毫无疑问age值+1,age的最终结果就是20000,由于某个线程比较强势提前执行完就先打印结果了,导致最先打印的值大于10000。

    synchronized锁又叫可重入锁,意思是说同一线程外层函数获得锁之后 ,内层函数仍然持有锁,这也就是synchronized修饰的方法的锁被某个线程持有后,其他线程不能访问此对象的所有synchronized修饰的方法的原因。

    public class SynTest {
        private int age = 0;
    
        public synchronized void add(int i) {
            age = age + i;
            System.out.println(Thread.currentThread().getName() + "=========" + age);
            if (age < 100) {
                add(i);
            }
    
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        private Object o = new Object();
    
        private static class SynAddThread implements Runnable {
    
            private SynTest synTest;
            private String name;
    
            public SynAddThread(SynTest synTest, String name) {
                this.synTest = synTest;
                this.name = name;
            }
    
            public void run() {
    
                synTest.add(1);
    
                System.out.println(name + " addThread age " + synTest.getAge());
    
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            SynTest syntest = new SynTest();
            SynAddThread sat = new SynAddThread(syntest, "t0");
            Thread t0 = new Thread(sat);
    
            SynAddThread sat1 = new SynAddThread(syntest, "t1");
            Thread t1 = new Thread(sat1);
            t0.start();
            t1.start();
        }
    }

    打印的结果是只有一个线程在执行0-100的自增,这也就是说这个是可重入锁

    看来synchronized方法还是不能完全符合预期,我想着用同步代码块的方法 修改代码如下

    package com.ljj.study;
    
    public class SynTest {
        private int age = 0;
    
        public synchronized void add() {
            age++;
        }
    
        public void remove() {
            age--;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        private Object o = new Object();
    
        private static class SynAddThread implements Runnable {
    
            private SynTest synTest;
            private String name;
    
            public SynAddThread(SynTest synTest, String name) {
                this.synTest = synTest;
                this.name = name;
            }
    
            public void run() {
                synchronized (synTest.o) {
                    for (int i = 0; i < 10000; i++) {
                        synTest.add();
                    }
                    System.out.println(name + " addThread age " + synTest.getAge());
    
                }
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
            SynTest syntest = new SynTest();
            SynAddThread sat = new SynAddThread(syntest, "t0");
            Thread t0 = new Thread(sat);
    
            SynAddThread sat1 = new SynAddThread(syntest, "t1");
            Thread t1 = new Thread(sat1);
            t0.start();
            t1.start();
        }
    }

    执行结果如下,符合预期。2个线程去争夺synTest.o这个锁,谁争取到就执行后面的代码块

    总结:synchronized方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁。不是说某线程获得了该锁,就一直持有直到线程结束。

    t0 addThread age 10000
    t1 addThread age 20000

    2.volatile--轻量级同步

    不能确保原子性,能确保可见性。

    所谓的确保可见性,就是说每次操作该变量时都会把主存中的该变量刷新到线程内存中。

    不能确保原子性就是说,在做原子性操作时,假如主内存中的该值改变了不能及时的应用于此次操作中。比如volatile int a ; a=a+1这个操作会先从主内存中获取最新的值(可见性) 然后自增,然而在自增过程中a值在主内存中改变了,a=a+1不能同步此值(不能确保原子性)

    public class VolatileUnsafe {
        
        private static class VolatileVar implements Runnable {
    
            private volatile int a = 0;
            
            @Override
            public void run() {
                String threadName = Thread.currentThread().getName();
                a = a+1;
                System.out.println(threadName+":======"+a);
                SleepTools.ms(10);
                a = a+1;
                System.out.println(threadName+":======"+a);
            }
        }
        
        public static void main(String[] args) {
    
            VolatileVar v = new VolatileVar();
    
            Thread t1 = new Thread(v);
            Thread t2 = new Thread(v);
            Thread t3 = new Thread(v);
            Thread t4 = new Thread(v);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    
    }

    上述代码每次执行的结果都是千奇百怪的,所以volatile不能保证线程同步。

    但是在只有一个线程写,多个线程读的场景下,volatile就可以使用。

    3.Thredlocal

    public class UseThreadLocal {
        
        //可以理解为 一个map,类型 Map<Thread,Integer>
        static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {
                return 1;
            }
        };
    
        /**
         * 运行3个线程
         */
        public void StartThreadArray(){
            Thread[] runs = new Thread[3];
            for(int i=0;i<runs.length;i++){
                runs[i]=new Thread(new TestThread(i));
            }
            for(int i=0;i<runs.length;i++){
                runs[i].start();
            }
        }
        
        /**
         *类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
         */
        public static class TestThread implements Runnable{
            int id;
            public TestThread(int id){
                this.id = id;
            }
            public void run() {
                System.out.println(Thread.currentThread().getName()+":start");
                Integer s = threadLaocl.get();//获得变量的值
                s = s+id;
                threadLaocl.set(s);
                System.out.println(Thread.currentThread().getName()+":"
                +threadLaocl.get());
                //threadLaocl.remove();
            }
        }
    
        public static void main(String[] args){
            UseThreadLocal test = new UseThreadLocal();
            test.StartThreadArray();
        }
    }
    Thread-0:start
    Thread-2:start
    Thread-1:start
    Thread-2:3
    Thread-1:2
    Thread-0:1
    ThreadLocal可以理解为 一个map,类型 Map<Thread,Integer>,相当于每个线程都有一个该变量的副本,每个线程都只操作自己的副本,但是假如该变量所占空间很大,会很浪费空间。
  • 相关阅读:
    HDU 1002 大数A+B
    HDU 2066 一个人的旅行(最短路)
    HDU 1869 六度分离(最短路 floyd)
    HDU 1159 Common Subsequence(LCS)
    POJ 3061 Subsequence(尺取法)
    NYOJ 10 skiing(记忆化搜索)
    dedecms添加全站的rss订阅功能
    dedecms artlist读取全站最新文章
    dedecms的title怎么优化?
    DedeCMS提示Maximum execution time of 30 seconds exceeded in解决办法
  • 原文地址:https://www.cnblogs.com/ljjnbpy/p/10014499.html
Copyright © 2020-2023  润新知