Java基础 多线程 解决安全问题 等待唤醒机制 Lock Condition interrupt join setPriority yield

线程间通信

函数说明:

wait

public final void wait()
throws InterruptedException在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:

synchronized (obj) {
while ()
obj.wait();
… // Perform action appropriate to condition
}
此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。

抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
InterruptedException - 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态 被清除。


notify

public final void notify()唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

通过执行此对象的同步实例方法。
通过执行在此对象上进行同步的 synchronized 语句的正文。
对于 Class 类型的对象,可以通过执行该类的同步静态方法。
一次只能有一个线程拥有对象的监视器。

抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。


notifyAll

public final void notifyAll()唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。

此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。

抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。


解决安全问题

两个线程操作同一资源,并防止同时操作造成的数据错误。

一个读取,一个写入。
不能一个线程正在写入,写入到一半时,读取线程获得执行权把写入到一半的错误数据读取出来。
为了防止发生这类问题,可以使用同步 synchronized 对资源操作部分代码进行保护。
以实际例子进行说明:

package 多线程.通信;

class Res//资源类
{
    private String name;
    private String sex;
    public synchronized void setInfo(String name,String sex)
    {//资源写入操作,加同步 synchronized防止写入时被读取

            this.name=name;
            this.sex=sex;

    }
    public synchronized String[] getInfo()
    {//资源读取操作,加同步 synchronized防止写入时被读取

            return new String[]{this.name,this.sex};

    }

}

class Input implements Runnable//实现Runnable接口类 Input写入
{
    Res r;
    Input(Res r)//通过构造函数获取到资源的引用
    {
        this.r=r;
    }
    public void run()
    {
        boolean b=true;
        while(true)
        {
            //synchronized(r){
            if(b)//设置不同的名字和性别
            {
                r.setInfo("Tom","man");

            }
            else
            {
                r.setInfo("丽丽","女");

            }
            b=!b;
            //}
        }
    }
}

class Output implements Runnable//实现Runnable接口 Output写出数据
{
    Res r;
    Output(Res r)//通过带参构造函数获取资源的引用
    {
        this.r=r;
    }
    public void run()
    {
        String[] sl;
        while(true)
        {
            //synchronized(r){
            sl=r.getInfo();//获取资源
            System.out.println(sl[0]+"----"+sl[1]);//显示
            //}
        }
    }
}

public class InputOutputDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Res res=new Res();//创建一个新的资源对象
        Output op=new Output(res);//创建一个新的读取对象 op
        Input ip=new Input(res);//创建一个新的写入对象 ip
        Thread t1=new Thread(op);//创建一个线程执行 op定义的run方法
        Thread t2=new Thread(ip);//创建一个线程执行 ip定义的方法
        t1.start();//启动线程
        t2.start();//启动线程
    }

}

等待唤醒机制wait() notify()

实现两个线程交替运行,而非完全抢占式执行.

package 多线程.等待唤醒;

class Res//定义资源类
{
    public String name;//资源类成员变量
    public String sex;//资源类成员变量
    public boolean flag=false;//flag意味着线程是否进行等待
}

