1.什么是线程安全?
1.1为什么有线程安全问题?
当多个线程同时共享同一个全局变脸或静态变量,做写的操作时,可能会发生数据冲突的问题,
也就是线程安全的问题。但是做读操作是不会发生数据冲突问题。
举例:现在有100张火车票,有两个窗口同时抢火车票,用多线程模拟抢票效果。
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.线程安全的解决办法
1 如何解决多线程之间线程安全问题?
使用多线程之间同步synchronized或使用锁(lock)
2 为什么使用线程同步或使用锁能解决线程安全问题呢?
将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程执行。
代码执行完成后释放资源,然后才能让其它线程进行执行。这样就可以解决线程
不安全的问题。
3 什么是多线程之间同步?
当多个线程共享同一个资源,不会受到其它线程的干扰。
4 什么是多线同步?
当多个线程共享同一个资源,不会受到其它线程的干扰。
2.1内置的锁
java提供了一种内置的锁机制来支持原子性。
每个java对象都可以作一个实现同步的锁,成为内置锁。线程进入同步代码之前自动获取到锁
代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁。
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞知道线程A释放锁,线程B才能获取到同一个锁。
内置锁使用synchronized关键字实现,synchronized关键字有两种用法:
1. 修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象调用同步方法的对象
2. 同步代码块和直接使用synconized 修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且
充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活。
2.2同步代码块synchronized
就是将可能会发生线程安全问题的代码给括起来。
synchronized(同一个数据){
//可能要发生线程冲突的代码
}
就是同步代码块
synchronized(对象){//这个对象可以为任意对象
//需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行
没有持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:
1. 必须要有两个或者两个以上的线程
2. 必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处:解决多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
2.3同步方法
2.3.1什么是同步方法呢?
就是在方法上修饰synchronized 成为同步方法。
代码示例:
public synchronized void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
2.3.2同步方法使用的是什么锁呢?
同步方法使用的this锁
证明方式:一个线程使用同步代码块(this 明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,
那么就会出现数据错误。
代码示例:
class Thread009 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) {
// TODO: handle exception
}
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) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
public class Test009 {
public static void main(String[] args) throws InterruptedException {
Thread009 threadTrain = new Thread009();
Thread t1 = new Thread(threadTrain, "窗口1");
Thread t2 = new Thread(threadTrain, "窗口2");
t1.start();
Thread.sleep(40);
threadTrain.flag = false;
t2.start();
}
}
2.3.3静态同步函数
什么是静态同步函数?
方法上加上static关键字,使用synchornized关键字修饰,或使用类.class文件。
静态的同步函数使用的锁是 –>该函数所属字节码文件对象
可以使用 getClass方法获取,也可以使用当前类名.class表示。
代码示例:
public static void sale() {
synchronized (ThreadTrain3.class) {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
}
总结:
synchronized修饰方法使用的锁是 当前的 this锁。
synchronized修饰静态方法使用的锁是当前类的字节码文件。