线程同步
什么是线程同步?
我们可以在计算机上运行各种计算机软件程序。每一个运行的程序可能包括多个独立运行的线程(Thread)。 线程(Thread)是一份独立运行的程序,有自己专用的运行栈。线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
同步这个词是从英文synchronize(使同时发生)翻译过来的。我也不明白为什么要用这个很容易引起误解的词。既然大家都这么用,咱们也就只好这么将就。 线程同步的真实意思和字面意思恰好相反。
线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
因此,关于线程同步,需要牢牢记住四点:
- 线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。这可真是个无聊的绕口令。
- “共享”这两个字。只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
- 只有“变量”才需要同步访问。变量需要同步,常量不需要(常量存放于方法区)。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
- 多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。
并发问题
并发:同一个对象被多个线程同时操作
例子:
1.上万人同时抢100张票
如果每个人都买成功了,那么后台票数就会变成负数,出错。
实际生活中也许会显示“你的手慢了”等表示你没有抢到
2.两个银行同时取钱
比如手机和柜台上都可以取钱,你卡上有500万,你想取300万,你媳妇想去500万,你俩同时去取,你媳妇在柜台上取,你在手机上操作的。你们取钱时都发现余额是500万,如果你们两个人都可以取出来,那么银行卡余额就是负的了,这是错误的情况。所以说你和你媳妇一定会形成一个排队的关系,有先后顺序的去取。
3.食堂排队打饭
现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,但窗口是有限的,最天然的解决办法就是:排队 一个个来.
什么时候需要线程同步?
- 处理多线程问题时,多个线程访问同一个对象(即并发问题),并且某些线程还想修改这个对象。这时候我们就需要线程同步。
- 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用(就是排队,一个一个来)。
如何实现线程同步?
队列和锁(synchronized)
实现线程同步:队列 + 锁
队列 + 锁 ------> 同步,解决线程不安全的问题
1. 队列:就是排队
几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
2. 锁 synchronized
每个对象本身都有一把锁(sleep不会释放锁)
比如一个景点里只有一个厕所,很多人都要去上厕所,所有人都在排队上厕所,这就已经形成队列了, 队列要保证安全,比如你进去了,然后如果厕所门没关,那么后面队列里的人就都进去了,这样这个厕所就不安全了。于是乎,厕所门上都会有一把锁,一个人进去了,然后就把门锁上,锁上之后,后面的人就进不去了,前面的人方便完之后,再把锁打开,然后下一个人才能继续使用锁,如此循环,这样才能保证里面安全。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
但是锁存在以下问题:鱼和熊掌不可兼得,性能与安全不可兼得,必定会损失性能。
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁,会导致比较多的上下文切换和调度延时,引起性能问题,损失性能。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能倒置问题。(比如一个想小便的人在等一个大便的人释放锁,本来小便只需要几秒,但现在却要等很久)