//15同步问题的分析案例以及解决思路 //两个客户到一个银行去存钱,每个客户一次存100,存3次。
//问题,该程序是否有安全问题,如果有,写出分析过程,并定于解决方案。 /*
发现运行结果:
sum=200
sum=200
sum=300
sum=400
sum=600
sum=500 打印错乱,不关心,但是发现数值错误,没有100.
运行了几次,发现有对的。 说明多线程的随机性造成了安全问题。
哪的问题?
1,既然是多线程的问题,必须问题发生在多线程中
2,任务代码中是否有共性数据呢?b对象中的sum.
3,是否有对sum进行多次运算呢?有! 加同步就行了。
*/
//描述银行,
class Bank
{
private int sum;
private Object obj = new Object();
public void add(int num)
{
synchronized(obj)
{
sum = sum+num;
System.out.println("sum="+sum);//每存一次,看到银行金额变化。
}
}
} class Consumer implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0;x<3;x++)
{
b.add(100);//一次存100,循环3次。
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Consumer c = new Consumer();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
//同步函数相关问题,以及同步函数和同步代码块的比较
/*
同步的另一种体现形式。同步函数。 同步函数使用的锁是哪个?
经过简单的分析:大概猜的是this。因为函数必须被对象调用。 验证:
写一个同步代码块,写一个同步函数。如果同步代码块中的锁对象和同步函数中的锁对象
是同一个,就同步了,就没有错误的数据了,如果不是同一个锁对象,就不同步,会出现错误数据, 让两个线程,一个线程在同步代码块中执行,一个线程待同步函数中执行。 总结:同步函数使用的锁是this. 同步函数和同步代码块的的区别?
1,同步函数使用的锁是固定的this。当线程任务只需要一个同步时,完全可以通过同步函数来体现。
2,同步代码块使用的是锁可以是任意对象。当线程任务需要多个同步时,必须通过所来区分。这时必须使用同步代码块。
同步代码块较为常用。 如果只用一个同步时,完全可以简化为
*/ class Ticket implements Runnable
{
//1,描述票的数量
private int tickets = 100; //2,售票的动作。这个动作需要被多线程执行,那就是线程任务代码,
//需要定义在run方法中。
//记住,线程任务中通常都有循环结构。
//private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true)
{
synchronized(this)
{
if(tickets > 0)
{
try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
System.out.println(Thread.currentThread().getName()+"..obj.."+tickets--);//打印线程名称
}
}
}
} else{
while(true)
{
this.sale();
}
}
} public synchronized void sale()//同步函数。具备了同步性的函数,使用的锁对象就是this
{
if(tickets > 0)
{
//要让线程在这里稍停,模拟问题的发生。sleep 看到了 0 -1 -2 这样的错误的数据,这就是传说中的安全问题。
try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
System.out.println(Thread.currentThread().getName()+"..sale.."+tickets--);//打印线程名称
}
}
} class ThreadDemo4
{
public void main(String[] args)
{
//1,创建Runnable接口的子类对象。
Ticket t = new Ticket(); //创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t); //3,开启四个线程
t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){}
//切换标记;之前,让主线程停一会儿,这时就只有一个t1线程在。
t.flag = false; t2.start();
}
}
/*
静态同步函数使用的锁不是this.而是字节码文件对象。类名.class
*/ class Ticket implements Runnable
{
//1,描述票的数量
private static int tickets = 100; //2,售票的动作。这个动作需要被多线程执行,那就是线程任务代码,
//需要定义在run方法中。
//记住,线程任务中通常都有循环结构。
//private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true)
{
synchronized(Ticket.class)
{
if(tickets > 0)
{
try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
System.out.println(Thread.currentThread().getName()+"..obj.."+tickets--);//打印线程名称
}
}
}
} else{
while(true)
{
this.sale();
}
}
} public static synchronized void sale()//加了静态后,已经知道静态函数里面没有了this.
{
if(tickets > 0)
{
//要让线程在这里稍停,模拟问题的发生。sleep 看到了 0 -1 -2 这样的错误的数据,这就是传说中的安全问题。
try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
System.out.println(Thread.currentThread().getName()+"..sale.."+tickets--);//打印线程名称
}
}
} class ThreadDemo5
{
public static void main(String[] args)
{
//1,创建Runnable接口的子类对象。
Ticket t = new Ticket(); //创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t); //3,开启四个线程
t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){}
//切换标记;之前,让主线程停一会儿,这时就只有一个t1线程在。
t.flag = false; t2.start();
}
}
//单例懒汉模式的并发访问相关问题
/*
单例懒汉模式的并发访问相关问题
*/ //饿汉式;多线程并发没问题。
class Single
{
private static final Single s = new Single(); private Single(){} public static Single getInstance()
{
return s;
}
} //懒汉式:
class Single
{
private static Single s = null; private Single(){}
/*
并发访问,会有安全隐患,所以加入同步机制解决安全问题。
但是,同步的出现却降低了效率。(提稿效率的办法就是减少判断锁的次数)
可以通过双重判断的方式,解决效率问题,减少判断的次数。 */
public static /*synchronized*/ Single getInstance()
{
if(s==null)//只要有一个线程把对象创建完,其他线程就再也不会判断锁了。
{
synchronized(Single.class)
{
if(s==null)
// --->0 --->1
s = new Single();
}
}
return s;
}
}
class Demo implements Runnable
{
public void run()
{
Single.getInstance();
}
} class ThreadDemo6
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}
//同步的另一个弊端。死锁
/*
同步的另一个弊端。 情况之一:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。
这时容易引发一种现象,死锁。
这种情况能避免就避免。 //Thread-0
synchronized(obj1)
{
--->thread-0 obj1
synchronized(obj2)
{ }
} //Thread-1
synchronized(obj2)
{
thread-1 obj2
synchronized(obj1)
{ }
} */ class Ticket implements Runnable
{
//1,描述票的数量
private int tickets = 100; //2,售票的动作。这个动作需要被多线程执行,那就是线程任务代码,
//需要定义在run方法中。
//记住,线程任务中通常都有循环结构。
private Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag){
while(true)
{
synchronized(obj)//obj锁
{
sale();//this锁
}
}
} else{
while(true)
{
this.sale();
}
}
} public synchronized void sale()//this锁
{
synchronized(obj)//obj锁
{
if(tickets > 0)
{
//要让线程在这里稍停,模拟问题的发生。sleep 看到了 0 -1 -2 这样的错误的数据,这就是传说中的安全问题。
try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
System.out.println(Thread.currentThread().getName()+"..sale.."+tickets--);//打印线程名称
}
}
}
} class ThreadDemo7
{
public static void main(String[] args)
{
//1,创建Runnable接口的子类对象。
Ticket t = new Ticket(); //创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
Thread t1 = new Thread(t);
Thread t2 = new Thread(t); //3,开启四个线程
t1.start(); try{Thread.sleep(10);}catch(InterruptedException e){}
//切换标记;之前,让主线程停一会儿,这时就只有一个t1线程在。
t.flag = false; t2.start();
}
} //这个程序在运行过程中死锁了。
/*
Thread-0..sale..100
Thread-0..sale..99
Thread-0..sale..98
Thread-0..sale..97
Thread-0..sale..96
Thread-0..sale..95
*/
//死锁示例--面试会用到
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true)
{
synchronized(MyLock.LOCKA)
{
System.out.println(Thread.currentThread().getName()+"if......locka");
synchronized(MyLock.LOCKB)
{
System.out.println(Thread.currentThread().getName()+"if......lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.LOCKB)
{
System.out.println(Thread.currentThread().getName()+"else......lockb");
synchronized(MyLock.LOCKA)
{
System.out.println(Thread.currentThread().getName()+"else......locka");
}
}
}
}
}
} //单独定义一个用于存储锁对象类。
class MyLock
{
public static final Object LOCKA = new Object();
public static final Object LOCKB = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
//创建了两个线程任务。
Test t1 = new Test(true);
Test t2 = new Test(false); Thread t11 = new Thread(t1);
Thread t22 = new Thread(t2);
t11.start();
t22.start();
}
}
//多线程间的通信-生产者&消费者-问题发生。
/*
多线程中最为常见的应用案例,
生产者消费者问题。
生产和消费同时执行,需要多线程。
但是执行的任务却不相同,处理的资源是相同的。线程间的通信。 1,描述资源。
2,描述生产者,因为具备着自己的任务。
3,描述消费者,因为具备着自己的任务。 问题1:
数据错误,已经被生产很早期的商品,才被消费到。
出现线程安全问题,需要用同步来解决。
问题已解决:不会再消费到之前很早期的商品。 问题2:
发现了连续生产却没有消费,同时对同一个商品进行多次消费。
希望的结果应该是生产一个商品,就被消费掉,生产下一个商品。 搞清楚机个问题:
生产者什么时候应该生产呢?
当盘子中没有面包,就生产,如果有了面包,就不要消费。 消费者什么时候应该消费呢?
当盘子中已有面包,就消费,如果没有面包,就不要消费。 */ //1,描述资源。属性:名称和编号。 行为:对商品名称赋值,获取商品
class Resource
{
private String name;
private int count=1; //1,提供设置的方法。
public synchronized void set(String name)
{
//1,给成员变量赋值并加上编号。
this.name = name+count;
count++; //2,打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name); } public synchronized void out()
{
System.out.println(Thread.currentThread().getName()+"....消费者..."+this.name);
}
} //2,描述生产者。
class Producer implements Runnable
{
private Resource r ;
//生产者一初始化就要有资源。需要将资源传递到构造函数中。
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("面包");
}
}
} //3,描述消费者。
class Consumer implements Runnable
{
private Resource r ;
//消费者一初始化就要有资源。需要将资源传递到构造函数中。
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} class ThreadDemo8
{
public static void main(String[] args)
{
//创建资源对象。
Resource r = new Resource(); //创建线程任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r); //3,创建线程。
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con); t1.start();
t2.start();
}
}
//多线程间的通信-生产者&消费者-问题发生。以及等待唤醒机制。
/*
多线程中最为常见的应用案例,
生产者消费者问题。
生产和消费同时执行,需要多线程。
但是执行的任务却不相同,处理的资源是相同的。线程间的通信。 1,描述资源。
2,描述生产者,因为具备着自己的任务。
3,描述消费者,因为具备着自己的任务。 问题1:
数据错误,已经被生产很早期的商品,才被消费到。
出现线程安全问题,需要用同步来解决。
问题已解决:不会再消费到之前很早期的商品。 问题2:
发现了连续生产却没有消费,同时对同一个商品进行多次消费。
希望的结果应该是生产一个商品,就被消费掉,生产下一个商品。 搞清楚机个问题:
生产者什么时候应该生产呢?
当盘子中没有面包,就生产,如果有了面包,就不要消费。 消费者什么时候应该消费呢?
当盘子中已有面包,就消费,如果没有面包,就不要消费。 生产者生产了商品后,应该去告诉消费者来消费。而这时的生产者应该处于等待状态。
消费者消费了商品后,应该告诉生产者去生产,而这时的消费者应该处于等待状态。 ======================================================
等待/唤醒机制。
wait();会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
notify();会唤醒线程池中任意一个等待的线程。
notifyAll();会唤醒线程池中所有的等待线程。 记住:这些方法必须使用在同步中,必须要标识wait,notify等方法所属的锁。
同一个锁上的notify,只能唤醒该锁上的wait线程。 为什么这些方法定义在了Object中呢?
因为这些方法必须标识所属的锁。而锁可以是任意对象,任意对象可以调用的方法必然是Objec的方法。 距离:小朋友抓人游戏。
*/ //1,描述资源。属性:名称和编号。 行为:对商品名称赋值,获取商品
class Resource
{
private String name;
private int count=1; //定义标记。
private boolean flag = false; //1,提供设置的方法。
public synchronized void set(String name)
{
if(flag)
try{this.wait();}catch(InterruptedException e){}
//1,给成员变量赋值并加上编号。
this.name = name+count;
count++; //2,打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name); //将标记该为true
flag = true;
//唤醒消费者。
this.notify(); } public synchronized void out()
{
if(!flag)
try{wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"....消费者..."+this.name);
//将标记该为flase。
flag = false;
//唤醒生产者。
notify();
}
} //2,描述生产者。
class Producer implements Runnable
{
private Resource r ;
//生产者一初始化就要有资源。需要将资源传递到构造函数中。
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("面包");
}
}
} //3,描述消费者。
class Consumer implements Runnable
{
private Resource r ;
//消费者一初始化就要有资源。需要将资源传递到构造函数中。
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} class ThreadDemo9
{
public static void main(String[] args)
{
//创建资源对象。
Resource r = new Resource(); //创建线程任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r); //3,创建线程。
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con); t1.start();
t2.start();
}
}
//多生产&多消费问题发生及解决。
/*
java.util.concurrent.locks 软件包中提供了相应的解决方案。
Lock接口。比同步更厉害,有更多的操作,获取锁:lock(); 释放锁:unlock();
提供了一个更加面向对象的锁。在该锁中提供了更多的显示的锁操作。
可以替代同步。 升级到JDK1.5,先别同步改为Lock.
*/ /*
多生产多消费。
问题1:生产了商品没有被消费,同一个商品被消费多次。
Thread-2....生产者...面包285
Thread-0....生产者...面包286
Thread-1....消费者...面包286
Thread-2....生产者...面包287
Thread-0....生产者...面包288
Thread-1....消费者...面包288 被唤醒的线程没有判断标记,造成了问题1的产生。
解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while判断标记。
注意:只要是多生产,多消费,必须是while判断标记。 问题2:while判断后,死锁了。
原因:生产方唤醒了线程池中的生产方的线程。本方唤醒了本方。
解决:希望本方要唤醒对方。没有对应方法,只能唤醒所有。 其实还是有一些问题的。效率低了。
*/
//1,描述资源。属性:名称和编号。 行为:对商品名称赋值,获取商品
class Resource
{
private String name;
private int count=1; //定义一个锁对象。
private Lock lock = new ReentrantLock(); //定义标记。
private boolean flag = false; //1,提供设置的方法。
public synchronized void set(String name)// t1 t2
{
//if(flag)
while(flag)
try{this.wait();}catch(InterruptedException e){}
//1,给成员变量赋值并加上编号。
this.name = name+count;//商品1
count++;//2
//2,打印生产了哪个商品。
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);//生产了商品1 //将标记该为true
flag = true;
//唤醒消费者。
this.notifyAll(); } public synchronized void out()// t3 t4
{
//if(!flag)
while(!flag)
try{wait();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"....消费者..."+this.name);
//将标记该为flase。
flag = false;
//唤醒生产者。
this.notifyAll();
}
} //2,描述生产者。
class Producer implements Runnable
{
private Resource r ;
//生产者一初始化就要有资源。需要将资源传递到构造函数中。
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("面包");
}
}
} //3,描述消费者。
class Consumer implements Runnable
{
private Resource r ;
//消费者一初始化就要有资源。需要将资源传递到构造函数中。
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} class ThreadDemo10
{
public static void main(String[] args)
{
//创建资源对象。
Resource r = new Resource(); //创建线程任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r); //3,创建线程。
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con); t1.start();
t2.start();
t3.start();
t4.start();
}
}
//多生产&多消费问题发生及解决。
import java.util.concurrent.locks.*;
/*
java.util.concurrent.locks 软件包中提供了相应的解决方案。
Lock接口。比同步更厉害,有更多的操作,获取锁:lock(); 释放锁:unlock();
提供了一个更加面向对象的锁。在该锁中提供了更多的显示的锁操作。
可以替代同步。 升级到JDK1.5,先别同步改为Lock.
已经将旧锁替换成新锁,那么所上的监视器方法(wait notify notifyAll )也应该替换
成新锁上的方法。
而JDK1.5中将这些原有的监视器方法封装到了一个Condition对象中。
想要获取监视器方法,需要先获取Condition对象。 Condition对象的出现其实就是替代了Object中的监视器方法。
await();
signal();
signalAll();
将所有的监视器方法替换成了Condition.
功能和ThreadDemo10.java的程序一样,仅仅是用新的对象,改了写法而已。
但是问题依旧,效率还是很低。
老程序中可以通过两个嵌套完成,但是容易引发死锁。 新程序中,就可以解决这个问题。
可以在一个锁上加上多个监视器对象。
*/
class Resource
{
private String name;
private int count=1; //定义一个锁对象。
private Lock lock = new ReentrantLock();
//获取锁上的Condition对象。为了解决本方唤醒对方的问题,可以一个锁上创建两个监视器对象。
private Condition produce = lock.newCondition();//负责生产的.
private Condition consume = lock.newCondition();//负责消费的. //定义标记。
private boolean flag = false; //1,提供设置的方法。
public void set(String name)// t1 t2
{
//获取锁。
lock.lock();
try{
while(flag)
try{produce.await();}catch(InterruptedException e){}
this.name = name+count;//商品1
count++;
System.out.println(Thread.currentThread().getName()+"....生产者..."+this.name);//生产了商品1 //将标记该为true
flag = true;
//执行消费者的唤醒。而且是唤醒一个消费者就行了。
consume.signal();
}
finally{
lock.unlock();//一定要执行。
}
} public void out()// t3 t4
{
lock.lock();
try{
while(!flag)
try{consume.await();}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()+"....消费者..."+this.name);
//将标记该为flase。
flag = false;
produce.signal();
}
finally{
lock.unlock();
}
}
} //2,描述生产者。
class Producer implements Runnable
{
private Resource r ;
//生产者一初始化就要有资源。需要将资源传递到构造函数中。
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("面包");
}
}
} //3,描述消费者。
class Consumer implements Runnable
{
private Resource r ;
//消费者一初始化就要有资源。需要将资源传递到构造函数中。
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
} class ThreadDemo11
{
public static void main(String[] args)
{
//创建资源对象。
Resource r = new Resource(); //创建线程任务。
Producer pro = new Producer(r);
Consumer con = new Consumer(r); //3,创建线程。
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con); t1.start();
t2.start();
t3.start();
t4.start();
}
}
//接口 Condition示例:
class BoundedBuffer {
final Lock lock = new ReentrantLock();//锁
final Condition notFull = lock.newCondition(); //生产。
final Condition notEmpty = lock.newCondition(); //消费。 final Object[] items = new Object[100];//存储商品的容器。
int putptr/*生产者使用的脚标*/, takeptr,/*消费者使用的角标*/ count;//计数器。 /*生产者使用的方法,往数组中存储商品。*/
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) //判断计数器是否已到数据长度。慢了。
notFull.await();//生产者等待。 items[putptr] = x; //安照角标将商品存储到数组中。 if (++putptr == items.length)//如果存储的角标到了数组的长度,就将角标归零。
putptr = 0;
++count;//计数器自增。
notEmpty.signal();//唤醒一个消费者。
} finally {
lock.unlock();
}
} public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) //如果计数器为零,说明没有商品,消费者需要等待。
notEmpty.await();
Object x = items[takeptr]; //从数组中通过消费者角标获取商品。 if (++takeptr == items.length) //如果消费者角标等于数组长度,将角标归零。
takeptr = 0;
--count;//计数器自增。
notFull.signal();//唤醒生产者。
return x;
} finally {
lock.unlock();
}
}
}