class Input implements Runnable//实现接口
{
    Res r;
    int x=0;
    Input(Res r)//带参构造函数传入资源引用
    {
        this.r=r;
    }
    public void run()
    {
        while(true)
        {
            synchronized(r)//同步代码块,使用r对象作为监视器(锁)
            {
                if(r.flag)//如果为true
                {
                    try {
                        r.wait();//当前线程进入等待,使用r对象作为锁,也就是对锁进行了操作,释放了锁。此时线程在这里暂停运行,直到其他拥有r对象锁的线程使用r.notify()通知等待的线程取消等待,才会继续执行。
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(x==0)
                {
                    r.name="mike";
                    r.sex="man";
                }
                else
                {
                    r.name="丽丽";
                    r.sex="女";
                }
                x=(x+1)%2;
                //写入数据完毕,把标识设为true。
                r.flag=true;//原flase设置为true后Output可以正常执行,Input进入等待.
                r.notify();//唤醒其他在等待的一个线程

            }
        }
    }
}

class Output implements Runnable
{
    Res r;
    Output(Res r)
    {
        this.r=r;
    }
    public void run()
    {
        while(true)
        {
            synchronized(r)
            {
                if(!r.flag)
                {
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println(r.name+" "+r.sex);
                //标识设为false意味着输出操作完成
                r.flag=false;
                r.notify();//解除一个正在等待的线程.

            }
        }
    }
}


public class InputOutputDemo {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Res r=new Res();
        Input in=new Input(r);
        Output ot=new Output(r);
        Thread t1=new Thread(in);
        Thread t2=new Thread(ot);
        t1.start();
        t2.start();
    }

}

因为两个线程都受到 synchronized 同步的保护,所以不可能同时都对flag进行判断,只有其中一个可以判断,而且在判断时另一个线程一定是执行完保护代码的,也就是一定是对flag标识完成了操作。所以不必担心flag会不同
这样,就保证了其中一个线程准备执行受到保护的代码时通过flag可以得知是否满足执行条件,如果满足才执行,不满足则使用 监视器对象的wait()方法 进入等待状态,并释放保护权限。

这时保护权限解除,另一线程就可以执行他自己的受到相同监视器保护的代码,同理,此时flag标识可得与另一线程条件相反,也就是可以继续执行保护代码,所以就完成执行,最后重置flag状态,取反,以防止自己下次抢占到执行权而不该执行时执行。最后自己放弃对象锁定,对另一线程进行解除等待操作 notify()。之后,另一线程获取到锁定权限,接着执行没有执行完的受保护代码,依次反复赋予权限。

最终实现了两个线程交替执行。各执行一次,而非完全抢占式执行。

多个线程之间通讯 使用 notifyAll()进行唤醒.

生产者/消费者例程。
实现 两个 数据读取 线程 和 两个 数据写入 线程 共同使用 同一个资源。
在这个例子中使用while(flag)的方式来对线程的wait()等待状态进行控制,防止线程在没有判断flag的情况下从睡眠状态唤醒而不加判断就对数据进行操作,而造成数据错误!
上一个例子使用if语句的形式显然在大于两个线程时将发生错误,因为线程被唤醒后直接从wait()语句之后开始执行而不进行flag判断了.
故应使用while()语句对 wait() 方法进行控制更加安全.
下面是代码:

class ProducerConsumerDemo
{
    public static void main(String[] args)
    {
        Resource res=new Resource();
        Producer pd=new Producer(res);
        Consumer cs=new Consumer(res);

        Thread t1=new Thread(pd);//数据写入 线程1
        Thread t2=new Thread(pd);//数据写入 线程2
        Thread t3=new Thread(cs);//数据读取 线程3
        Thread t4=new Thread(cs);//数据读取 线程4

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Resource{                 //资源类
    private String name;        //资源名
    private int count =1;       //计数
    private boolean flag = false;       //写入和输出 标识

    public synchronized void set(String name) //同步的 写入数据
    {
        while(flag)             //注意,此处使用while 而非 if,控制线程等待更加安全有效。防止睡死
            try
            {
                wait();         //线程进入等待.
            }
            catch(Exception e)
            {

            }
        this.name=name+"--"+count++;    //计数增加

        System.out.println(Thread.currentThread().getName()+".......生产者.."+this.name);//输出显示写入数据完毕
        flag=true;                      //把标志位置 true,允许读取
        this.notifyAll();               //唤醒所有处于wait()状态的线程
    }

    public synchronized void out()//同步的 读取数据
    {
        while(!flag)        //此处使用while 控制线程等待更加安全有效。防止睡死
            try
            {
                wait();         //线程进入等待.
            }
            catch(Exception e)
            {

            }

        System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
        flag=false;             //标识置为 false 允许写入
        this.notifyAll();       //唤醒所有处于wait
    }
}

class Producer implements Runnable      //生产者(数据写入)实现接口Runnable 表示这是一个可以作为线程执行的 Runnable 子类
{
    private Resource res;
    Producer(Resource res)//构造函数,通过参数 res设置线程的共享数据 ,在创建可执行对象时就必须设置共享数据
    {
        this.res=res;
    }
    public void run()
    {
        while(true)
        {
            res.set("商品");//调用资源的写入数据方法.
        }
    }

}

class Consumer implements Runnable//消费者(数据读取)
{
    private Resource res;
    Consumer(Resource res)
    {
        this.res=res;
    }
    public void run()
    {
        while(true)
        {
            res.out();//调用资源的读取数据方法.
        }
    }
}

当使用 ifflag 进行判断时的 错误分析:
假设一种情况,当线程1执行完后,线程2获得执行权,两个线程都是数据写入,那么此时线程2判断flag后会进入wait()状态,等待读取线程操作,线程3 读取数据后 调用 notify(),这时数据读取完毕,数据又可以写入了,加入此时 写入线程1 获得执行权,那么数据将被写入一次,关键:写入完毕,调用notify(),此时处于wait()状态的线程2抢到执行权,线程2就会不经判断flag而直接执行后边写入数据的代码,但数据已经被线程1写入完毕,这时线程2的执行就是一个错误,不应该对数据进行两次写入,不是我们预期的效果!!!


JDK5.0升级版同步 锁(Lock) 和 监视器(Condition)

java.util.concurrent.locks Condition/Lock

接口 Lock

所有已知实现类:
ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock

public interface LockLock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。

随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

     Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }

void lock()
获取锁。

void unlock()
释放锁。

接口 Condition

public interface Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 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();
     }
   } 
 }

