【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 @   植树chen  阅读(189)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示