synchronized介绍
一、基本概念
synchronized关键字是java里面用来在多线程环境下保证线程安全的同步锁;java里面有对象锁和类锁,对象锁是用在对象实例的方法上或者一个对象实例上的,而类锁是用在一个类的静态方法上或者一个类的class对象上的。所以对于对象锁,不同的实例对象的对象锁不同,但是类锁只有一个,所有的对象实例共享这个类锁
二、synchronize使用场景
1、修饰类中的普通方法:在类中的普通方法上加上synchronized修饰,锁对象是调用当前同步方法的对象实例,线程在执行该方法时,首先需要拿到该对象锁。
2、修饰类中的静态方法:在类中的静态方法上加上synchronized修饰,锁对象是当前类的Class对象,线程在执行该方法时,首先需要拿到该类锁,一个类不同的对象实例的类锁是同一把锁。
3、修饰代码块:修饰代码块时,传入的锁对象可以是对象实例也可以是类的Class对象,分别对应1和2情况,但是synchronized修饰代码块比上面两中修饰方法有一个优点,就是颗粒度更小,可以只同步我们需要同步的部分代码,其他不需要同步的代码不会被同步。
三、生产者消费者实例
1、生产者:负责不断生产票,当存储票的仓库满了的时候则停止生产票,唤醒消费者消费票
/**
* 生成票 .
*/
public void provideTickets() {
synchronized (Tickets.class) {
// 不断生产票
while (true) {
// 唤醒沉睡线程(唤醒消费者)
Tickets.class.notify();
try {
//当前线程睡眠一秒钟,继续持有当前锁
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票满了 则停止生产
if (Tickets.tickets == Tickets.MAXNUMTICHETS) {
System.out.println(Thread.currentThread().getName() + "--->仓库满了,不能继续生产票了");
try {
Tickets.class.wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "try");
e.printStackTrace();
}
}
// 生产票 随机生产多张票,不需要一直生产到仓库满,模拟有票就可以卖
int num = new Random().nextInt(10);
while ((Tickets.tickets < Tickets.MAXNUMTICHETS) && (num >= 0)) {
Tickets.tickets++;
System.out.println(Thread.currentThread().getName() + "--->生产一张票,还有" + Tickets.tickets + "张票");
num--;
}
try {
//生产了多张票则放弃锁,让消费者消费
Tickets.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、消费者:负责不断消费票,当票被卖完了的时候唤醒生产者生产票。
/**
* 卖票 .
*/
public void saleTickets() {
synchronized (Tickets.class) {
// 不断买票
while (true) {
// 唤醒沉睡线程(唤醒生产生产票)
Tickets.class.notify();
try {
//睡眠一秒钟,不释放当前锁
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
// 如果票卖完,则通知生产票
if (Tickets.tickets == 0) {
System.out.println(Thread.currentThread().getName() + "--->票都没了,还让我们去买,骗子");
try {
Tickets.class.wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "try");
e.printStackTrace();
}
}
// 卖票 随机卖出多张票,不一定要票卖完才通知生产者
int num = new Random().nextInt(10);
while ((Tickets.tickets > 0) && (num > 0)) {
Tickets.tickets--;
System.out.println(Thread.currentThread().getName() + "--->票卖出一张,还剩" + Tickets.tickets + "张");
num--;
}
try {
//卖了多张票之后放弃锁,通知生产者生产票
Tickets.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3、主程序代码:
/**
* 票卖方 .
*
* @author 小柱
*
*/
public class Tickets {
/**
* 最大票数 .
*/
public final static int MAXNUMTICHETS;
/**
* 现有票数 .
*/
public static int tickets;
static {
MAXNUMTICHETS = 20;
tickets = 0;
}
public static void main(String[] args) {
// 票卖方
Tickets tickets = new Tickets();
// 生产者
Provider provider = new Provider(tickets);
// 消费者
Consumer consumer = new Consumer(tickets);
// 线程池启动生产者和消费者线程
ExecutorService eService = Executors.newFixedThreadPool(10);
eService.execute(provider);
eService.execute(consumer);
}
/**
* 卖票 .
*/
public void saleTickets() {
//对应上面消费票的方法
}
/**
* 生成票 .
*/
public void provideTickets() {
//对应上面生产票的方法
}
}
//-------------------------------------------------
/**
*票卖方线程 调用 消费票的动作.
*
* @author 小柱
*
*/
class Consumer implements Runnable {
/**
* 票卖方 .
*/
private Tickets tickes;
public Consumer(final Tickets tickets) {
this.tickes = tickets;
}
@Override
public void run() {
this.tickes.saleTickets();
}
}
/**
* 票生产方线程 调用生产票动作 .
*
* @author 小柱
*
*/
class Provider implements Runnable {
/**
* 票卖方 .
*/
private Tickets tickes;
public Provider(final Tickets tickets) {
this.tickes = tickets;
}
@Override
public void run() {
this.tickes.provideTickets();
}
}
部分运行结果:
pool-1-thread-1--->生产一张票,还有1张票
pool-1-thread-1--->生产一张票,还有2张票
pool-1-thread-1--->生产一张票,还有3张票
pool-1-thread-1--->生产一张票,还有4张票
pool-1-thread-1--->生产一张票,还有5张票
pool-1-thread-1--->生产一张票,还有6张票
pool-1-thread-1--->生产一张票,还有7张票
pool-1-thread-1--->生产一张票,还有8张票
pool-1-thread-1--->生产一张票,还有9张票
pool-1-thread-1--->生产一张票,还有10张票
pool-1-thread-1--->生产一张票,还有11张票
pool-1-thread-1--->生产一张票,还有12张票
pool-1-thread-1--->生产一张票,还有13张票
pool-1-thread-1--->生产一张票,还有14张票
pool-1-thread-1--->生产一张票,还有15张票
pool-1-thread-1--->生产一张票,还有16张票
pool-1-thread-1--->生产一张票,还有17张票
pool-1-thread-1--->生产一张票,还有18张票
pool-1-thread-2--->票卖出一张,还剩17张
pool-1-thread-2--->票卖出一张,还剩16张
pool-1-thread-2--->票卖出一张,还剩15张
pool-1-thread-1--->生产一张票,还有16张票
pool-1-thread-2--->票卖出一张,还剩15张
pool-1-thread-2--->票卖出一张,还剩14张
pool-1-thread-2--->票卖出一张,还剩13张
pool-1-thread-2--->票卖出一张,还剩12张
pool-1-thread-2--->票卖出一张,还剩11张
pool-1-thread-2--->票卖出一张,还剩10张
pool-1-thread-2--->票卖出一张,还剩9张
pool-1-thread-2--->票卖出一张,还剩8张
pool-1-thread-2--->票卖出一张,还剩7张
pool-1-thread-1--->生产一张票,还有8张票
pool-1-thread-1--->生产一张票,还有9张票
pool-1-thread-1--->生产一张票,还有10张票
pool-1-thread-1--->生产一张票,还有11张票
pool-1-thread-2--->票卖出一张,还剩10张
pool-1-thread-2--->票卖出一张,还剩9张
pool-1-thread-2--->票卖出一张,还剩8张
pool-1-thread-2--->票卖出一张,还剩7张
pool-1-thread-2--->票卖出一张,还剩6张
四、注意点
调用notify()和wait()方法必须在synchronized块里面调用;每次调用wait()方法等待的线程被唤醒之后,是接着wait()方法后面的代码继续执行,并非重新进入同步代码块;而且调用的这两个方法的对象一定是当前synchronized同步的锁对象,不然程序会报错,如下:
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2" java.lang.IllegalMonitorStateException
at java.base/java.lang.Object.notify(Native Method)
at com.concurent.test.Tickets.saleTickets(Tickets.java:55)
at com.concurent.test.Consumer.run(Tickets.java:164)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1135)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:844)
报错原因附上连接,我找到的解释报错原因解释的最好的一篇文章:
https://cloud.tencent.com/developer/article/1602598
总结
以上就是java中使用synchronized实现多线程同步安全的生产者消费者例子,注意的是,这种同步锁只能用在一个运行在一个虚拟机里面的多线程,如果是不同的虚拟机运行的多线程的同步安全需要用到分布式锁,这个稍微复杂一些。