Java多线程——线程的同步和通信

线程安全是指多个线程访问同一代码,不会产生不确定的结果;

线程同步:

为了避免多线程在共享资源时发生冲突,所以 要在线程使用该资源时,就为线程上一把“锁”,第一个访问资源的线程为资源上锁,其他线程若想访问该资源,则必须等待解锁为止,解锁的同时,另一个线程访问资源并为资源上锁。

在使用多线程编程时,一般解决同步问题的方法有两种(都是用synchronize关键字):一种是同步代码块,一种是同步方法(同步方法使用this关键字作为标记,即对象本身作为同步监视器);但还有一种非常灵活简便的同步锁可以选择,可以通过java.util.concurrent.locks.ReentrntLock类 的对象调用lock()方法来实现加锁操作,此时可以进行同步操作,同步操作结束后,调用unlock()释放同步锁。(在调用同一对象的wait()和notify()方法语句必须放在同步代码块中,并且同步代码块使用该对象的同步锁,否则在运行时会抛出ILLegalMonitorStateException)异常。

同步代码块:
synchronize(Object obj) {
     //需要同步的代码块
}
同步方法:
【访问控制符】 【static|final】synchronize 返回类型 方法名(参数列表){
         //需要同步的代码return 返回值】
}

其中,synchronize实现同步代码块的关键字,obj表示任意对象,可以是实际存在的也可以是假设的,在Java中任意一个对象都有一个同步锁,以下是注意事项:

  • 对象O的同步锁在任何时刻最多只能被一个线程拥有;
  • 如果对象O的同步锁被线程T拥有,那么当其他线程来访问O时,这些线程将被放到O的锁池中,并将它们转为同步阻塞状态;
  • 拥有O的锁的线程T执行完后,会自动释放O的锁。若在执行过程中,线程T发生异常退出,也将自动释放O的锁;
  • 如果线程T在执行同步代码块时,调用了O的wait()方法,则线程T会释放O的锁,线程T也将转为等待阻塞状态;
  • 如果线程T在执行同步代码块时,调用了Thread类的sleep()方法,线程T将放弃运行权,即放弃CPU,但线程T不会放弃对象O的锁;
  • 如果线程T释放了对象O的锁,并放弃运行权,则CPU将会随机分配给对象O锁池中的线程,该线程也将会拥有对象O锁;
/**
 * 一个锅里有五碗饭,有两个窗口,一次只能给一个人打饭。
 * @author lzq
 *
 */
class Ticket implements Runnable {
    private int ticket = 5;
    public Ticket() {

    }
    public void run() {
        while(ticket >= 1) {
            this.sale();
            }
        }
    /**
     * 同步方法
     */
    private void sale() {
        if(ticket > 0) {
            try {
                Thread.sleep(1000);
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket > 0) {
                System.out.println("------"+Thread.currentThread().getName()+"卖出第"
            +(ticket--)+"碗饭");
            }
        }

    }
}

/**
 * 两个窗口卖票,一次卖一张,总共5张
 * 使用同步代码块
 * @author lzq
 *
 */
public class TestDemo13 implements Runnable {
    private int ticket = 5;
    public TestDemo13() {

    }
    public void run() {
        while(ticket >= 1) {
            synchronized (this) {
                try {
                    Thread.sleep(1000);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                if(ticket > 0) {
                    System.out.println(Thread.currentThread().getName()+"卖出第"
                +(ticket--)+"张门票");
                }
            }
        }
    }


    public static void main(String[] args) {
        TestDemo13 x = new TestDemo13();

        Thread t1 = new Thread(x,"第一窗口");
        Thread t2 = new Thread(x,"第二窗口");
        t1.start();
        t2.start();

        Ticket y = new Ticket();
        Thread z1 = new Thread(y,"第1窗口");
        Thread z2 = new Thread(y,"第2窗口");
        z1.start();
        z2.start();

    }

}

运行结果:

------第2窗口卖出第5碗饭
------第1窗口卖出第4碗饭
第一窗口卖出第5张门票
第一窗口卖出第4张门票
------第2窗口卖出第2碗饭
------第1窗口卖出第3碗饭
------第2窗口卖出第1碗饭
第一窗口卖出第3张门票
第一窗口卖出第2张门票
第一窗口卖出第1张门票
/**
 * 使用同步锁实现
 * 两个窗口卖票,一次卖一张,总共10张
 * @author lzq
 *
 */
public class TestDemo14 implements Runnable {
    private final ReentrantLock lock = new ReentrantLock();//创建锁
    private int ticket = 10;
    public TestDemo14() {

    }
    public void run() {
        while(ticket > 0) {
            lock.lock();   //加锁
            try {
                Thread.sleep(1000);
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket > 0) {
                System.out.println(Thread.currentThread().getName()+"卖出第"
                +(ticket--)+"张门票");
            }
            lock.unlock();//释放锁
        }
    }



