1.什么情况下数据在多线程并发下会存在安全问题?
三个条件:
1)多线程并发
2) 有共享数据
3) 共享数据有修改的行为
满足以上三个条件后,就会存在线程安全问题
例如,多线程账户对同一个账户取款
2. 怎么解决线程安全问题?
线程排队执行。(不能并发)
用排队执行就解决线程安全问题。
这种机制被称为:线程同步机制。
这就是并发带来的问题,有可能会导致数据不安全。
线程同步就是线程排队,线程排队会牺牲一部分效率。
3. 同步编程模型 :线程排队执行。 同步就是排队
异步编程模型:多线程并发(效率较高)。 异步就是并发
4. 举例 银行取款
1)定义账户类 Account
package leetcode0606;
public class Account {
//账户
private String actno;
//余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
// 多线程并发这个方法,t1和t2 是两个栈,两个栈操作堆中的同一个对象
public void withdraw(double money){
//取款之前的余额
double before = this.getBalance();
//取款之后的余额
double after = before - money;
//更新余额
// 若来不及更新,那么就会产生问题
// 利用sleep模拟网络延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
2)”取款的线程” 类 AccountThread
package leetcode0606;
public class AccountThread extends Thread{
//如何做到多线程共享同一个账户
private Account act;
public AccountThread(Account act){
this.act = act;
}
@Override
public void run() {
// run方法的执行表示取款操作
//设置取款的金额
double money = 5000;
// 进行取款
// 多线程并发这个方法
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"账户"+act.getActno()+"取款"+money+",余额"+act.getBalance());
}
}
3)测试类 Test
public class Test {
public static void main(String[] args) {
// 创建账户对象
Account act = new Account("act-001",10000);
// 创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
5.利用线程同步机制解决线程安全问题
synchronized(){
//线程同步代码块
}
synchronized 后面的参数很重要,必须是多线程的共享数据,才能达到排队的效果。
()中写什么?那要看你想让谁排队
如果有t1 t2 t3 t4 t5 五个线程,如果你想让前三个排队,那么共享的对象一定是前三个线程共享的,后两个不共享的。
此问题 中的共享对象是账户对象,所以写this
public void withdraw(double money){
//以下的代码必须是线程排队的,不能并发
synchronized(this){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
t1账户act-001取款5000.0,余额5000.0
t2账户act-001取款5000.0,余额0.0
6. 对synchronized的理解
1)争抢对象锁
这个线程能否运行代码块,就看能不能抢到锁(每个对象只有一把锁)
一种锁机制(排他锁)。一个线程遇到synchronized就会去找这个共享对象的锁(每一个java对象都有一把锁),一个线程会占有这把锁,直到执行完同步代码块(执行完后归还锁)。另外一个线程也会执行这段代码,也会去找锁,但是没有找到,所有只能等待。类似于茅坑拉屎关门上锁的例子。
另一种写法:
package leetcode0606.ThreadSafe1;
public class Account {
//账户
private String actno;
//余额
private double balance;
Object obj = new Object(); // 实例变量. 由于Account是共享的,所以对象中的obj也是共享的,也是只有一份
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void withdraw(double money){
//以下的代码必须是线程排队的,不能并发
// synchronized(this){
synchronized(obj){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
挖坑:
由于obj2是局部变量,不是共享变量,所以还是会有安全问题
public void withdraw(double money){
Object obj2 = new Object();
//以下的代码必须是线程排队的,不能并发
// synchronized(this){
synchronized(obj2){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
使用字符串常量:
synchronized("abc"){}
字符串常量池则存在于方法区,故是共享数据
参考:彻底搞懂String:字符串常量池_weixin_42073629的博客-CSDN博客_java string 常量池
但是这样所有的线程都要排队。
2)争抢类锁
7.java 的三大变量
实例变量: 堆
静态变量 : 方法区
局部变量: 在栈中
以上三大变量中,局部变量没有线程安全问题,不会被共享。
常量没有线程安全问题,因为不可修改。
8. 同步代码块的范围:
同步代码块越小,效率越高。
9.作用在实例方法上
public synchronized void withdraw(double money){
//取款之前的余额
double before = this.getBalance();
//取款之后的余额
double after = before - money;
//更新余额
// 若来不及更新,那么就会产生问题
// 利用sleep模拟网络延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
出现在实例方法上,锁的是this,这种方式不灵活。
整个方法都需要同步,效率较低。
但是代码少。。。。
================================================================
如果使用局部变量的话,建议使用StringBuilder.
StringBuffer 底层的实例方法都是加了synchronized的。
ArrayList 非线程安全
Vector 线程安全
HashMap HashSet 非线程安全
Hashtable 线程安全
加了synchronized关键字,就算不需要等待,也要去锁池里看一下,很麻烦的,所以拖慢了效率。
10 . 三种写法
代码块
实例方法
在静态方法上:找类锁,类锁只有一把,与对象数量无关。类锁保证了静态变量的安全,前两类保证了实例变量的安全。
11.面试题1 : doOther方法需要等待doSome结束在执行吗?
不需要,因为doOther方法没有被synchronized修饰。
package leetcode0606.exam;
public class Exam1 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
MyThread myThread1 = new MyThread(myClass);
MyThread myThread2 = new MyThread(myClass);
myThread1.setName("t1");
myThread2.setName("t2");
myThread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
doSome begin
doOther begin
doOther over
11.面试题1 : doOther方法需要等待doSome结束在执行吗?doOther加了synchronized
需要。
package leetcode0606.exam2;
public class Exam2 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
MyThread myThread1 = new MyThread(myClass);
MyThread myThread2 = new MyThread(myClass);
myThread1.setName("t1");
myThread2.setName("t2");
myThread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread2.start();
}
}
class MyThread extends Thread{
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
doSome begin
doSome over
doOther begin
doOther over
13.面试题3
MyClass myClass = new MyClass();
MyClass myClass1 = new MyClass();
MyThread myThread1 = new MyThread(myClass);
MyThread myThread2 = new MyThread(myClass1);
并发了。
14.面试题4: 静态方法加synchronized
需要等
class MyClass{
public synchronized static void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized static void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
doSome begin
doSome over
doOther begin
doOther over



