多线程不同步的问题(线程不安全)
每个线程都有自己的工作空间,当线程要操作数据时,先从内存中把数据复制到自己的工作空间,然后读取数据或者修改数据,最后用新的数据覆盖原来的数据。
一个线程要修改数据,有三个步骤,1.读取数据到工作空间;2.修改工作空间中的数据;3.用工作空间中的数据覆盖内存中的数据。线程是同时运行的,就会带来一个问题,如果一个线程还没把修改好的数据覆盖到内存,另一个线程就读取了内存中的数据,这样显然程序运行出错,这就是线程不安全的问题。
例如抢票问题,取钱问题,容器的写入(CopyOnWriteArrayList类解决了这个问题)。都要一个线程把数据改完再让另一个线程操作。
不同步问题只出现在改数据的时候,只是读数据不会出现不同步问题
并发
同一个对象多个线程同时操作
同步
在现实当中买票,我们不会出现同一张票被买两次的清空,这是因为我们买票的时候是排队买的,并且当前一个人在买的时候,后面的人都要等前一个人买完后再买。解决多线程也是如此,让线程在看到前面要用的数据其他线程在用时,等待前面的线程用完再继续执行。
前面的线程将数据锁住,后面的线程不能用只能等待前面线程用完数据后再用,从而实现数据的同步
synchronized
线程的同步通过synchronized修饰次来实现,可以修饰方法,叫做同步方法;也可以修饰语句块,叫做同步块。(用法在下面)
注:
- 因为在实现数据同步的时候,只让一个线程运行,所以会影响性能,并且应为优先级是先执行的概率,所以会出现线程优先级倒置的问题,即低优先级的线程先运行。
- synchronized锁的是对象,是在内存中的资源。
- synchronized要合理选择范围,太小会导致锁不住数据,太大会影响程序的效率。
同步方法
同步方法的同步监视器是对象本身即this,即对象本身被锁住。
实际中为了提高效率最好不用同步方法,因为锁的范围大。
在“服务器”中用同步方法,在“用户类”中用同步块。自己总结的,不知道对不对。
package ThreadClass;
/**
* 测试线程锁
* @author 王星宇
* @date 2020年2月17日
*/
public class safeThread {
public static void main(String[] args) {
//一份资源
SafeWeb12306 web = new SafeWeb12306();
//多个代理
new Thread(web,"12306").start();
new Thread(web,"美团").start();
new Thread(web,"智行").start();
}
}
class SafeWeb12306 implements Runnable{
private int tickets = 10;
@Override
public void run() {
while(true) {
try {
//模拟网络延迟
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(rob()) continue;
else break;
}
}
public synchronized boolean rob() {
if(tickets <= 0) return false;
System.out.println(Thread.currentThread().getName() + "抢到一张,还有" + tickets-- + "张");
return true;
}
//为了提高性能,用同步块,双重检测(边界检测)
public boolean rob2() {
if(tickets <= 0) return false;
synchronized(this) {
if(tickets <= 0) return false;
System.out.println(Thread.currentThread().getName() + "抢到一张,还有" + tickets-- + "张");
}
return true;
}
}
同步块
同步块的形式:
synchroinzed(obj){}
obj称之为同步监视器
同步监视器执行过程:
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
package ThreadClass;
/**
* 测试同步块
*
* @author 王星宇
* @date 2020年2月17日
*/
public class safeThread02 {
public static void main(String[] args) {
Account aa = new Account(100);
draw you = new draw(aa,0,70);
draw girlfriend = new draw(aa,0,80);
new Thread(you,"你").start();
new Thread(girlfriend,"女朋友").start();
}
}
//账户
class Account{
int money;
public Account (int money) {this.money = money; }
}
//取钱
class draw implements Runnable{
private Account account;//账户
private int poketMoney;//口袋里的钱
private int drawMoney;//取出来的钱
public draw(Account account,int poketMoney,int drawMoney) {
this.account = account;
this.poketMoney = poketMoney;
this.drawMoney = drawMoney;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
drawMoney();
}
//这里加synchronized不能同步
public void drawMoney() {
synchronized(account) {
System.out.println(account.money+ " " + drawMoney);
if(account.money - drawMoney <= 0) {
System.out.println("你的账户没钱了");
return ;
}
account.money -= drawMoney;
poketMoney = drawMoney;
System.out.println(Thread.currentThread().getName() +
"取出了" + drawMoney + "元," +
"账户余额:" + account.money);
}
}
}
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块同时拥有两个以上对象的锁时(锁套锁),就有可能发生“死锁”的问题。
解决方式就是不要锁套锁,让锁并列。