同步

线程安全问题

原因

  • 多个线程操作共享的数据
  • 操作共享数据的线程代码有多条,只有一条不会产生混乱

当一个线程在执行操作共享数据的多条代码中,其他线程参与了运算,就会导致线程安全问题的产生

public class Ticket implements Runnable {

    private int num = 100;

    @Override
    public void run() {
        while(true){
            if(num>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "..." + num--);
            }
        }
    }
}
//会有负数的输出

同步代码块

在Java中用同步代码块可以解决上述问题 synchronized(对象){需要同步的代码}

public class Ticket implements Runnable {

    private int num = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while(true){
            synchronized (obj)
            {
                if(num>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "..." + num--);
                }
            }

        }
    }
}

好处 弊端 前提

同步的好处:解决线程安全问题 同步的弊端:相对以前降低了效率,因为都会判断同步锁 同步的前提:同步中必须有多个线程使用同一个锁,同一个对象数据

同步函数

都是给共享对象的操作加锁,共享对象定义不用加锁

//Bank
public class Bank {
    private int sum;
    public synchronized void add(int num){
        sum = sum + num;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"...sum="+sum);
    }
}
//Customer
public class Customer implements Runnable {
    Bank bank = new Bank();
    @Override
    public void run() {
        for (int i=0;i<3;i++){
            //bank和add是同一条数据,所以bank不用枷锁,必须放外面,因为每次都要new对象
            bank.add(100);
        }

    }
}
//main
Customer cus = new Customer();
Thread t1 = new Thread(cus);
Thread t2 = new Thread(cus);

t1.start();
t2.start();

//Thread-0...sum=100
//Thread-1...sum=200
//Thread-1...sum=300
//Thread-1...sum=400
//Thread-0...sum=500
//Thread-0...sum=600

同步函数和代码块

同步函数的锁是this 同步代码块的锁是任意对象 public synchronized void add synchronized(this){} 上面两行代码可以同步 建议使用同步代码块

静态同步函数使用的锁就是该函数所属的字节码文件对象,可以用对象.getClass()获取,也可以用 类名.class获取 public static synchronized void add synchronized(this.getClass()){}

单例模式的线程同步问题

//饿汉 不存在同步问题
class Single{
	
	static Single single = new Single();
	private Single() {}
	public static Single getInstance() {
		return single;
	}
}
//懒汉 外层加null判断是为了提高效率,下个线程进来不用判断锁
class SingleLazy{
	static SingleLazy single_lazy = null;
	private SingleLazy() {}
	public static SingleLazy getInstance() {
		if(single_lazy==null) {
			synchronized (SingleLazy.class) {
				if(single_lazy==null)
					single_lazy = new SingleLazy();
			}
		}
		return single_lazy;
	}
}

死锁

//DeadLock
public class DeadLock implements Runnable {
    public boolean flag;
    DeadLock(boolean flag){
        this.flag = flag;
    }
    @Override
    public void run() {
        if(flag){
            while(true){
                synchronized (MyLock.oLocka){
                    System.out.println(Thread.currentThread().getName()+"---true---oLocka");
                    synchronized (MyLock.oLockb){
                        System.out.println(Thread.currentThread().getName()+"---true---oLockb");
                    }
                }
            }

        }else{
            while (true){
                synchronized (MyLock.oLockb){
                    System.out.println(Thread.currentThread().getName()+"---false---oLockb");
                    synchronized (MyLock.oLocka){
                        System.out.println(Thread.currentThread().getName()+"---false---oLocka");
                    }
                }
            }

        }
    }
}
//MyLock
public class MyLock {
    public static final Object oLocka = new Object();
    public static final Object oLockb = new Object();
}
//main
DeadLock dl1 = new DeadLock(true);
DeadLock dl2 = new DeadLock(false);
Thread t1 = new Thread(dl1);
Thread t2 = new Thread(dl2);
t1.start();
t2.start();

将嵌套关系改为并列关系就不会导致死锁

public void run() {
    if(flag){
        while(true){
            synchronized (MyLock.oLocka){
                System.out.println(Thread.currentThread().getName()+"---true---oLocka");
            }
            synchronized (MyLock.oLockb){
                System.out.println(Thread.currentThread().getName()+"---true---oLockb");
            }
        }

    }else{
        while (true){
            synchronized (MyLock.oLockb){
                System.out.println(Thread.currentThread().getName()+"---false---oLockb");
            }
            synchronized (MyLock.oLocka){
                System.out.println(Thread.currentThread().getName()+"---false---oLocka");
            }
        }

    }
}

死锁的避免 银行家算法:该算法需要检查申请者对资源的最大需求量, 如果系统现存的各类资源可以满足申请者的请求,就满足申 请者的请求。这样申请者就可很快完成其计算,然后释放它 占用的资源,从而保证了系统中的所有进程都能完成,所以 可避免死锁的发生。(计算资源的大小,计算出来后,永远 按照从大到小的方式来获得锁)

疑问:为什么要用两个对象?一个对象为什么不能实现?

举个例子。注意:票的总数是不变的,不能因为要多线程再多印两张票。同一列车同一个座位有且只有一张票

假如有1000张票要卖

单线程,就好比把一列火车的票集中到一个售票窗口来卖。只要谁想买票必须过来排队,第一个人没有买完之前,第二个人就必须等着。

这样卖1000张【票】第1000个买票的人需要排队好久好久,其实这还是好的,那么你想想第1001个人的心理阴影面积

多线程之后。还是1000张票。但是分开5个窗口来卖,队形一下子少了五分之一。

这就带来了新的问题,假如张三正在买1000号票(最后一张),正掏钱的时候,李四支付成功了把票买走了。那当张三给完钱取票的时候发现票没了(掏钱之前看着还有票),怎么办?这就是多线程中最重要也是最常见的多线程访问共有资源的线程同步问题。

虽然会有上述问题,但是比起一个窗口来说,第1000个买票的人是不是体验好多了呢?以前排队需要3天,现在只需要半天。但是原则上票还得一张一张卖,只是分散到各个窗口售卖。

程序中的真实案例是只有一个变量用来存放票的个数,上述例子中是在买票前就把票分好了,(假如)每个窗口200张


书籍推荐