一、安全问题引入
多线程在操作共享数据或者是操作数据的线程代码有多行,如果有其他线程也参与了运算,就会导致线程的安全问题。
package com.xiaoqiang.interview.thread;
/**
* Created by Huiq on 2021/4/13.
*/
public class ThreadDemo06 {
public static void main(String[] args) {
Bank bank = new Bank();
Runnable runnable = new MoneyThread(bank);
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
class Bank {
private int money = 1000;
public int get(int number) {
if (number < 0) {
System.out.println("不能取负数的钱。。。");
return -1;
} else if (number > money) {
System.out.println("余额不足1。。。");
return -2;
} else if (money < 0) {
System.out.println("余额不足2。。。");
return -3;
} else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money -= number;
System.out.println("还剩:" + money);
return number;
}
}
}
class MoneyThread implements Runnable {
private Bank bank;
public MoneyThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.get(800);
}
}
运行上面的代码,可能出现这三种情况:
二、安全问题解决方法
加锁:synchronized关键字来完成对对象的加锁功能,同一个时间点只能有一个线程访问,只有等这个线程执行完毕或者抛出异常之后,其他的线程才可以进来。
例子二:
package com.xiaoqiang.interview.thread;
/**
* Created by Huiq on 2021/4/13.
*/
public class ThreadDemo07 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable = new TheThread(example);
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized void execute() {
for (int i=0; i<5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Example.execute: " + i);
}
}
}
class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
}
@Override
public void run() {
example.execute();
}
}
没有synchronized的时候运行结果:
有synchronized的时候运行结果:
三、synchronized代码块
synchronized(object):对该object对象上锁
synchronized方法与synchronized代码块的区别:synchronized方法是一种粗粒度的控制,因为它控制的是整个方法,同一时刻只能有一个线程进入到该synchronized方法中。synchronized代码块是一种细粒度的控制,它只是将我们可能存在问题的代码修饰起来了。
四、线程同步拓展
上面的是两个线程对于同一个对象,改成两个线程对两个不同的对象,运行结果是各自管各自的没有任何干扰。
再修改一下代码,思考下运行结果是有顺序还是没有顺序的?
package com.xiaoqiang.interview.thread;
/**
* Created by Huiq on 2021/4/13.
*/
public class ThreadDemo07 {
public static void main(String[] args) {
Example example = new Example();
Runnable runnable1 = new TheThread(example);
Runnable runnable2 = new TheThread2(example);
Thread thread1 = new Thread(runnable1);
Thread thread2 = new Thread(runnable2);
thread1.start();
thread2.start();
}
}
class Example {
public synchronized void execute() {
for (int i=0; i<5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Example.execute: " + i);
}
}
public synchronized void execute2() {
for (int i=0; i<5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Example.execute: " + i);
}
}
}
class TheThread implements Runnable {
private Example example;
public TheThread(Example example) {
this.example = example;
}
@Override
public void run() {
example.execute();
}
}
class TheThread2 implements Runnable {
private Example example;
public TheThread2(Example example) {
this.example = example;
}
@Override
public void run() {
example.execute2();
}
}
答案是顺序的,当两个不同的线程方法对同一个对象的不同synchronized方法,只要哪个先进来它就立马对这个对象上把锁,其他的线程就无法进来了。运行结果为:
五、synchronized与static synchronized的区别
在Java中,synchronized是用来表示同步的,我们可以synchronized来修饰一个方法。也可以synchronized来修饰方法里面的一个语句块。那么,在static方法和非static方法前面加synchronized到底有什么不同呢?大家都知道,static的方法属于类方法,它属于这个Class(注意:这里的Class不是指Class的某个具体对象),那么static获取到的锁,是属于类的锁。而非static方法获取到的锁,是属于当前对象的锁。所以,他们之间不会产生互斥。
举例:
pulbic class Something(){
public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
那么,假如有Something类的两个实例x与y,那么下列各组方法被多线程同时访问的情况是怎样的?
a. x.isSyncA()与x.isSyncB()
b. x.isSyncA()与y.isSyncA()
c. x.cSyncA()与y.cSyncB()
d. x.isSyncA()与Something.cSyncA()
- a. 都是对同一个实例(x)的synchronized域访问,因此不能被同时访问。(多线程中访问x的不同synchronized域不能同时访问)
如果在多个线程中访问x.isSyncA(),因为仍然是对同一个实例,且对同一个方法加锁,所以多个线程中也不能同时访问。(多线程中访问x的同一个synchronized域不能同时访问) - b. 是针对不同实例的,因此可以同时被访问(对象锁对于不同的对象实例没有锁的约束)
- c. 因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与 Something.isSyncB()了,因此不能被同时访问。
- d. 是可以被同时访问的,理由是synchronzied的是实例方法与synchronzied的类方法由于锁不同的原因。也就是synchronized 与static synchronized 各自管各自,相互之间就无约束了,可以被同时访问。
总结:
1.synchronized修饰的普通方法是对对象加锁,修饰的static方法是对类进行加锁。
2.若类对象被锁,则类对象的所有同步方法全被锁。
3.若实例对象被锁,则该实例对象的所有同步方法全被锁。
阿里系的一道笔试题:为什么是这个结果,中间到底间隔了多久才输出
public class ThreadDemo08 {
public static void main(String[] args) {
C c = new C();
Thread t1 = new T1(c);
Thread t2 = new T2(c);
t1.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class C {
public synchronized static void hello() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
public synchronized void world() {
System.out.println("world");
}
}
class T1 extends Thread {
public C c;
public T1(C c) {
this.c = c;
}
@Override
public void run() {
c.hello();
}
}
class T2 extends Thread {
public C c;
public T2(C c) {
this.c = c;
}
@Override
public void run() {
c.world();
}
}