线程同步
并发:多个线程操作同一个对象
-
上万人抢同一张音乐节票
-
排队去打饭
-
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个一个来
-
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这是hi欧我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程在使用
队列和锁
- 每个对象都有一把锁,
线程同步
形成条件:队列+锁
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入* 锁机制 synchronized*,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁,存在以下问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引其性能问题:
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,因其性能问题
不安全三大案例
不安全抢票
package edu.wzw.TestUnsafe;
public class UnsafeBuySicket {
public static void main(String[] args) {
BuySicket buySicket=new BuySicket();
new Thread(buySicket,"wzw").start();
new Thread(buySicket,"该死的黄牛").start();
new Thread(buySicket,"mofan").start();
}
}
class BuySicket implements Runnable{
private int sicket=10;
Boolean flag=true;
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(flag){
buy();
}
}
public void buy(){
if(sicket==1){
flag=false;
System.out.println("没票了");
// return;
}
System.out.println(Thread.currentThread().getName()+"拿到了"+sicket--);
}
}
银行取钱
package edu.wzw.TestUnsafe;
public class UnsafeBank {
public static void main(String[] args) {
Acount acount=new Acount("学费",10000);
Withdraw withdraw1 = new Withdraw(acount,6000);
Withdraw withdraw2 = new Withdraw(acount,5000);
new Thread(withdraw1,"小张").start();
new Thread(withdraw2,"小王").start();
}
}
class Acount{
int balance;//余额
String name;
public Acount(String name,int balance){
this.balance=balance;
this.name=name;
}
}
class Withdraw implements Runnable{
Acount acount;
int withdrewing;//要取多少钱
public void run() {
//判断有无钱
if(acount.balance-withdrewing<=0){
System.out.println(Thread.currentThread().getName()+"去取"+withdrewing+"发现卡里没钱了");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acount.balance=acount.balance-withdrewing;//银行余额还剩多少
System.out.println("学费还有"+acount.balance);
System.out.println(Thread.currentThread().getName()+"取走了"+withdrewing+"还剩下"+acount.balance);
}
public Withdraw(Acount acount,int withdrewing){
this.acount=acount;
this.withdrewing=withdrewing;
}
}
线程不安全的集合
package edu.wzw.TestUnsafe;
import org.omg.PortableServer.THREAD_POLICY_ID;
import java.util.ArrayList;
import java.util.List;
public class UNsafeList {
public static void main(String[] args) throws InterruptedException {
final List<String> list=new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
public void run() {
list.add(Thread.currentThread().getName());
}
}).start();
}Thread.sleep(1000);
System.out.println(list.size());
}
}
用线程同步的方法解决
-
由于我们可以通过private关键字来保证数据对象只能备方法访问,所以我们只需要针对方法提出一套机制,这套机制就是***synchronized***关键字,他包括两种方法
-
***synchronized***方法
-
***synchronized***块
-
同步方法
public synchronized void method(int args){}
-
-
***synchronized***方法控制“对象”的访问,每个对象对应一把锁,每个***synchronized***方法都必须调用该方法的对象的所才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
- 缺陷:若将一个大的方法声明为 synchronized 将会影响效率
解决不安全抢票
package edu.wzw.TestUnsafe;
public class UnsafeBuySicket {
public static void main(String[] args) {
BuySicket buySicket=new BuySicket();
new Thread(buySicket,"wzw").start();
new Thread(buySicket,"该死的黄牛").start();
new Thread(buySicket,"mofan").start();
}
}
class BuySicket implements Runnable{
private int sicket=10;
Boolean flag=true;
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(flag){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
public synchronized void buy(){
if(sicket<1){
flag=false;
System.out.println("没票了");
return;
}
System.out.println(Thread.currentThread().getName()+"拿到了"+sicket--);
}
}
解决银行取钱
package edu.wzw.TestUnsafe;
public class UnsafeBank {
public static void main(String[] args) {
Acount acount=new Acount("学费",10000);
Withdraw withdraw1 = new Withdraw(acount,6000);
Withdraw withdraw2 = new Withdraw(acount,5000);
new Thread(withdraw1,"小张").start();
new Thread(withdraw2,"小王").start();
}
}
class Acount{
int balance;//余额
String name;
public Acount(String name,int balance){
this.balance=balance;
this.name=name;
}
}
class Withdraw implements Runnable{
Acount acount;
int withdrewing;//要取多少钱
public Withdraw(Acount acount,int withdrewing){
this.acount=acount;
this.withdrewing=withdrewing;
}
public void run() {
//锁的对象是变化的量,需要增删改的对象
synchronized (acount){
//判断有无钱
if(acount.balance-withdrewing<=0){
System.out.println(Thread.currentThread().getName()+"去取"+withdrewing+"发现卡里没钱了");
return;
}
try {//sleep可以放大问题的发生性
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acount.balance=acount.balance-withdrewing;
System.out.println("学费还有"+acount.balance);
System.out.println(Thread.currentThread().getName()+"取走了"+withdrewing+"还剩下"+acount.balance);
}
}
}
解决线程不安全集合.1
package edu.wzw.TestUnsafe;
import org.omg.PortableServer.THREAD_POLICY_ID;
import java.util.ArrayList;
import java.util.List;
public class UNsafeList {
public static void main(String[] args) throws InterruptedException {
final List<String> list=new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
public void run() {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}
}).start();
}Thread.sleep(1000);
System.out.println(list.size());
}
}
测试JUC安全类型的集合
这里如果不休眠的话还是会出现没跑完1000的情况,因为在main方法中输出集合的大小,sleep保证其他线程都结束了在输出大小,不然有可能别的线程还在执行主程序已经跑完了,就输出的大小与实际大小不一致
package edu.wzw.TestUnsafe;
import sun.util.resources.CalendarData;
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> copyOnWriteArrayList=new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
copyOnWriteArrayList.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(100);
System.out.println(copyOnWriteArrayList.size());
}
}