并发编程--线程共享

线程不安全

下方为一个卖票的程序,非线程安全

public class ThreadNoSyncTest implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        buy();
    }
	// 线程不安全的
    public void buy() {
        while (true) {
            try {
                if (ticket <= 0) {
                    return;
                }
                Thread.currentThread().sleep(50);
                System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket + " 张票");
                ticket--;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadNoSyncTest test = new ThreadNoSyncTest();
        for (int i = 0; i < 5; i++) {
            new Thread(test, "demo" + i).start();
        }
    }
}
/**
 * ....
 * demo1买到了第 9 张票
 * demo4买到了第 9 张票
 * demo3买到了第 9 张票
 * demo0买到了第 9 张票
 * demo2买到了第 5 张票
 * demo3买到了第 4 张票
 * demo1买到了第 4 张票
 * demo4买到了第 4 张票
 * demo0买到了第 4 张票
 * demo2买到了第 0 张票
 * demo4买到了第 -1 张票
 * demo1买到了第 -2 张票
 * demo3买到了第 -3 张票
 * 最终的打印结果有可能会出现同一个票被多个线程买到,也有可能会出现票数为负的情况
 * 这就是多线程之间数据共享带来的安全问题
 */

synchronized内置锁

synchronized 锁为互斥锁,也就说同一时刻最多只能有一个线程拿到锁,此锁支持可重入
一个synchronized块有两个部分:锁对象的引用,以及这个锁保护的代码块 锁的对象分为两种:对象类和类锁

import java.util.Random;

public class ThreadSyncTest implements Runnable {

    public static void sleep() {
        Random random = new Random();
        try {
            Thread.sleep(random.nextInt(30));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步方法 static 类锁 锁对象为 ThreadSyncTest.class
     */
    public static synchronized void sycClass() {
        for (int i = 0; i < 100; i++) {
            sleep();
            System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
        }
    }

    /**
     * 同步代码块 类锁 锁对象为 ThreadSyncTest.class
     */
    public static void sycClass_block() {
        synchronized (ThreadSyncTest.class) {
            for (int i = 0; i < 100; i++) {
                sleep();
                System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
            }
        }
    }

    /**
     * 同步方法 普通方法 对象锁 锁对象为 ThreadSyncTest实例
     */
    public synchronized void sycObject() {
        for (int i = 0; i < 100; i++) {
            sleep();
            System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
        }
    }


    /**
     * 同步代码块 普通方法 对象锁 锁对象为 ThreadSyncTest实例
     */
    public void sycObject_block() {
        synchronized (this) {
            for (int i = 0; i < 100; i++) {
                sleep();
                System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
            }
        }
    }

    @Override
    public void run() {
        /**
         * 类锁
         */
        sycClass();
        sycClass_block();

        /**
         * 对象锁
         */
        sycObject();
        sycObject_block();
    }


    public static void main(String[] args) {
        /**
         * 测试类锁一个方法
         */
        for (int i = 0; i < 3; i++) {
            new Thread(new ThreadSyncTest(), "demo" + i).start();
        }

        /**
         * 测试对象锁一个方法
         */
        ThreadSyncTest threadSyncTest = new ThreadSyncTest();
        for (int i = 0; i < 3; i++) {
            new Thread(threadSyncTest, "demo" + i).start();
        }

        /**
         * 测试类锁两个方法
         */
        for (int i = 0; i < 3; i++) {
            new Thread(() -> new ThreadSyncTest().sycClass()).start();
            new Thread(() -> new ThreadSyncTest().sycClass_block()).start();
        }

        /**
         * 测试对象锁两个方法
         */

        for (int i = 0; i < 3; i++) {
            new Thread(() -> threadSyncTest.sycObject()).start();
            new Thread(() -> threadSyncTest.sycObject_block()).start();
        }

    }
}

结论

  • 一个锁的是类对象,一个锁的是实例对象
  • 若类对象被锁,则类对象的所有同步方法全被锁
  • 若实例对象被锁,则该实例对象的所有同步方法全被锁
  • 类锁与对象锁不是同一把锁,多线程可以同时访问类锁与对象锁方法

学习完synchronized关键字之后,我们就可以改造上面不安全的线程,因为要修改同一个对象的变量,所以 我们要用对象锁,只需要修改buy()方法就可以

// 方法1 给buy()方法加上 synchronized 关键字
public synchronized void buy() {
    while (true) {
        try {
            if (ticket <= 0) {
                return;
            }
            Thread.currentThread().sleep(50);
            System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket + " 张票");
            ticket--;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//方式2 给整个买票步骤同步,放到同步代码块
public void buy() {
    while (true) {
        try {
            synchronized (this) {
                if (ticket <= 0) {
                    return;
                }
                Thread.currentThread().sleep(50);
                System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket + " 张票");
                ticket--;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

volatile关键字

适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性
多线程情况下,volatile 无法提供操作的原子性 参考下方例子

import java.util.Random;

public class ThreadVolatileUnSafeTest implements Runnable {
    private volatile int a = 0;

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        a = a++;
        System.out.println(threadName + " before:======" + a);
        try {
            Random random = new Random();
            Thread.sleep(random.nextInt(10));
        } catch (Exception e) {
            e.printStackTrace();
        }
        a = a + 1;
        System.out.println(threadName + " after:====== " + a);
    }

    public static void main(String[] args) {
        ThreadVolatileUnSafeTest test = new ThreadVolatileUnSafeTest();
        for (int i = 0; i < 10; i++) {
            new Thread(test, "demo" + i).start();
        }
    }
}

/**
 * demo0 before:======0
 * demo1 before:======0
 * demo2 before:======0
 * demo3 before:======0
 * demo4 before:======0
 * demo5 before:======0
 * demo6 before:======0
 * demo7 before:======0
 * demo1 after:====== 2
 * demo2 after:====== 2
 * demo8 before:======2
 * demo9 before:======2
 * demo4 after:====== 3
 * demo0 after:====== 4
 * demo5 after:====== 7
 * demo6 after:====== 7
 * demo9 after:====== 7
 * demo3 after:====== 9
 * demo8 after:====== 9
 * demo7 after:====== 10
 */

ThreadLocal

ThreadLocal类并不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立

public class ThreadLocalTest {

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            super.initialValue();
            return 0;
        }
    };
    // 上面代码等价于下面的
	// public static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 0);
    
    public static class ThreadDemo implements Runnable {

        private int add;

        public ThreadDemo(int add) {
            this.add = add;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ":start");
            Integer num = threadLocal.get();//获得变量的值
            threadLocal.set(num + add);
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        }
    }

    public static void main(String[] args) {
        threadLocal.set(11);
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadDemo(i)).start();
        }
        System.err.println("main Thread get:" + threadLocal.get());//打印11
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值