以下是一个实现4个线程,2个写入,2个读取。不能同时写入或同时读取。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class MyBuffer          //资源类
{
    private String name;
    Lock lock=new ReentrantLock();//定义锁lock
    Condition cdW=lock.newCondition();//定义新的Condition(条件) cdW 写入条件
    Condition cdR=lock.newCondition();//定义 cdR 读取条件
    boolean flag=false;//定义读写标识
    public void write(String name) throws InterruptedException  //写入方法.抛出中断异常
    {

        lock.lock();//上锁
        try//尝试执行代码
        {
            while(flag)//用flag标识判断是否写入
                cdW.await();//写入条件等待
            this.name=name;
            System.out.println(Thread.currentThread().getName()+"---Write---"+this.name);
            flag=true;//标识为可读取
            cdR.signalAll();//读取条件唤醒所有等待
        }
        finally//无论代码是否有错误最后都必须释放锁.
        {
            lock.unlock();
        }

    }

    public void read() throws InterruptedException//读取方法,抛出中断异常,中断异常由Condition.await()方法抛出.
    {
        lock.lock();//上锁
        try
        {
            while(!flag)
                cdR.await();
            System.out.println(Thread.currentThread().getName()+"--Read---"+this.name);
            flag=false;
            cdW.signalAll();
        }
        finally
        {
            lock.unlock();
        }
    }
}

class MyWriter implements Runnable//实现Runnable接口,写入线程
{
    MyBuffer buf;
    boolean b=true;
    MyWriter(MyBuffer buf)//构造函数,获取资源
    {
        this.buf=buf;
    }

    public void run()//实现run方法
    {
        while(true)
        {
            if(b)
                try
                {
                    buf.write("Lily");
                }
                catch(InterruptedException e)//捕获抛出的中断异常
                {
                    e.printStackTrace();
                }
            else
                try{
                    buf.write("Tom");
                }
                catch(InterruptedException e)
                {
                    e.printStackTrace();
                }
            b=!b;
        }
    }
}
class MyReader implements Runnable//实现Runnable接口,读取线程
{
    MyBuffer buf;
    MyReader(MyBuffer buf)//构造函数
    {
        this.buf=buf;
    }

    public void run()//实现run方法
    {
        while(true)
        {
            try
            {
                buf.read();
            }
            catch(InterruptedException e)//捕获抛出的中断异常
            {
                e.printStackTrace();
            }
        }
    }
}


class MyReadWrite
{
    public static void main(String[] args)
    {
        MyBuffer buf=new MyBuffer();//创建资源
        MyWriter mw=new MyWriter(buf);//创建可执行对象写入,传入资源buf
        MyReader mr=new MyReader(buf);//创建可执行对象读取,传入资源buf
        Thread t1=new Thread(mw);//创建线程1,执行写入对象的run方法
        Thread t2=new Thread(mw);//创建线程2,执行写入对象的run方法
        Thread t3=new Thread(mr);//创建线程3,执行读取对象的run方法
        Thread t4=new Thread(mr);//创建线程4,执行读取对象的run方法
        t1.start();
        t2.start();
        t3.start();
        t4.start();


    }
}

