一、多线程线程安全问题
满足以下两个条件:
(1)多个线程共享了数据
(2)操作共享数据的语句有多条(超过1条),一个线程执行了操作共享数据的语句一部分时CPU就被抢走了,当再次获得CPU时,直接执行了后边的语句,从而导致了错误的修改。
则该程序存在多线程安全问题
如下:
下面利用Thread的sleep()方法来查看多线程不安全问题,修改上图代码
注意:这里需要处理异常InterruptedException,不能抛出,因为run()方法是重写的
class Ticket implements Runnable
{
private int num=50;
private Object obj=new Object();
public void run()
{
while(true)
{
synchronized(obj)//获取锁
{
if(num>0)
{
//睡着的线程不会释放锁
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}
}//释放锁
}
}
}
class Demo1
{
public static void main(String[] args)
{
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
问题:怎么解决安全问题? ------ synchronized()
操作共享数据的多条语句,只有一个线程全部执行完,其他线程才能执行。
同步代码块:
synchronized(Object obj) //对象任意
{
同步代码块
}
通过锁实现了多个线程之间的护持
多个线程需要使用同一把锁
原理如下:
用自己的话来说:一个线程先要获取到锁,才能执行同步代码块中的代码。即使一个线程睡着了,也不会放弃锁,当线程执行完同步代码块中的所有语句时,才会释放锁,从而实现了一个线程执行完所有操作共享数据的语句时,其他线程才能执行。
synchronized关键字可以修饰方法,可以修饰代码块。但不能修饰构造器、属性等
经典问题:银行问题
class Bank
{
private int sum;
private Object obj=new Object();
//存钱功能 同步函数(方法)
public synchronized void save(int money)//如果一个方法中的代码需要全部放在同步代码块中,那么这个方法就是同步函数
{
//synchronized(obj)
//{
sum=sum+money;//t1 t2
//---->
System.out.println("总钱数:sum="+sum);
//}
}
}
//线程的任务是存钱
class Task implements Runnable
{
private Bank bank=new Bank();
public void run()
{
for(int i=1;i<=3;i++)
{
bank.save(100);
}
}
}
class Demo2
{
public static void main(String[] args)
{
//创建任务对象
Task cus=new Task();
Thread t1=new Thread(cus);
Thread t2=new Thread(cus);
t1.start();
t2.start();
}
}
上面的例子都是函数中的全部代码都放在同步代码块中,因此可以将该方法使用synchronized关键字修饰,该方法也被称为同步函数。
同步函数使用的锁是------this
那么静态同步函数使用什么锁?------静态进内存的时候没有兑对象,不能使用this作为锁,只能使用它所属的字节码作为锁
二、单例模式中懒汉式多线程安全问题
//单例设计模式中懒汉式多线程安全问题
class Single
{
private static Single s;
private Single(){}
public static Single getInstance()
{
synchronized(Single.class)
{
if(s==null) //t1 t2
--->
s=new Single();
}
return s;
}
}
class Task implements Runnable
{
public void run()
{
Single single = Single.getInstance();
}
}
class Demo4
{
public static void main(String[] args)
{
Task task=new Task();
Thread t1=new Thread(task);
Thread t2=new Thread(task);
t1.start();
t2.start();
}
}
由此可见单例模式中存在多线程安全问题,而饿汉式是线程安全的,因此实际生活中用饿汉式
三、死锁
//死锁: 避免死锁
class Ticket implements Runnable
{
private int num=2000;
private Object obj=new Object();
boolean flag=true;
public void run()
{
if(flag)
{
while(true)
{
synchronized(obj)
{
fun();
}
}
}else{
while(true)
{
fun();
}
}
}
//同步函数 this
public synchronized void fun()
{
synchronized(obj)
{
if(num>0)
{
//睡着的线程不会释放锁
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}
System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
}
}
}
}
class Demo5
{
public static void main(String[] args)
{
Ticket t=new Ticket();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}
t.flag=false;
t2.start();
}
}
四、线程间的通信
定义:多个线程的任务不同,但是操作的数据相同
class Demo6
{
public static void main(String[] args)
{
//线程间的通信:多个线程执行的任务不同,但是操作的数据相同
Data data=new Data();
//创建输入线程的任务
InTask in = new InTask(data);
//创建输出线程的任务
OutTask out = new OutTask(data);
//创建输入线程
Thread ru=new Thread(in);
//创建输出线程
Thread chu=new Thread(out);
ru.start();
chu.start();
}
}
//描述数据
class Data
{
String name;
String sex;
}
//定义输入线程的任务
class InTask implements Runnable
{
private Data data;
InTask(Data data)
{
this.data=data;
}
public void run()
{
int i=0;
while(true)
{
synchronized(data){
if(i==0){
data.name="黄文强";
//----->
data.sex="男";
}else{
data.name="黄蓉";
data.sex="女";
}
}
i=(i+1)%2;
}
}
}
//定义输出线程的任务
class OutTask implements Runnable
{
private Data data;
OutTask(Data data)
{
this.data=data;
}
public void run()
{
while(true){
synchronized(data)
{
System.out.println(data.name+"...."+data.sex);
}
}
}
}
class Demo7
{
public static void main(String[] args)
{
//线程间的通信:多个线程执行的任务不同,但是操作的数据相同
Data data=new Data();
//创建输入线程的任务
InTask in = new InTask(data);
//创建输出线程的任务
OutTask out = new OutTask(data);
//创建输入线程
Thread ru=new Thread(in);
//创建输出线程
Thread chu=new Thread(out);
ru.start();
chu.start();
}
}
//描述数据
class Data
{
String name;
String sex;
boolean flag;//false
}
//定义输入线程的任务
class InTask implements Runnable
{
private Data data;
InTask(Data data)
{
this.data=data;
}
public void run()
{
int i=0;
while(true)
{
synchronized(data)
{
//判断能不能赋值
if(data.flag==true)
{
try{
data.wait();//让持有这个锁的线程进入等待状态
}catch(InterruptedException e){
e.printStackTrace();
}
}
if(i==0){
data.name="黄文强";
//----->
data.sex="男";
}else{
data.name="黄蓉";
data.sex="女";
}
data.flag=true;
data.notify();//唤醒对象,允许空唤醒
}
i=(i+1)%2;
}
}
}
//定义输出线程的任务
class OutTask implements Runnable
{
private Data data;
OutTask(Data data)
{
this.data=data;
}
public void run()
{
while(true){
synchronized(data)
{
//判断能不能输出
if(data.flag==false)
{
try{
data.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println(data.name+"...."+data.sex);
data.flag=false;
data.notify();
}
}
}
}
wait():让线程进入等待状态,被放入线程池
notify():唤醒线程池中的任意一个线程
notifyAll():唤醒的是线程池中的所有线程
这三个方法必须由锁来调用,因为锁是任意的对象,任意对象都能调用的方法只能定义在Object类中。
好的,现在实现了线程的安全同步。下面将代码进行优化。
class Demo8
{
public static void main(String[] args)
{
//线程间的通信:多个线程执行的任务不同,但是操作的数据相同
Data data=new Data();
//创建输入线程的任务
InTask in = new InTask(data);
//创建输出线程的任务
OutTask out = new OutTask(data);
//创建输入线程
Thread ru=new Thread(in);
//创建输出线程
Thread chu=new Thread(out);
ru.start();
chu.start();
}
}
class Data
{
private String name;
private String sex;
private boolean flag;
public synchronized void set(String name,String sex)
{
if(flag){
try{
this.wait();
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
this.name=name;
this.sex=sex;
flag=true;
this.notify();
}
public synchronized void out()
{
if(!flag){
try{
this.wait();
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println(name+","+sex);
flag=false;
this.notify();
}
}
class InTask implements Runnable
{
private Data data;
InTask(Data data)
{
this.data=data;
}
public void run()
{
int i=0;
while(true)
{
if(i==0)
data.set("黄文强","男");
else
data.set("黄蓉","女");
i=(i+1)%2;
}
}
}
class OutTask implements Runnable
{
private Data data;
OutTask(Data data)
{
this.data=data;
}
public void run()
{
while(true)
{
data.out();
}
}
}