java——线程同步的有关知识
1.并发
同一个对象被多个线程同时操作——买票,买饭,取钱……
现实生活中,排队来解决
程序中利用队列解决。
2.队列和锁
同一个进程的多个线程共享一个储存空间,会产生访问冲突!!,引入锁机制synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在问题
- 性能问题
- 如果一个优先级高的线程等待一个优先级低的线程货导致优先级倒置,引起性能问题。
3.举例
不安全的买票——存在冲突
/**
* 不安全
*/
//多个线程同时操作一个对象
//买火车票——对象车票
//存在线程并发的bug
public class Demo3 implements Runnable{
//票数
int numberTickets = 10;
//线程执行代码
public void run() {
while (numberTickets>0){
//模拟延时
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了票"+numberTickets--);
}
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
//三个人同时抢票
new Thread(demo3,"小明").start();
new Thread(demo3,"小惠").start();
new Thread(demo3,"黄牛党").start();
}
}
存在冲突
不安全的取钱
代码
public class BankN {
public static void main(String[] args) {
Account account = new Account(1000000,"小傅的账户");
Drawing you = new Drawing(account,10000,"XiaoFu");
Drawing girlFriend = new Drawing(account,100000,"girlFriend");
you.start();
girlFriend.start();
}
}
class Account{
int money;//余额
String cardName;//卡名
public Account(int money, String cardName) {
this.money = money;
this.cardName = cardName;
}
}
class Drawing extends Thread{
Account account;//账户
int drawing_money;//取了多少钱
int nowMoney;//手里有多少钱
public Drawing(Account account,int drawing_money,String T_name){
super(T_name);
this.account = account;
this.drawing_money = drawing_money;
}
@Override
public void run() {
if (account.money<drawing_money){
System.out.println(Thread.currentThread().getName()+"钱不够取不了");
return;
}
//模拟网络延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money-drawing_money;//账户减少钱
nowMoney = nowMoney+drawing_money;//手里的钱增加
System.out.println(account.cardName+"余额为:"+account.money);
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
结果
为什么存在冲突
以只剩下最后一张票为例,每个进程都有自己的内存,所以每个人都认为都有票,当买完后发现拿到了票0;
即两个线程在同一时间操作了同一数据。结果不是10000
/**
* 线程是不安全的
*/
public class ThreadUn {
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread());
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
4.实现机制
改进后——通过锁解决冲突(synchronized关键字)
- synchronized方法
- synchronized块
每个对象都有一把锁
缺陷:将一个大方法声明为synchronized执行效率低。
方法里面需要修改的内容才需要加锁。
1)synchronized方法
//synchronized 同步方法——锁的是this
private synchronized void buy() throws InterruptedException {
if (numberTickets<=0){
flag = false;
return;
}
//模拟网络延时
Thread.sleep(200);
//模拟买票
System.out.println(Thread.currentThread().getName()+"拿到了票"+numberTickets--);
}
2) synchronized块
锁的对象应该是增删改查的对象——变化的量
默认锁的是this——所以要给account加锁而不是给run方法加锁。
// synchronized默认锁的是this
public void run() {
// synchronized块
synchronized (account){
if (account.money<drawing_money){
System.out.println(Thread.currentThread().getName()+"钱不够取不了");
return;
}
//模拟网络延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money = account.money-drawing_money;//账户减少钱
nowMoney = nowMoney+drawing_money;//手里的钱增加
System.out.println(account.cardName+"余额为:"+account.money);
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
在run方法体里添加 synchronized块
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
3)java并发包——并发链表
public class CopyOnWriteArrayList<E>
extends Object
implements List<E>, RandomAccess, Cloneable, Serializable
一个线程安全的变量在所有的修改操作 ArrayList
( add
, set
,等等)都是通过底层数组的一个新版本的实现。
这通常是昂贵的,但可能比选择当遍历操作大大超过突变更高效,是有用的当你不能或不想同步遍历,但需要排除并发线程间的干扰。“快照”样式的迭代器方法在创建迭代器时使用指向数组的状态的引用。迭代器的一生在这阵从未改变,所以干扰是不可能的,迭代器是保证不丢ConcurrentModificationException
。迭代器不会反映增加,删除,或更改列表迭代器创建以来。元变更操作对迭代器本身(remove
,set
,和add
)不支持。这些方法把UnsupportedOperationException
。
所有的元素都是允许的,包括null
。
内存一致性效果:与其他并发集合,在将对象放入一个CopyOnWriteArrayList
happen-before行动从另一个线程的CopyOnWriteArrayList
元素的访问和去除之前的线程的行为。
//java实现的线程安全的链表
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
copyOnWriteArrayList.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(copyOnWriteArrayList.size());
}
}
5.死锁问题
一组线程互相持有对方所需的资源(锁),都无法释放。
synchronized (Object){
//代码体
}
synchronized (Object)相当于给Object加锁。
代码体执行完毕后相当于释放锁。
例子
public class TestDeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"灰姑娘");
Makeup g2 = new Makeup(1,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
//需要的资源,用static表示只有一份资源
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;
String girlName;
public Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
//线程运行主体
public void run() {
//化妆
make_up();
}
//互相持有对方的资源
private void make_up(){
if (choice==0){
//获得口红的锁
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//欲获得镜子
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
}
}
}
if (choice==1){
//获得口红的锁
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//欲获得镜子
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
}
解决问题——得到锁之后释放:
//互相持有对方的资源
private void make_up(){
if (choice==0){
//获得口红的锁
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//欲获得镜子
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
}
}
if (choice==1){
//获得镜子的锁
synchronized (mirror){
System.out.println(this.girlName+"获得镜子的锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//欲获得口红
synchronized (lipstick){
System.out.println(this.girlName+"获得口红的锁");
}
}
}
总结:死锁的必要条件