并发编程-多线程线程安全

1.为什么有线程安全问题

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。

/**
 * @Auther: bruceliu
 * @Classname ThreadTrain
 * @Date: 2020/2/21 16:07
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadTrain implements Runnable{

    private int trainCount = 100;

    @Override
    public void run() {
        while (trainCount > 0) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {

            }
            sale();
        }
    }

    public void sale() {
        if (trainCount > 0) {
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
            trainCount--;
        }
    }

    public static void main(String[] args) {
        ThreadTrain threadTrain = new ThreadTrain();
        Thread t1 = new Thread(threadTrain, "①号");
        Thread t2 = new Thread(threadTrain, "②号");
        t1.start();
        t2.start();
    }

}

运行结果:
一号窗口和二号窗口同时出售火车第九九张,部分火车票会重复出售。

结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。

2.线程安全解决办法

问:如何解决多线程之间线程安全问题
答:使用多线程之间同步synchronized或使用锁(lock)。

问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

问:什么是多线程之间同步
答:当多个线程共享同一个资源,不会受到其他线程的干扰。

3.内置的锁

Java提供了一种内置的锁机制来支持原子性,每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁

内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁
内置锁使用synchronized关键字实现

synchronized关键字有两种用法:
1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象
2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活

3.1.同步代码块synchronized

就是将可能会发生线程安全问题的代码,给包括起来。

synchronized(同一个数据){
 可能会发生线程冲突问题
}
就是同步代码块 
synchronized(对象)//这个对象可以为任意对象 
{ 
    需要被同步的代码 
} 

对象如同锁,持有锁的线程可以在同步中执行 ,没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:

1,必须要有两个或者两个以上的线程 
2,必须是多个线程使用同一个锁 

必须保证同步中只能有一个线程在运行

好处:解决了多线程的安全问题 
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。 

代码案例:

/**
 * @Auther: bruceliu
 * @Classname ThreadTrain
 * @Date: 2020/2/21 16:07
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: synchronized同步代码块
 */
public class ThreadTrain implements Runnable{

    private int trainCount = 100;

    @Override
    public void run() {
        while (trainCount > 0) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {

            }
            sale();
        }
    }

    public void sale() {
        synchronized (this) {
            if (trainCount > 0) {
                System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
                trainCount--;
            }
        }
    }

    public static void main(String[] args) {
        ThreadTrain threadTrain = new ThreadTrain();
        Thread t1 = new Thread(threadTrain, "①号");
        Thread t2 = new Thread(threadTrain, "②号");
        t1.start();
        t2.start();
    }
}

3.2.同步方法synchronized

什么是同步方法?
答:在方法上修饰synchronized 称为同步方法

代码案例

/**
 * @Auther: bruceliu
 * @Classname ThreadTrain
 * @Date: 2020/2/21 16:07
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: synchronized同步方法
 */
public class ThreadTrain implements Runnable{

    private int trainCount = 100;

    @Override
    public void run() {
        while (trainCount > 0) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {

            }
            sale();
        }
    }

    public synchronized void sale() {
        if (trainCount > 0) {
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
            trainCount--;
        }
    }


    public static void main(String[] args) {
        ThreadTrain threadTrain = new ThreadTrain();
        Thread t1 = new Thread(threadTrain, "①号");
        Thread t2 = new Thread(threadTrain, "②号");
        t1.start();
        t2.start();
    }

}

同步方法使用的是什么锁?
答:同步函数使用this锁。

证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。

/**
 * @Auther: bruceliu
 * @Classname Thread001
 * @Date: 2020/2/21 16:28
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
class ThreadTest1 implements Runnable {

    private int trainCount = 100;
    private Object oj = new Object();
    public boolean flag = true;

    public void run() {
        if (flag) {
            while (trainCount > 0) {
                synchronized (this) {
                    try {
                        Thread.sleep(10);
                    } catch (Exception e) {
                    }
                    if (trainCount > 0) {
                        System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
                        trainCount--;
                    }
                }

            }
        } else {
            while (trainCount > 0) {
                sale();
            }
        }

    }

    public synchronized void sale() {
        try {
            Thread.sleep(10);
        } catch (Exception e) {
        }
        if (trainCount > 0) {
            System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
            trainCount--;
        }

    }
}

public class Thread001 {
    public static void main(String[] args) throws InterruptedException {

        ThreadTest1 threadTrain = new ThreadTest1();
        Thread t1 = new Thread(threadTrain, "窗口1");
        Thread t2 = new Thread(threadTrain, "窗口2");

        t1.start();
        Thread.sleep(40);

        threadTrain.flag = false;
        t2.start();
    }
}

3.3.静态同步函数

什么是静态同步函数?
答:方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用 getClass方法获取,也可以用当前 类名.class 表示

代码示例:

/**
 * @Auther: bruceliu
 * @Classname ThreadTrain3
 * @Date: 2020/2/21 16:40
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: TODO
 */
