Java多线程(三)线程同步


Java多线程(三)

1.多线程同步

线程安全

之前售票的例子中,多线程共享tickets可能导致线程安全问题。

举例:

class SaleThread implements Runnable{
    private int tickets=10;
    public void run(){
        while(true){
            if(tickets>0){
                try{
                    Thread.sleep(100);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
            }
        }
    }
}

public class Page369 {
    public static void main(String[] args) {
        SaleThread saleThread=new SaleThread();
        new Thread(saleThread,"窗口1").start();
        new Thread(saleThread,"窗口2").start();
        new Thread(saleThread,"窗口3").start();
        new Thread(saleThread,"窗口4").start();
    }
}

错误原因: 在while循环中添加sleep()方法,这样就模拟了售票过程中的延迟。由于线程有延迟,当票号减为1,假设窗口2线程此时出售1号票,对票号进行判断后进入while循环,由于售票前通过sleep()模拟耗时,此时票号任为1,其他线程会进行售票,所以最终会出现0,-1,-2这样的票号。

同步代码块

线程安全问题是由于多个线程同时处理共享资源造成的,想要解决线程安全问题,必须保证处理共享资源的代码在任意时刻只能有一个线程访问。为此Java中提供了线程同步机制。将共享资源代码放置在一个使用synchronized关键字修饰的代码块中,这个代码块称为同步代码块。

语法:

synchronized(lock){
    //操作共享资源代码块
}

举例:

class SaleThread2 implements Runnable{
    private int tickets=10;
    Object lock=new Object();	//定义任意一个对象作为同步代码块的锁
    public void run(){
        while(true){
            //定义同步代码块
            synchronized(lock){
                if(tickets>0){
                    try{
                        Thread.sleep(100);
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
                }
            }
        }
    }
}

public class Page371 {
    public static void main(String[] args) {
        SaleThread2 saleThread2=new SaleThread2();
        new Thread(saleThread2,"窗口1").start();
        new Thread(saleThread2,"窗口2").start();
        new Thread(saleThread2,"窗口3").start();
        new Thread(saleThread2,"窗口4").start();
    }
}

同步方法

把共享资源放在synchronized定义的区域内,便为这些操作都加了同步锁。在方法前也可以用synchronized修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能。

举例:

class SaleThread3 implements Runnable {
    private int tickets=10;
    public void run(){
        while(true){
            saleTicket();
        }
    }
    private synchronized void saleTicket(){
        if(tickets>0){
            try{
                Thread.sleep(1000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
        }
    }
}

public class Page372 {
    public static void main(String[] args) {
        SaleThread3 saleThread=new SaleThread3();
        new Thread(saleThread,"窗口1").start();
        new Thread(saleThread,"窗口2").start();
        new Thread(saleThread,"窗口3").start();
        new Thread(saleThread,"窗口4").start();
    }
}

同步锁

synchronized同步代码块和同步方法也有一些限制,比如它无法中断一个正在等候获得锁的线程,也无法通过轮询得到锁,如果不想等下去就没办法得到锁。从JDK5开始Java增加了一个功能更强大的Lock锁。Lock锁与synchronized隐式锁在功能上基本相同,但Lock锁可以让某个线程在持续获取同步锁失败后返回,不再继续等待,在使用时也更加灵活。

举例:

import java.util.concurrent.locks.*;

class LockThread implements Runnable{
    private int tickets=10;
    //定义一个Lock锁对象
    private final Lock lock=new ReentrantLock();
    public void run(){
        while(true){
            lock.lock();    //对代码块加锁
            if(tickets>0){
                try{
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
                }catch(InterruptedException e){
                    e.printStackTrace();
                }finally{
                    lock.unlock();  //执行完代码块后释放锁
                }
            }
        }
    }
}

public class Page374 {
    public static void main(String[] args) {
        LockThread lockThread=new LockThread();
        new Thread(lockThread,"窗口1").start();
        new Thread(lockThread,"窗口2").start();
        new Thread(lockThread,"窗口3").start();
        new Thread(lockThread,"窗口4").start();
    }
}

通过Lock接口的实现类ReentrantLock来创建Lock锁对象,并通过Lock锁对象的lock()方法和unlock()方法对核心代码块进行上锁和解锁。

死锁问题

两个线程在运行时都在等待对方的锁,这样就导致程序停滞,称为死锁。

举例:

class DeadLockThread implements Runnable{
    //定义两个不同的锁对象
    static Object chopsticks=new Object();
    static Object knifeAndFork=new Object();
    private boolean flag;
    DeadLockThread(boolean flag){
        this.flag=flag;
    }
    public void run(){
        if(flag){
            while(true){
                synchronized(chopsticks){
                    System.out.println(Thread.currentThread().getName()+"---if---chopsticks");
                    synchronized(knifeAndFork){
                        System.out.println(Thread.currentThread().getName()+"---if---knifeAndFork");
                    }
                }
            }
        }else{
            while(true){
                synchronized(knifeAndFork){
                    System.out.println(Thread.currentThread().getName()+"---else---knifeAndFork");
                    synchronized (chopsticks){
                        System.out.println(Thread.currentThread().getName()+"---else---chopsticks");
                    }
                }
            }
        }
    }
}

public class Page376 {
    public static void main(String[] args) {
        DeadLockThread thread1=new DeadLockThread(true);
        DeadLockThread thread2=new DeadLockThread(false);
        new Thread(thread1,"Chinese").start();
        new Thread(thread2,"American").start();
    }
}

两个线程都需要对方所占用的锁,但是都无法释放自己所拥有锁。