【Java】线程的同步机制

1.线程安全问题

三个售票窗口,总票数为100张,卖票过程中,线程a在操作ticket,未操作完成时,其他线程参与进来,也操作ticket。导致出现重票、错票问题。(线程安全问题)。

class TicketWindow implements Runnable{
    private int ticket = 100;   //100张票

    @Override
    public void run() {
        while (true){
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+":卖票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

class WindowTest{
    public static void main(String[] args) {
        TicketWindow w = new TicketWindow();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

image

2.解决

当线程a操作ticket时,其他线程不能参与进来,直到线程a操作完成。
在Java中,通过同步机制来解决线程安全问题。

方式一:同步代码块,synchronized修饰代码块

image

(1)共享数据:在这里就是ticket,被多个线程共同操作
(2)同步监视器(锁),任何一个类的对象(多个线程必须共用一把锁)

① 实现Runnable接口的方式可以使用this,因为只有一个对象 Window1 w = new Window1();
② 继承Thread类的方式可以使用当前类 synchronized (Window2.class),类也是一个对象,只会加载一次;使用this则会代表t1,t2,t3多个对象。

class Window1 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票号" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
点击查看代码:继承Thread类的方式,ticket要用static修饰
class Window2 extends Thread {
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (Window2.class) {
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":售票,票号"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

方式二:使用同步方法,synchronized修饰方法

(1)同步方法中的同步监视器不需要显式声明
(2)非静态同步方法的同步监视器是this
(3)静态同步方法的同步监视器是当前类本身,因此继承Thread类的方式,同步方法要加static修饰,写为private static synchronized void show()。因为用不了this。

class Window3 implements Runnable {
    private int ticket = 100;
    private boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            show();
        }
    }

    private synchronized void show() {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":售票,票号" + ticket);
            ticket--;
        }else {
            flag = false;
        }

    }
}

public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

方式三:使用Lock锁,JDK5.0新增

创建对象ReentrantLock lock = new ReentrantLock();
手动上锁:lock.lock();
手动解锁:lock.unlock();

class Window implements Runnable {

    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();    //上锁,开启同步
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票号" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();    //解锁,关闭同步
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

synchronized与lock的异同

  • 相同:二者都可以解决线程安全问题
  • 不同:
    • synchronized在执行完同步代码后,自动释放同步监视器
    • Lock需要手动启动同步(lock()),结束同步也需要手动关闭同步(unlock())

优先使用顺序
① LOCK
② 同步代码块(已经进入了方法体,分配了相应资源)
③ 同步方法(在方法体之外)

posted @ 2022-10-27 09:40  植树chen  阅读(177)  评论(0编辑  收藏  举报