public class ThreadTrain3 implements Runnable {
    private static int trainCount = 100;

    @Override
    public void run() {
        while (trainCount > 0) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {

            }
            sale();
        }
    }

    public static void sale() {
        synchronized (ThreadTrain3.class) {
            if (trainCount > 0) {
                System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
                trainCount--;
            }
        }

    }

    public static void main(String[] args) {
        ThreadTrain3 threadTrain = new ThreadTrain3();
        Thread t1 = new Thread(threadTrain, "①号");
        Thread t2 = new Thread(threadTrain, "②号");
        t1.start();
        t2.start();
    }

}

总结:

synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件

4.多线程死锁

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
在这里插入图片描述
经典的“哲学家就餐问题”,5个哲学家吃中餐,坐在圆卓子旁。有5根筷子(不是5双),每两个人中间放一根,哲学家时而思考,时而进餐。每个人都需要一双筷子才能吃到东西,吃完后将筷子放回原处继续思考,如果每个人都立刻抓住自己左边的筷子,然后等待右边的筷子空出来,同时又不放下已经拿到的筷子,这样每个人都无法得到1双筷子,无法吃饭都会饿死,这种情况就会产生死锁:每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。这种情况能避免就避免掉。

synchronzied(A锁){
	synchronized(B锁){
         
    }
}

示例:

public class Chinese extends Thread {

	@Override
	public void run() {
		synchronized (Resouce.daocha) {
			System.out.println(Thread.currentThread().getName()+"说:给我筷子~~");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 想 同步 筷子资源
			synchronized (Resouce.kuaizi) {
				System.out.println(Thread.currentThread().getName()+"说:把刀叉给你~~");
			}
		}
		
	}
}
public class English extends Thread {

	@Override
	public void run() {
		synchronized (Resouce.kuaizi) {
			System.out.println(Thread.currentThread().getName() + "说:给我刀叉~~");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//代表想同步 刀叉资源
			synchronized (Resouce.daocha) {
				System.out.println(Thread.currentThread().getName() + "说:把筷子给你~~");
			}
		}

	}
}
/*
 * 资源
 */
public class Resouce {

	public static String kuaizi="筷子";
	
	public static String daocha="刀叉";
}
/*
 * 测试类 
 */
public class Test {

	public static void main(String[] args) {

		Chinese c = new Chinese();
		c.setName("中国人");
		c.start();

		English e = new English();
		e.setName("美国人");
		e.start();
	}
}

5.Threadlocal

5.1.什么是Threadlocal

ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

void set(Object value)设置当前线程的线程局部变量的值。
•	public Object get()该方法返回当前线程所对应的线程局部变量。
•	public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
•	protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

案例:创建三个线程,每个线程生成自己独立序列号。

/**
 * @Auther: bruceliu
 * @Classname ThreadLocaDemo2
 * @Date: 2020/2/21 16:54
 * @QQ交流群:750190373 (攻城狮大本营)
 * @Description: ThreadLocal线程本地变量
 */
class Res {
    // 生成序列号共享变量
    public static Integer count = 0;

    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        };
    };

    public Integer getNum() {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }
}

public class ThreadLocaDemo2 extends Thread {
    private Res res;

    public ThreadLocaDemo2(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
        }
    }

    public static void main(String[] args) {
        Res res = new Res();
        ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
        ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
        threadLocaDemo1.start();
        threadLocaDemo2.start();
        threadLocaDemo3.start();
    }
}
发布了274 篇原创文章 · 获赞 80 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/BruceLiu_code/article/details/104429545
今日推荐