以下三种情况都是在需要生产出一个,马上就消费一个的情况下:
单生产单消费:
单生产单消费是个简单的多线程,只需保证多线程同步,并且锁为同一把锁即可。代码体现:
class Demo{
private Object obj=new Object(); //创建锁对象obj
private int count=0;
private boolean flag=false; //创建标记
public void set(){
synchronized(obj){ //同步线程
while(flag)
try {
obj.wait(); //线程等待
} catch (InterruptedException e) {
}
count++;
flag=true;
System.out.println("生产的产品"+count);
obj.notify(); //唤醒线程
}
}
public void sell(){
synchronized (obj) {
while(!flag)
try {
obj.wait();
} catch (InterruptedException e) {}
flag=false;
System.out.println("消费的产品"+count);
obj.notify();
}
}
}
class Set implements Runnable{
private Demo d;
public Set(Demo d){
this.d=d;
}
public void run(){
while(true){
d.set();
}
}
}
class Sell implements Runnable{
private Demo d;
public Sell(Demo d){
this.d=d;
}
public void run(){
while(true){
d.sell();
}
}
}
public class TextThread01 {
public static void main(String[] args) {
Demo d=new Demo();
Set st=new Set(d);
Sell sl=new Sell(d);
Thread t1=new Thread(st);
Thread t2=new Thread(sl);
t1.start();
t2.start();
}
}
以上是整个单生产单消费的程序,重点:1 保证锁必须为同一个,2线程等待唤醒的判断逻辑没问题。单生产多消费并不复杂.
多生产多消费和单生产和多消费就比单生产单消费复杂点了,按照上面的代码的逻辑来处理多生产多消费的话,会出现多线程的安全问题。下面分析原因:
先说单生产多消费的线程运行:生产线程1运行唤醒语句后可能还有执行权,然后继续判断,这时的flag为true,生产线程1便进入等待。唤醒线程照样执行,只是没有意义。然后,这时有2个活的消费线程,其中一个启动,运行结束后同样可能有执行权,判断(这时flag为false),消费线程1就进入等待,交出锁,因为这里只有生产线程处于等待状态,所以就唤醒线程1.到这里,生产线程1和消费线程2都有执行资格,如果消费线程2拿到锁,进入判断,又进入等待,把锁交出,只有生产线程1可以拿到,生产线程1拿到后运行后进入等待,唤醒消费线程中的任意一个(这里假设唤醒消费线程1),消费线程拿到执行权,执行完等待。这是有2个线程在等待,如果消费线程1唤醒的是消费线程2,这里的falg是false,线程2就进入等待,这里没有唤醒任何线程,就出现了死锁。所以单生产多消费用这样的代码实现是会出现安全问题的。
处理方案:将程序中的唤醒语句obj.notify换成obj.notifyAll可以解决安全问题,推导参照上述推导即可。换成全部唤醒后,线程的运行的效率就会变得很低。要解决这里的效率问题,需要用到jdk1.5新推出的locke类和其子类condition接口。lock类相当于锁,condition相当于监视器。lock类相较于同步来说,优势在于可以在一个锁内创建2个监视器。
代码体现:
class Demo{
private Object obj=new Object();
private int count=0;
private boolean flag=false;
private Lock lock=new ReentrantLock();
private Condition cot=lock.newCondition();
private Condition col=lock.newCondition();
public void set(){
//synchronized(obj){
lock.lock();try{
while(flag)
try {
cot.await();
} catch (InterruptedException e) {
}
count++;
flag=true;
System.out.println("生产的产品"+count);
col.signal();
//}
}finally{
lock.unlock();
}
}
public void sell(){
lock.lock();
try{
//synchronized (obj) {
while(!flag)
try {
col.await();
} catch (InterruptedException e) {}
flag=false;
System.out.println("消费的产品"+count);
cot.signal();
//}
}finally{
lock.unlock();
}
}
}
class Set implements Runnable{
private Demo d;
public Set(Demo d){
this.d=d;
}
public void run(){
while(true){
d.set();
}
}
}
class Sell implements Runnable{
private Demo d;
public Sell(Demo d){
this.d=d;
}
public void run(){
while(true){
d.sell();
}
}
}
public class TextThread01 {
public static void main(String[] args) {
Demo d=new Demo();
Set st=new Set(d);
Sell sl=new Sell(d);
Thread t1=new Thread(st);
Thread t2=new Thread(sl);
Thread t3=new Thread(sl);
t1.start();
t2.start();
t3.start();
}
}
多消费多生产和单生产单消费的机制是一样的,只需再创建一个生产对象即可,代码体现如上。
守护线程:
将线程定位守护线程后,该线程的特性为:当该多线程中只剩下守护线程在运行时,守护线程就停止运行。代码体现:
class Daemon extends Thread{
private int count=0;
public void print(){
System.out.println(Thread.currentThread().getName()+"***********"+count);
count++;
}
public void run(){
for(int i=0;i<1000;i++){
print();
}
}
}
public class TextDaemon01 {
public static void main(String[] args) {
//TextDaemon01 td=new TextDaemon01();
Daemon d=new Daemon();
Daemon d1=new Daemon();
d1.start();
d.setDaemon(true);
//d.setDaemon(true);
d.start();
}
}
这里有个疑问:主函数能不能作为守护线程?
线程优先级:
getPriority是返回线程的优先级,setPriority为设置线程的优先级。线程优先级为0-10级,我们设置的时候为了可以得到明显的线程优先效果,一般设置为MAX_PRIORITY,MINPRIORITY,NORM_PRIORITY.
Join yield方法:
Join方法的意思:线程2是加入一个线程1,线程1将释放执行全,等线程2执行完毕后,线程1才开始继续运行。先看代码体现:
public class TextDaemon01 {
public static void main(String[] args) throws InterruptedException {
//TextDaemon01 td=new TextDaemon01();
Daemon d=new Daemon();
Daemon d1=new Daemon();
d.start(); //语句1
d.join();//语句2
d1.start();//语句3
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
语句123现在这个情况就是主函数线程运行到语句1时,线程1启动,语句2时主函数线程开始等待线程1的执行完毕,再和线程2抢夺执行权开始运行。
d.start(); //语句1
d1.start();//语句3
d.join();//语句2
当语句123是上面的情况时,线程12交替运行,等线程1运行完毕后主函数线程才开始运行。这里说明一点:主线程释放执行权不是指向的,活线程都可以抢夺执行权并执行。
Yield:
这个方法的意思:让线程暂时暂停,但是该线程可能获取到执行权。
线程在匿名内部类的的表现:
代码体现:
public class TextThread02 {
public static void main(String[] args) {
new Thread(){
public void run(){
while(true){
System.out.println("......");
}
}
}.start();;
}
}
就是将线程写入匿名内部类,功能没变化。