线程不安全
下方为一个卖票的程序,非线程安全
public class ThreadNoSyncTest implements Runnable {
private int ticket = 100;
@Override
public void run() {
buy();
}
// 线程不安全的
public void buy() {
while (true) {
try {
if (ticket <= 0) {
return;
}
Thread.currentThread().sleep(50);
System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket + " 张票");
ticket--;
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadNoSyncTest test = new ThreadNoSyncTest();
for (int i = 0; i < 5; i++) {
new Thread(test, "demo" + i).start();
}
}
}
/**
* ....
* demo1买到了第 9 张票
* demo4买到了第 9 张票
* demo3买到了第 9 张票
* demo0买到了第 9 张票
* demo2买到了第 5 张票
* demo3买到了第 4 张票
* demo1买到了第 4 张票
* demo4买到了第 4 张票
* demo0买到了第 4 张票
* demo2买到了第 0 张票
* demo4买到了第 -1 张票
* demo1买到了第 -2 张票
* demo3买到了第 -3 张票
* 最终的打印结果有可能会出现同一个票被多个线程买到,也有可能会出现票数为负的情况
* 这就是多线程之间数据共享带来的安全问题
*/
synchronized内置锁
synchronized 锁为互斥锁,也就说同一时刻最多只能有一个线程拿到锁,此锁支持可重入
一个synchronized块有两个部分:锁对象的引用,以及这个锁保护的代码块 锁的对象分为两种:对象类和类锁
import java.util.Random;
public class ThreadSyncTest implements Runnable {
public static void sleep() {
Random random = new Random();
try {
Thread.sleep(random.nextInt(30));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 同步方法 static 类锁 锁对象为 ThreadSyncTest.class
*/
public static synchronized void sycClass() {
for (int i = 0; i < 100; i++) {
sleep();
System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
}
}
/**
* 同步代码块 类锁 锁对象为 ThreadSyncTest.class
*/
public static void sycClass_block() {
synchronized (ThreadSyncTest.class) {
for (int i = 0; i < 100; i++) {
sleep();
System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
}
}
}
/**
* 同步方法 普通方法 对象锁 锁对象为 ThreadSyncTest实例
*/
public synchronized void sycObject() {
for (int i = 0; i < 100; i++) {
sleep();
System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
}
}
/**
* 同步代码块 普通方法 对象锁 锁对象为 ThreadSyncTest实例
*/
public void sycObject_block() {
synchronized (this) {
for (int i = 0; i < 100; i++) {
sleep();
System.err.println(Thread.currentThread().getName() + " static synchronized print " + i);
}
}
}
@Override
public void run() {
/**
* 类锁
*/
sycClass();
sycClass_block();
/**
* 对象锁
*/
sycObject();
sycObject_block();
}
public static void main(String[] args) {
/**
* 测试类锁一个方法
*/
for (int i = 0; i < 3; i++) {
new Thread(new ThreadSyncTest(), "demo" + i).start();
}
/**
* 测试对象锁一个方法
*/
ThreadSyncTest threadSyncTest = new ThreadSyncTest();
for (int i = 0; i < 3; i++) {
new Thread(threadSyncTest, "demo" + i).start();
}
/**
* 测试类锁两个方法
*/
for (int i = 0; i < 3; i++) {
new Thread(() -> new ThreadSyncTest().sycClass()).start();
new Thread(() -> new ThreadSyncTest().sycClass_block()).start();
}
/**
* 测试对象锁两个方法
*/
for (int i = 0; i < 3; i++) {
new Thread(() -> threadSyncTest.sycObject()).start();
new Thread(() -> threadSyncTest.sycObject_block()).start();
}
}
}
结论
- 一个锁的是类对象,一个锁的是实例对象
- 若类对象被锁,则类对象的所有同步方法全被锁
- 若实例对象被锁,则该实例对象的所有同步方法全被锁
- 类锁与对象锁不是同一把锁,多线程可以同时访问类锁与对象锁方法
学习完synchronized关键字之后,我们就可以改造上面不安全的线程,因为要修改同一个对象的变量,所以 我们要用对象锁,只需要修改buy()方法就可以
// 方法1 给buy()方法加上 synchronized 关键字
public synchronized void buy() {
while (true) {
try {
if (ticket <= 0) {
return;
}
Thread.currentThread().sleep(50);
System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket + " 张票");
ticket--;
} catch (Exception e) {
e.printStackTrace();
}
}
}
//方式2 给整个买票步骤同步,放到同步代码块
public void buy() {
while (true) {
try {
synchronized (this) {
if (ticket <= 0) {
return;
}
Thread.currentThread().sleep(50);
System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket + " 张票");
ticket--;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
volatile关键字
适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性
多线程情况下,volatile 无法提供操作的原子性 参考下方例子
import java.util.Random;
public class ThreadVolatileUnSafeTest implements Runnable {
private volatile int a = 0;
@Override
public void run() {
String threadName = Thread.currentThread().getName();
a = a++;
System.out.println(threadName + " before:======" + a);
try {
Random random = new Random();
Thread.sleep(random.nextInt(10));
} catch (Exception e) {
e.printStackTrace();
}
a = a + 1;
System.out.println(threadName + " after:====== " + a);
}
public static void main(String[] args) {
ThreadVolatileUnSafeTest test = new ThreadVolatileUnSafeTest();
for (int i = 0; i < 10; i++) {
new Thread(test, "demo" + i).start();
}
}
}
/**
* demo0 before:======0
* demo1 before:======0
* demo2 before:======0
* demo3 before:======0
* demo4 before:======0
* demo5 before:======0
* demo6 before:======0
* demo7 before:======0
* demo1 after:====== 2
* demo2 after:====== 2
* demo8 before:======2
* demo9 before:======2
* demo4 after:====== 3
* demo0 after:====== 4
* demo5 after:====== 7
* demo6 after:====== 7
* demo9 after:====== 7
* demo3 after:====== 9
* demo8 after:====== 9
* demo7 after:====== 10
*/
ThreadLocal
ThreadLocal类并不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立
public class ThreadLocalTest {
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
super.initialValue();
return 0;
}
};
// 上面代码等价于下面的
// public static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 0);
public static class ThreadDemo implements Runnable {
private int add;
public ThreadDemo(int add) {
this.add = add;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":start");
Integer num = threadLocal.get();//获得变量的值
threadLocal.set(num + add);
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
}
}
public static void main(String[] args) {
threadLocal.set(11);
for (int i = 0; i < 5; i++) {
new Thread(new ThreadDemo(i)).start();
}
System.err.println("main Thread get:" + threadLocal.get());//打印11
}
}