java多线程之线程同步
来源:互联网 发布:做java培训讲师怎么样 编辑:程序博客网 时间:2024/04/30 20:04
线程不同步问题的出现
当处理共享资源的时候,修改数据和读取数据的同时,多线程不同步会出现脏数据,注意脏数据只是数据错处,并不是说代码逻辑有问题,代码本身是没有什么问题的。举个栗子,共享数据为i,i初始值为1,假设线程分为a,b并且线程不同步。
首先假设我们需要做的操作为:
int a = i;a++;i = a;上面代码为两个线程需要执行的代码,当a线程执行代码的时候,局部变量a = i的操作的时候a为1,此时假设还没有执行a++操作,线程b获取资源执行代码,此时b线程对应的局部变量a也为1,然后分别执行a++,然后在赋值,两个线程中的i结果都等于2,我们需要的结果应该是3(至少要一个为3),因为两个线程分别执行这个方法,又因为i是共享资源,所以i应该变为3,由于线程不同步出现了脏数据。有人又会提出质疑,直接i++不久结束了吗?但是java代码执行i++,并不是单纯的执行a++ 一步操作即可,底层是分为多步执行的,既然分步就会有先后也会造成刚刚出现的问题。这个就是非原子性造成的结,这里原子性就是最小的操作·,不能分割了的操作。
接下来我使用代码作为实例演示一下问题具体所在:
public class TestThread {public static void main(String[] args) {Ticket ticket = new Ticket();//创建两个线程子类Customer s1 = new Customer(ticket);Customer s2 = new Customer(ticket);s1.start();s2.start();}}class Ticket{//车票数量private int number = 100000;final Object object = new Object();public int getNumber() {return number;}public void setNumber(int number) {this.number = number;}//买票public void buyTicket() {if(number > 0){number--;}}}class Customer extends Thread{private Ticket ticket;@Overridepublic void run() {for(int i = 0; i < 10000; i++){ticket.buyTicket();System.out.println("线程名"+getName()+" : 剩余火车票"+ticket.getNumber());if(ticket.getNumber() == 0) break;}}public Customer(Ticket ticket) {this.ticket = ticket;}}结果:
线程名Thread-0 : 剩余火车票80064线程名Thread-0 : 剩余火车票80063线程名Thread-0 : 剩余火车票80062线程名Thread-0 : 剩余火车票80061线程名Thread-0 : 剩余火车票80060线程名Thread-0 : 剩余火车票80059线程名Thread-0 : 剩余火车票80058线程名Thread-0 : 剩余火车票80057
这里最终结果为80057,也就是车票还剩这么多,但是我设置的每个线程都会自动买10000张车票,这里开了两个线程,结果应该为80000才对,原因在于线程不同步。
解决线程同步问题简单的分成5种
使用synchronized修饰符
public synchronized void buyTicket() {if(number > 0){number--;}}
其他代码不变,在这个方法上加上一个synchronized,说一下原理,首先java每个对象都有一个内置锁,这里的内置锁是这个类(Ticket),有了这个修饰符就代表,只有这个方法不能同时被多个线程调用,只有其中一个调用完之后才可以让其他线程(包括自己)争夺线程。这时候就可以把整个方法看作一个原子,也就具有了原子性。
使用同步代码块,这里还是使用synchronized
public void buyTicket() {synchronized (object) {if(number > 0){number--;}}}
还是将那块代码改变成这样,这个和上面差不多,但是值得一说的线程同步是非常消耗资源的,如果要在这两种选择的话尽量使用这种,不要使用上面那种。
使用volatile特殊变量
private volatile int number = 100000;//volatile修饰变量
volatile在上述场是没有用的。因为volatile只能保证可见性,可见性就是一个线程修改了一个数据,另一个线程是可见的,但是他不具有原子性,所以在遇到非原子性操作的时候是没有用的。
使用ReentrantLock类
Lock lock = new ReentrantLock();public void buyTicket() {lock.lock();try {if(number > 0){number--;}}finally{lock.unlock();}}这种方法很简单理解,将需要同步的代码上锁就好了。这个和synchronized差不多都堵塞线程,所以其实都是好消耗资源的操作。需要注意的是使用了锁,需要解锁。解锁最好在finally种执行,以免线程导致死锁。
使用Threadlocal类
private ThreadLocal<Integer> number = new ThreadLocal<Integer>() { //初始值设置 @Override protected Integer initialValue() { return 100000; }};public void buyTicket() {if(number.get() > 0){number.set(number.get()-1);}}注意这个已经不是数据共享的问题了。
结果太长不好贴出,描述一下
最后结果:线程名Thread-0 : 剩余火车票90000
最后结果:线程名Thread-1 : 剩余火车票90000
结果就是每个线程都执行了10000遍,但是数据不会相互交互,
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
上面那个栗子不能体现这种用法的好处,接下来我再写一个栗子。
ThreadLocalTest.java
public class ThreadLocalTest implements Runnable{private final static ThreadLocal<User> userTL =new ThreadLocal<>();public static void main(String[] args) {ThreadLocalTest test = new ThreadLocalTest();Thread t1 = new Thread(test,"线程a");Thread t2 = new Thread(test,"线程b");t1.start();t2.start();}@Overridepublic void run() {User user = userTL.get();if(user == null){userTL.set(new User());}System.out.println(userTL.get());user = userTL.get();user.setMoney((int)(Math.random()*1000));for(int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName()+":用户拥有金额"+user.getMoney());}}}
User.java
public class User {private int money;public int getMoney() {return money;}public void setMoney(int money) {this.money = money;}}结果:
线程a:用户拥有金额156线程b:用户拥有金额710线程a:用户拥有金额156线程b:用户拥有金额710线程a:用户拥有金额156线程b:用户拥有金额710线程a:用户拥有金额156线程b:用户拥有金额710线程a:用户拥有金额156线程b:用户拥有金额710
两个用户数据不会因为线程而发生任何交互,这样就达到了线程安全。
- Java多线程之线程同步
- Java多线程之线程同步
- java多线程之 ---- 线程同步
- JAVA多线程之线程同步
- Java多线程之线程同步
- Java多线程之线程同步
- java多线程之线程同步
- java多线程之synchronized(线程同步)
- java多线程之Lock线程同步
- Java多线程之线程的同步
- java 多线程学习笔记之 线程同步
- java多线程之线程间同步通信
- Java多线程之线程同步和死锁
- Java多线程-3 线程同步之synchronized
- java多线程之线程同步问题
- java多线程之线程同步问题
- Java多线程总结之---线程同步
- 五.java多线程之线程同步
- linux下如何安装配置redis及主从配置
- 图片压缩、编辑、剪切
- 婚姻:多么庆幸,没有在最美丽的年华遇见你......
- shell脚本加载数据文件到hive表中
- 简单配置使用Redis
- java多线程之线程同步
- scala的类型系统
- 如何彻底删除hao123的桌面快捷方式
- BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡
- Spark统一内存管理模型UnifiedMemoryManager
- 负载均衡实现原理和方法
- Android内存泄露问题汇总
- 关于在ffmpeg中time.h 和 项目中的time.h 的冲突解决。苹果真坑。
- 进程间通信方式及比较