Java多线程[3]:线程同步(互斥)

什么是线程同步

当两个或多个线程需要访问共享资源时,它们需要以某种方式确保每次只有一个线程使用资源,实现这一目的的过程称为线程同步。Java为线程同步提供了很好的支持。

监视器的概念常用来解决线程同步问题。监视器是用做互斥锁的对象。在任何时刻,只有一个线程可以拥有监视器。当线程取得锁时,也就进入了监视器。其它所有企图进入加锁监视器的线程都会被挂起,直到第一个线程退出监视器。

在Java中,可以使用两种方法来实现线程之间的同步,这两种方法都会用到synchronized关键字。

  1. 同步方法
  2. 同步语句块

不使用线程同步导致的问题

首先我们先给出一个应该使用线程同步而没有使用的应用场景。
首先,我们定义一个工具类叫PrintUtil,用来实现打印输出的功能

public class PrintUtil {
        public static void Print(String content) {
        System.out.print("[" + content);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("]");
    }
}

然后定义一个类NewThread,实现Runnable接口,这个类在run方法中调用了PrintUtil.Print()方法。

public class NewThread implements Runnable {

    Thread t;
    String message;

    public NewThread(String msg) {
        this.message = msg;
        t = new Thread(this, "my test thread");
        t.start();
    }

    @Override
    public void run() {
        PrintUtil.Print(message);
    }
}

下面是main方法。

public class Program {

    public static void main(String[] args) {
        NewThread obj1= new NewThread("hello");
        NewThread obj2= new NewThread("java");
        NewThread obj3= new NewThread("servlet");
        try {
            obj1.t.join();
            obj2.t.join();
            obj3.t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在main方法中,实例化了三个NewThread对象,也就启动了三个线程,每个线程的run方法都调用了PrintUtil.Print() 方法。由于没有做线程同步的处理,输出的结果如下:

[hello[java[servlet]
]
]

很明显,这不是我们想要的结果

使用同步方法来实现线程同步

我们必须要限制每次只能由一个线程的run方法来调用Print()方法,并且当该方法执行完毕之后下一个线程才能调用。在Java中,想做到这个是非常容易的,只需要在PrintUtil.Print() 方法前面加上一个关键字synchronized就能轻松搞定。修改后的PrintUtil类的代码如下

public class PrintUtil {
    synchronized public static void Print(String content) {
        System.out.print("[" + content);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("]");
    }
}

其它的地方无需修改。重新运行程序,结果如下:

[hello]
[servlet]
[java]

输出的顺序可能会不一样,但左右括号都是完整的。

使用同步语句块来实现线程同步

虽然在类中创建synchronized方法是一种比较容易并且行之有效的实现线程同步的方法,但是,它并不能适用于所有场景。例如:假设PrintUtil类不是由你设计的,你也不能修改它,肿么办?同步语句块诞生了!
首先,我们将PrintUtil.Print() 方法前面的synchronized关键字去掉,然后假装我们没有权限修改这个类。然后,我们来修改NewThread的run方法,修改后的NewThread类的代码如下

public class NewThread implements Runnable {

    Thread t;
    String message;
    //这个对象其实没有卵用,只是为了对它进行加锁,因为这个对象对于多个线程来说是同一个引用
    static Object obj = new Object();
    public NewThread(String msg) {
        this.message = msg;
        t = new Thread(this, "my test thread");
        t.start();
    }

    @Override
    public void run() {     
        synchronized (obj) {
            PrintUtil.Print(message);
        }
    }
}

重新运行代码,输出的结果是

[hello]
[servlet]
[java]

下面说一下synchronized关键字后面的括号中的对象。这个对象对于多个线程来说,必须是同一个引用,通常情况下是类外部的一个对象。如果在类内部的话,必须声明为静态的。总之,它不能属于当前线程对象,要么属于外部,要么属于当前类(静态)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,可以使用synchronized关键字和wait()、notify()、notifyAll()方法来解决多线程之间的同步互斥问题。具体实现步骤如下: 1. 定义一个共享资源类,其中包含需要互斥访问的方法。 2. 在需要互斥访问的方法前加上synchronized关键字,表示该方法是同步方法。 3. 在访问共享资源时,使用wait()方法将当前线程挂起,直到其他线程执行notify()或notifyAll()方法唤醒它。 4. 在访问共享资源完毕后,使用notify()或notifyAll()方法唤醒其他线程。 下面是一个简单的例子,实现两个线程交替打印奇偶数: ```java class SharedResource { private int number = 1; private boolean isOdd = true; public synchronized void printOdd() { while (!isOdd) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(number); number++; isOdd = false; notify(); } public synchronized void printEven() { while (isOdd) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(number); number++; isOdd = true; notify(); } } public class Main { public static void main(String[] args) { SharedResource sharedResource = new SharedResource(); Thread thread1 = new Thread(() -> { for (int i = 0; i < 5; i++) { sharedResource.printOdd(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 5; i++) { sharedResource.printEven(); } }); thread1.start(); thread2.start(); } } ``` 在此例子中,首先定义了一个共享资源类SharedResource,其中包含了两个同步方法printOdd()和printEven(),分别用于打印奇数和偶数。 在printOdd()方法中,首先使用while循环判断是否可以打印奇数,如果不能,则使用wait()方法将当前线程挂起,等待其他线程唤醒它。如果可以打印奇数,则打印并将number加1,然后将isOdd设置为false表示下一个应该打印偶数,并使用notify()方法唤醒其他线程。 在printEven()方法中同理,只不过判断条件和操作的变量不同。 最后,在主函数中创建两个线程,分别调用printOdd()和printEven()方法,实现了奇偶数的交替打印。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值