携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情
哈喽,大家好!我是Why,一名在读学生,目前刚刚开始进入自己的编程学习生涯。虽然学习起步较晚,但我坚信做了才有0或1的可能。学了一段时间以后也是选择在掘金上分享自己的日常笔记,也希望能够在众多道友的大家庭中打成一片。 本文主要讲解线程安全问题,如果大家读后觉得有用的话,还请大家多多支持博主:欢迎 ❤️点赞👍、收藏⭐、留言💬 ✨✨✨个人主页:JinHuan
线程安全问题
线程实例--什么时候出现线程安全问题
Account
/*
银行账户
不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题。
*/
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;
}
//取款的方法
public void withdraw(double money){
// t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
// 取款之前的余额
double before = this.getBalance(); // 10000
// 取款之后的余额
double after = before - money;
// 在这里模拟一下网络延迟,100%会出现问题
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余额
// 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
this.setBalance(after);
}
}
AccountThread
public class AccountThread extends Thread {
// 两个线程必须共享同一个账户对象。
private Account act;
// 通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的执行表示取款操作。
// 假设取款5000
double money = 5000;
// 取款
// 多线程并发执行这个方法。
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
}
}
Test
public class Test {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act = new Account("act-001", 10000);
// 创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 设置name
t1.setName("t1");
t2.setName("t2");
// 启动线程取款
t1.start();
t2.start();
}
}
线程实例修改--让其变得安全Synchornized(一)
Account
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;
}
//取款的方法
/*
在实例方法上可以使用synchronized吗?可以的。
synchronized出现在实例方法上,一定锁的是this。
没得挑。只能是this。不能是其他的对象了。
所以这种方式不灵活。
另外还有一个缺点:synchronized出现在实例方法上,
表示整个方法体都需要同步,可能会无故扩大同步的
范围,导致程序的执行效率降低。所以这种方式不常用。
synchronized使用在实例方法上有什么优点?
代码写的少了。节俭了。
如果共享的对象就是this,并且需要同步的代码块是整个方法体,
建议使用这种方式。
*/
public synchronized void withdraw(double money){
double before = this.getBalance(); // 10000
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
线程实例修改--让其变得安全Synchornized(二)
Account
public class Account {
// 账号
private String actno;
// 余额
private double balance; //实例变量。
//对象
Object obj = new Object(); // 实例变量。(Account对象是多线程共享的,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){
//int i = 100;
//i = 101;
// 以下这几行代码必须是线程排队的,不能并发。
// 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
/*
线程同步机制的语法是:
synchronized(){
// 线程同步代码块。
}
synchronized后面小括号中传的这个“数据”是相当关键的。
这个数据必须是多线程共享的数据。才能达到多线程排队。
()中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3、t4、t5,有5个线程,
你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个
对象对于t4 t5来说不是共享的。
这里的共享对象是:账户对象。
账户对象是共享的,那么this就是账户对象吧!!!
不一定是this,这里只要是多线程共享的那个对象就行。
在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
100个对象,100把锁。1个对象1把锁。
以下代码的执行原理?
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序。
这样就达到了线程排队执行。
这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队
执行的这些线程对象所共享的。
*/
//Object obj2 = new Object();
//synchronized (this){
//synchronized (obj) {
//synchronized ("abc") { // "abc"在字符串常量池当中。
//synchronized (null) { // 报错:空指针。
//synchronized (obj2) { // 这样编写就不安全了。因为obj2不是共享对象。
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
//}
}
}
AccountThread
public class AccountThread extends Thread {
// 两个线程必须共享同一个账户对象。
private Account act;
// 通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
// run方法的执行表示取款操作。
// 假设取款5000
double money = 5000;
// 取款
// 多线程并发执行这个方法。
//synchronized (this) { //这里的this是AccountThread对象,这个对象不共享!
synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
act.withdraw(money);
}
System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
}
}
Test
public class Test {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act = new Account("act-001", 10000);
// 创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
// 设置name
t1.setName("t1");
t2.setName("t2");
// 启动线程取款
t1.start();
t2.start();
}
}
死锁
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// t1和t2两个线程共享o1,o2
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}