    public static void main(String[] args) {
        TestDemo14 x = new TestDemo14();

        Thread t1 = new Thread(x,"第一窗口");
        Thread t2 = new Thread(x,"第二窗口");
        t1.start();
        t2.start();

    }

}

运行结果:

第一窗口卖出第10张门票
第一窗口卖出第9张门票
第一窗口卖出第8张门票
第二窗口卖出第7张门票
第二窗口卖出第6张门票
第二窗口卖出第5张门票
第二窗口卖出第4张门票
第一窗口卖出第3张门票
第一窗口卖出第2张门票
第一窗口卖出第1张门票

范例:

  1. 有两个线程分别为sf和gf,sf用来放置食物,而gf用来取走食物;
  2. sf一次只能放置一批次食物,sf放置好后通知gf取走;
  3. gf一次只能取走一批次的食物,等到gf取走食物后通知sf放置食物;
  4. sf不放置食物,则gf不能取走食物,若gf没有取走食物,则sf不能放置食物;
  5. 开始时,先由sf放置,再由gf取走;
/**
 * 放置食物类
 * @author lzq
 *
 */
class SetFood implements Runnable {
    private Food food;
    public SetFood(Food food) {
        this.food = food;
    }
    public void run() {
        for(int number = 1;number <= 5;number++) {
            try {
                Thread.sleep(500);
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            food.setfood(number);  //放置食物
        }
    }
}
/**
 * 取走食物类
 * @author lzq
 *
 */
class GetFood implements Runnable {
    private Food food;
    public GetFood(Food food) {
        this.food = food;
    }
    public void run() {
        for(int number = 1;number <= 5;number++) {
            try {
                Thread.sleep(500);
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            food.getfood();  //取走食物
        }
    }
}
/**
 * 食物类
 * @author lzq
 *
 */
public class Food {
    private int number = 0; //食物批次
    private String food = null; //食物名称
    public Food() {

    }
    public Food(String food,int number) {  
        this.food = food;
        this.number = number;
    }
    /**
     * 放置食物
     * @param n
     */
    public synchronized void setfood(int n) {   
        if(this.number != 0) {
            try {
                wait();   //开始等待
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        number = n;
        System.out.println("放置"+this.food+"第"+this.number+"批次");
        notify();  //通知取走食物
    }
    /**
     * 取走食物
     * @param n
     * @return
     */
    public synchronized String getfood() {
        if(this.number == 0) {
            try {
                wait();  //等待放置食物
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("\t取走"+"第"+this.number+"批次"+this.food);
        this.number = 0;
        notify();   //通知放置食物
        return food+number;   

    }

    public static void main(String[] args) {
        Food f= new Food("白菜",0);
        SetFood sf = new SetFood(f);
        GetFood gf = new GetFood(f);
        new Thread(sf).start();
        new Thread(gf).start();

    }

}

运行结果:

放置白菜第1批次
    取走第1批次白菜
放置白菜第2批次
    取走第2批次白菜
放置白菜第3批次
    取走第3批次白菜
放置白菜第4批次
    取走第4批次白菜
放置白菜第5批次
    取走第5批次白菜

猜你喜欢

转载自blog.csdn.net/QQ2899349953/article/details/81914213