线程安全
线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
案例:
卖票(只能卖100张票)
初始:
public static void main(String[] args) {
//创建票对象
Ticket ticket = new Ticket();
//创建3个窗口
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
public class Ticket implements Runnable {
//共100票
int ticket = 100;
@Override
public void run() {
//模拟卖票
while(true){
if (ticket > 0) {
//模拟选坐的操作
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
}
}
}
结果:
运行结果发现:上面程序出现了问题
票出现了重复的票
错误的票 0、-1
总结:
其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作。
一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
-1和0的出现原因:
当刚刚判断完还有票时CPU资源被其它线程占用进入休眠状态,当有空闲资源在进行时就不会再判断直接进行了减票操作就会出现0和-1的状况
解决方式:
线程同步
利用(Synchronized)
线程同步的方式有两种:
方式1:同步代码块
方式2:同步方法
同步代码块(上厕所原理)
在代码块声明上 加上synchronized
synchronized (锁对象) { 可能会产生线程安全问题的代码 }
注意:
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
public class Tickets implements Runnable{
private /*static*/ int ticket=100;
//对象锁
private Object obj=new Object();
public void run() {
while(true){
synchronized (obj) {
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
}else{
return;
}
}
}
}
原理:线程运行首先要拿到锁(obj)才能进行下面的程序,如果锁被占用那么只能等待,线程循环运行完后自动归还锁供下一个线程循环运行。
同步方法
在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
注意:
①同步方法中的锁对象是 this
②同步锁别名:对象锁,对象监视器
演示:
private /*static*/ int ticket=100;
public void run() {
while(true){
sale();
}
}
public /*static*/ synchronized void sale(){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
}else{
return;
}
}
静态同步方法:
在方法声明上加上static synchronized
public static synchronized void method(){ 可能会产生线程安全问题的代码 }
静态同步方法中的锁对象是 类名.class
补充:
StringBuilder和StringBuffer真正区别体现:
StringBuffer(方法都有synchronized,都是同步的方法,但这样就会降低速度)
新方式:
Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
常用方法:
演示(还是拿上面的卖票为例):
public class MewTicket implements Runnable{
private int ticket=100;
private Lock lock=new ReentrantLock();
public void run() {
while(true){
lock.lock();
if(ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//释放锁
lock.unlock();
}
}
}
}
}
死锁现象
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。
这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
synchronzied(A锁){ synchronized(B锁){ } }
定义锁对象:
public class lockA { private lockA(){ } public static final lockA locka=new lockA(); }
public class lockB { private lockB(){ } public static final lockB lockb=new lockB(); }
任务:
public class deadlock implements Runnable{
private int i=0;
public void run() {
while(true){
if(i%2==0){
//偶数先进A再进B
synchronized (lockA.locka) {
System.out.println("if......lockA");
synchronized (lockB.lockb) {
System.out.println("if......lockB");
}
}
}else{
//奇数先进B再进A
synchronized (lockB.lockb) {
System.out.println("else......lockB");
synchronized (lockA.locka) {
System.out.println("else......lockA");
}
}
}
i++;
}
}
}
创建线程:
public static void main(String[] args) {
deadlock d1=new deadlock();
Thread t1=new Thread(d1);
Thread t2=new Thread(d1);
t1.start();
t2.start();
}
等待唤醒机制
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。
而这种手段即—— 等待唤醒机制。
等待唤醒机制所涉及到的方法:
①wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
②notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
③notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。
同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
举例:
分析:
1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();
2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。
演示:
public class Resouce { public String name; public String sex; public boolean flag=false; }
输入:
public class Input implements Runnable{
private Resouce r;
public Input(Resouce r) {
super();
this.r = r;
}
private int i=0;
public void run(){
while(true){
//注意要用同一个锁对象,保证同步(r)
synchronized (r) {
//标记为true
if(r.flag==true){
try {
//这些方法在使用时必须标明所属锁
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(i%2==0){
r.name="张三";
r.sex="男";
}else{
r.name="李四";
r.sex="nv";
}
//改变flag,唤醒Output
r.flag=true;
r.notify();
}
i++;
}
}
}
输出:
public class Output implements Runnable{
private Resouce r;
public Output(Resouce r) {
super();
this.r = r;
}
public void run() {
while(true){
synchronized (r) {
//标记为false就wait
if(r.flag==false){
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果为true就打印
System.out.println(r.name+"..."+r.sex);
//更改flag唤醒input
r.flag=false;
r.notify();
}
}
}
}
测试:
public class text {
public static void main(String[] args) {
Resouce r=new Resouce();//定义同一个对象
Input in=new Input(r);
Output out=new Output(r);
Thread tin=new Thread(in);
Thread tout=new Thread(out);
tin.start();
tout.start();
}
}
练习:
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300};
创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从arr数组中获取奖项元素并打印在控制台上,格式如下:
抽奖箱1 又产生了一个 10 元大奖
抽奖箱2 又产生了一个 100 元大奖
。。。。。。
演示:
定义奖池:
public class jiangchi {
public int[] arr={10,5,20,50,100,200,500,800,2,80,300};
public boolean flag=false;
}
抽奖箱1:
public class xiang1 implements Runnable{
private jiangchi j;
Random a=new Random();//创建随机数对象
public xiang1(jiangchi j) {
super();
this.j = j;
}
public void run() {
while(true){
synchronized (j) {
if(j.flag==true){
try {
j.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
//随机抽取奖品
int arr[]=j.arr;
int rdm=0;
int random = a.nextInt(11);
rdm=random;
System.out.println("抽奖箱1 又产生了一个 "+arr[rdm]+"元大奖");
j.flag=true;
j.notify();
}
}
}
}
}
抽奖箱2:
public class xiang2 implements Runnable{
private jiangchi j;
Random a=new Random();//创建随机数对象
public xiang2(jiangchi j) {
super();
this.j = j;
}
public void run() {
while(true){
synchronized (j) {
if(j.flag==false){
try {
j.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
//随机抽取奖品
int arr[]=j.arr;
int rdm=0;
int random = a.nextInt(11);
rdm=random;
System.out.println("抽奖箱2 又产生了一个 "+arr[rdm]+"元大奖");
j.flag=false;
j.notify();
}
}
}
}
}
测试:
public static void main(String[] args) {
jiangchi jc=new jiangchi();
xiang1 x1=new xiang1(jc);
xiang2 x2=new xiang2(jc);
Thread xiang1=new Thread(x1);
Thread xiang2=new Thread(x2);
xiang1.start();
xiang2.start();
}