线程出现问题的根本原因是因为线程上下文切换,导致线程里的指令没有执行完就切换执行其它线程了
示例
public class Test {
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 1; i < 5000; i++){
count++;
}
});
Thread t2 =new Thread(()->{
for (int i = 1; i < 5000; i++){
count--;
}
});
t1.start();
t2.start();
t1.join(); // 主线程等待t1线程执行完
t2.join(); // 主线程等待t2线程执行完
// main线程只有等待t1, t2线程都执行完之后, 才能打印count, 否则main线程不会等待t1,t2
// 直接就打印count的值为0
System.out.println("count的值是: "+count);
}
}
结果:
count的值是: 261
Process finished with exit code 0
为什么不是0? 字节码的层面进行分析
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i
getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i
单线程执行: count的值不会计算错
出现负数的情况: 一个线程没有完成一次完整的自增/自减(多个指令) 的操作, 就被别的线程进行操作, 此时就会出现线程安全问题
临界区
static int counter = 0;
static void increment()
// 临界区
{
counter++;
}
static void decrement()
// 临界区
{
counter--;
}
竞态条件
多个线程在临界区执行,那么由于代码指令的执行不确定而导致的结果问题,称为竞态条件
为了避免临界区中的竞态条件发生,由多种手段可以达到
现在讨论使用synchronized来进行解决,即俗称的对象锁,它采用互斥的方式让同一时刻至多只有一个线程持有对象锁,其他线程如果想获取这个锁就会阻塞住,这样就能保证拥有锁的线程可以安全的执行临界区内的代码
synchronized语法
synchronized(对象) { // 线程1获得锁, 那么线程2的状态是(blocked)
临界区
}
上面的实例程序使用synchronized后如下,计算出的结果是正确!
public class Test {
static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
// 对临界资源(共享资源的操作) 进行 加锁
synchronized (room) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count的值是: "+counter);
}
}
结果:
count的值是: 0
Process finished with exit code 0
synchronized 加在方法上
加在实例方法上, 锁对象就是对象实例
public class Demo {
//在方法上加上synchronized关键字
public synchronized void test() {
}
//等价于
public void test() {
synchronized(this) {
}
}
}
加在静态方法上, 锁对象就是当前类的Class实例
public class Demo {
//在静态方法上加上synchronized关键字
public synchronized static void test() {
}
//等价于
public void test() {
synchronized(Demo.class) {
}
}
}
其实就是考察synchronized 锁住的是哪个对象, 如果锁住的是同一对象, 就不会出现线程安全问题
1. 锁住同一个对象都是this(e1对象),结果为:1,2或者2,1
public class EightLockTest {
// 锁对象就是this, 也就是e1
public synchronized void a() {
System.out.println("1");
}
// 锁对象也是this, e1
public synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e1.b()).start();
}
}
2. 锁住同一个对象都是this(e1对象),结果为:1s后1,2 或者 2,1s后1
public class EightLockTest {
// 锁对象就是this, 也就是e1
public synchronized void a(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象也是this, e1
public synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e1.b()).start();
}
}
3. a,b锁住同一个对象都是this(e1对象),c没有上锁。结果为:3,1s后1,2 || 2,3,1s后1 || 3,2,1s后1
/**
* Description: 会出现安全问题, 因为前两个线程, 执行run方法时, 都对相同的对象加锁;
* 而第三个线程,调用的方法c, 并没有加锁, 所以它可以同前两个线程并行执行;
* 打印结果顺序为: 分析: 因为线程3和线程1,2肯定是并行执行的, 所以有以下情况
* 3,1s后1,2 || 2,3,1s后1 || 3,2,1s后1
* 至于 1,3,2的情况是不会发生的, 可以先调用到1,但需要sleep一秒.3肯定先执行了
*/
public class EightLockTest {
// 锁对象就是this, 也就是e1
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象也是this, e1
public synchronized void b() {
System.out.println("2");
}
public void c() {
System.out.println("3");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e1.b()).start();
new Thread(() -> e1.c()).start();
}
}
4.a锁住对象this(n1对象),b锁住对象this(n2对象),不互斥。结果为:2,1s后1
public class EightLockTest {
// 锁对象是e1
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象是e2
public synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
EightLockTest e2 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e2.b()).start();
}
}
5.a锁住的是EightLockTest.class对象, b锁住的是this(e1),不会互斥; 结果: 2,1s后1
public class EightLockTest {
// 锁对象是EightLockTest.class类对象
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象是e2
public synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e1.b()).start();
}
}
6.a,b锁住的是EightLockTest.class对象, 会发生互斥; 结果为:2,1s后1 || 1s后1,2
public class EightLockTest {
// 锁对象是EightLockTest.class类对象
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象是EightLockTest.class类对象
public static synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e1.b()).start();
}
}
7.a锁住的是EightLockTest.class对象, b锁住的是this(e1),不会互斥; 结果: 2,1s后1
public class EightLockTest {
// 锁对象是EightLockTest.class类对象
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象是this,e2对象
public synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
EightLockTest e2 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e2.b()).start();
}
}
8.a,b锁住的是EightLockTest.class对象, 会发生互斥; 结果为:2,1s后1 || 1s后1,2
public class EightLockTest {
// 锁对象是EightLockTest.class类对象
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1");
}
// 锁对象是EightLockTest.class类对象
public static synchronized void b() {
System.out.println("2");
}
public static void main(String[] args) {
EightLockTest e1 = new EightLockTest();
EightLockTest e2 = new EightLockTest();
new Thread(() -> e1.a()).start();
new Thread(() -> e2.b()).start();
}
}
成员变量和静态变量的线程安全分析
局部变量线程安全分析
线程安全的情况
public static void test1() {
int i = 10;
i++;
}