线程同步

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>,相当于每个线程都有一个该变量的副本,每个线程都只操作自己的副本,但是假如该变量所占空间很大,会很浪费空间。
posted @ 2018-11-25 02:18  sagan15  阅读(116)  评论(0编辑  收藏  举报