- 多个线程操作同一个对象 叫
同步
- 同一个对象被多个线程同时操作 叫–
并发
- 多个线程需要等待,
等待机制就是线程同步,线程同步是一种等待机制
,线程都进入这个对象的等待池形成队列,等待前一个线程使用完毕 - 不仅需要队列还需要锁保证线程同步的安全性
银行取钱
public class Unsafe {
public static void main(String[] args) {
Account account=new Account("银行卡",100);
YinHang hus=new YinHang(account,50,"hus");
YinHang wife=new YinHang(account,100,"wife");
hus.start(); wife.start();
}
}
class Account{
public Account(String name, int money) {
this.name = name;
this.money = money;
}
String name;
int money;
}
class YinHang extends Thread
{
public YinHang(Account account,int takeMoney,String name) {
this.account = account;
this.takeMoney = takeMoney;
this.name=name;
}
String name;
Account account;
int takeMoney;
int nowMoney=0;
@Override
public void run() {
if(account.money<takeMoney)
{
System.out.println("卡里钱不够了");
return;
}
try {
Thread.sleep(1000);//等待确保两个线程都进入
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=takeMoney;
nowMoney+=takeMoney;
System.out.println(name+"取走了"+takeMoney);
System.out.println("卡里剩余"+account.money);
}
}
当两个线程同时操作同一个对象Account的时候就会发生线程安全问题
两个人都以为现在还有100万
集合
list线程不同步
如果某个线程类的run方法是向list中添加一个东西
我们创建10000个这个线程类的对象 正常list中的长度就应该是10000现在却比10000少
说明有两个线程一瞬间同时操作list把数字添加到链表底层数组的同一个位置
public static void main()
{
List<String> list=new ArrayList<>();
for(int i=0;i<10000;i++)
{
new Thread(()->{
list.add(Thread.currentThread.getName());
}).start();
}
sout(list.size());
}
线程安全 同步代码块
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());}
}).start();
}
- 如果要同步的是同步方法锁的就是本类对象 比如买票的例子 是sellTicket()这方法在进行同步
那就不用设置同步监听对象,因为同步方法的同步监听对象就是这个类对象本身或者反射的class对象本身
@Override
public void run() {
while (flag) {
sell();
}
}
public synchronized void sell()
{
if(tickets<=0)
{
flag=false;
System.out.println("票已经卖完");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到票"+tickets--);
}
-
如果用同步代码块就是可以锁任何类型的对象
synchronized默认锁的对象是当前方法所在类的类对象this
如果本类this对象并不是真正被操作的对象(真正锁的对象(同步监视对象)是被CRUD的,是变化的量),想要改变同步监视对象 就用锁同步代码块 ()括号里写同步监视对象{}里写我们用这个锁对象进行的操作 -
对银行取钱代码改进,现在如果直接在run加sychronized发现没有改变 因为本类对象银行并不是被操作的对象
Account账户才是被多个线程共同操作的对象 才是变化的所以我们要用同步代码块来改进
@Override
public void run() {
synchronized (account)
{
if(account.money<takeMoney)
{
System.out.println("卡里钱不够了");
return;
}
try {
Thread.sleep(1000);//等待确保两个线程都进入
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=takeMoney;
nowMoney+=takeMoney;
System.out.println(name+"取走了"+takeMoney);
System.out.println("卡里剩余"+account.money);
}
}
死锁
互斥条件
不剥夺条件
请求保持
循环等待
Lock锁
sychronized是隐式锁 锁的对象定义麻烦 也看不到锁对象开始和结束
可重入锁
int tickets=10;
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true) {
try {//显示枷锁
lock.lock();
if (tickets <= 0) {
System.out.println("票卖完了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "取到票" + tickets--);
}finally {
lock.unlock();//显示解锁
}
}
}
线程通信
每一个线程都有一个锁
Object类的方法有toString() equals() hashCode()
还有wait() notify()
生产者和消费者共享同一片资源,互相依赖互为条件
- 生产者生成产品之前,生产者会通知消费者等待,生成之后放入池子,
池子如果满了通知消费者来消费,生产者等待 - 消费者来池子里消费了,告诉生产者我消费完了,你可以继续生产,
消费完池子空了,要通知生产者生产,消费者等待