interrupt() 方法是强制中断线程的wait()让线程恢复运行,而不是终结线程.

Thread.interrupt();


class StopThread extends Thread
{
    private boolean flag=true;
    public synchronized void run()
    {
        while(flag)
        {
            try
            {
                wait();
            }
            catch(InterruptedException e)//线程捕获异常并处理
            {
                System.out.println(Thread.currentThread().getName()+"---"+"Exception.");
                flag=false;//将flag置为false,下次循环直接结束线程.
            }
            System.out.println(Thread.currentThread().getName()+"---Run.");//捕获到异常后处理异常,并继续执行该语句.
        }
    }
    public void changeFlag()
    {
        flag=!flag;
    }
}


public class StopThreadDemo
{
    public static void main(String[] args)
    {
        int i=0;
        StopThread t1=new StopThread();
        StopThread t2=new StopThread();
        t1.start();
        t2.start();
        while(true)
        {
            if(i++==60)
            {
                //t1.changeFlag();
                t1.interrupt();//调用interrupt叫醒线程.会产生一个InterruptException异常并被线程捕获。
                t2.interrupt();
                break;
            }
            System.out.println("main---"+i);
        }
        System.out.println("main---over");
    }
}

Thread.setDaemon(boolean on)

setDaemon
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。

可以立即而为设置为后台线程,当前台线程退出,只剩后台线程,那么后台线程也将停止运行,Java虚拟机退出.整个程序结束.

示例代码:


class StopThread extends Thread
{
    private boolean flag=true;
    public void run()
    {
        while(flag)
        {
            /*
            try
            {
                wait();
            }
            catch(InterruptedException e)
            {
                System.out.println(Thread.currentThread().getName()+"---"+"Exception.");
                flag=false;
            }
            */
            System.out.println(Thread.currentThread().getName()+"---Run.");
        }
    }
    public void changeFlag()
    {
        flag=!flag;
    }
}


public class StopThreadDemo
{
    public static void main(String[] args)
    {
        int i=0;
        StopThread t1=new StopThread();
        StopThread t2=new StopThread();
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();
        while(true)
        {
            if(i++==60)
            {
                //t1.changeFlag();
                //t1.interrupt();
                //t2.interrupt();
                break;
            }
            System.out.println("main---"+i);
        }
        System.out.println("main---over");
    }
}

Thread.join() 方法

join
public final void join()
throws InterruptedException等待该线程终止。

抛出:
InterruptedException - 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。

  • 当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
  • join可以用来临时加入线程执行.
class Demo implements Runnable
{
    public void run()
    {
        for(int x=0;x<70;x++)
        {
            System.out.println(Thread.currentThread().getName()+"--"+x);
        }
    }
}

class JoinDemo
{
    public static void main(String[] args) throws InterruptedException
    {
        Demo d=new Demo();
        Thread t1=new Thread(d);
        Thread t2=new Thread(d);
        t1.start();

        //t1.join();//放在此处t2.start()还未执行,要等到t1执行完,main才继续执行,才会启动t2.

        t2.start();//t2启动,可以和t1同时执行,不受到t1.join()影响.
        t1.join();//放在此处,t1,t2同时执行.而主线程要等待t1执行完才继续执行.
        for(int x=0;x<80;x++)
        {
            System.out.println(Thread.currentThread().getName()+"..."+x);
        }
        System.out.println("Over");
    }
}

setPriority(int newPriority) 设置线程优先级

setPriority
public final void setPriority(int newPriority)更改线程的优先级。
首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。

在其他情况下,线程优先级被设定为指定的 newPriority 和该线程的线程组的最大允许优先级相比较小的一个。

参数:
newPriority - 要为线程设定的优先级
抛出:
IllegalArgumentException - 如果优先级不在 MIN_PRIORITY 到 MAX_PRIORITY 范围内。
SecurityException - 如果当前线程无法修改该线程。

yield()

yield
public static void yield()暂停当前正在执行的线程对象,并执行其他线程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章