主要包括实现同步的两种方式,以及同步锁(生产者消费者模式),静态锁,死锁(左右筷子死锁)
一、同步
并发访问的问题:
有时两个或多个线程可能会试图同时访问一个资源
例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据。
例如,100个线程同时往一个帐户存钱。
此情况下,数据可能会变得不一致,解决方法——同步
概念:
当两个或多个线程需要访问同一资源时,它们需要以某种顺序来确保该资源某一时刻只能被一个线程使用的方式称为同步。
同步的实现:
同步基于“监视器”这一概念。
“监视器”是用作互斥锁的对象;在给定时刻,只有一个线程可以拥有监视器。
Java中所有的对象都拥有自己的监视器。
1.使用同步方法:
synchronized void methodA() { }
2.使用同步块:(同步块需要指定“同步监视器”,即上锁的对象)
synchronized(object) {
class Account {
private double count;
public double getCount() {
return count;
}
public void setCount(double count) {
this.count = count;
}
//public synchronized void add(double money){}//同步方法
public void add(double money) {
synchronized (this) {//同步代码块,参数可以是任意对象,但是必须是同一个对象
String name=Thread.currentThread().getName();//获取当前线程名称
System.out.println(name+"正在存钱,当前账户余额为"+count);
double temp=count;
count=temp+money;
System.out.println(name+"存完钱了,当前余额为"+count);
}
}
}
class AccountThread extends Thread{//存钱线程
Account account;//建立account账户
public AccountThread(Account account) {//构造方法初始化account账户
this.account=account;
}
@Override
public void run() {
account.add(100);
}
}
public class TestAccount {
public static void main(String[] args) throws InterruptedException {
Account account=new Account();
for(int i=0;i<100;i++) {//创建100个线程往账户存钱
new AccountThread(account).start();
}
Thread.sleep(1000);
System.out.println("存钱完毕,当前余额为"+account.getCount());
}
}
二、锁
- java中每一个对象都有一个lock
- 当访问一个对象的synchronized方法的时候,该对象就会被上锁
注意:被上锁的是对象,而不是方法,举例来说,如果一个对象中有多个synchronized方法,如果线程访问到其中的一个synchronized方法时,此时不能同步访问其他的方法
Java中的同步“锁”:同步锁、静态锁、死锁
1.同步锁(synchronized的互斥机制)
当synchronized被加在多个方法或者成员变量的时候,并且我们用多个线程去访问任意一个用synchronized修饰的方法或者变量的时候,保证有且仅有一个线程在同一个时刻能够访问到synchronized资源。剩余的线程需要等带同步锁对象解锁之后才能够访问synchronized资源。
//生产者消费者模式。生产者负责生产商品和销售商品
public class Factory {
Stack<Integer> stack=new Stack<>();
final int MAX_SIZE=5;
//生产的方法
public synchronized void add(int i) throws InterruptedException {
if(stack.size()==MAX_SIZE) {
System.out.println("这是生产的方法");
System.out.println("生产者停止生产");
this.wait();//让当前正在执行的线程等待
System.out.println("通知消费者消费");
this.notify();//唤醒当前正在等待的线程
}else {
Integer num=stack.push(i);
System.out.println("当前正在生产第"+num+"个产品,库存为"+stack.size());
}
}
//消费的方法
public synchronized void sub() throws InterruptedException {
if(stack.size()==0) {
System.out.println("这是消费的方法");
System.out.println("唤醒生产者开始生产");
this.notify();
System.out.println("消费者停止消费");
this.wait();
}else {
Integer i=stack.pop();
System.out.println("当前正在消费第"+i+"个商品,当前库存为"+stack.size());
}
}
}
//生产者
public class Producer extends Thread{
Factory factory;
public Producer(Factory factory) {
super();
this.factory=factory;
}
public void run() {
for(int i=1;i<=50;i++) {
try {
factory.add(i);//生产者调用add方法生产商品
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//消费者
public class Custom extends Thread{
Factory factory;
public Custom(Factory factory) {
super();
this.factory=factory;
}
public void run() {
for(int i=1;i<=50;i++) {
try {
factory.sub();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//消费者调用sub方法消费
}
}
}
public class Test {
public static void main(String[] args) {
//初始化工厂类
Factory factory=new Factory();
//创建生产者线程
new Producer(factory).start();
//创建消费者线程
new Custom(factory).start();
}
}
2.静态锁:在静态方法中使用同步锁
如果在静态方法上使用synchronized修饰,该方法具有同步的效果,但是该锁和对象无关
Synchronized和static synchronized的区别:
synchronized相当于是this.synchronized,对象在访问synchronized资源的时候,会生成一个该类实例的监视块
static synchronized相当于是类.synchronized,所有该类的实例共用一个监视块
class Demo{}
class Printer{
public static synchronized void print1() {
System.out.print("好");
System.out.print("好");
System.out.print("学");
System.out.print("习\n");
}
public static void print2() {
synchronized (Printer.class) {
System.out.print("注");
System.out.print("意");
System.out.print("休");
System.out.print("息\n");
}
}
}
public class StaticLock {
public static void main(String[] args) {
Printer printer=new Printer();//创建printer对象
//开启两个线程
new Thread() {
@Override
public void run() {
while(true) {
printer.print1();
}
}
}.start();
new Thread() {
@Override
public void run(){
while(true) {
printer.print2();
}
}
}.start();
}
}
3、死锁:当两个线程循环依赖于一对同步对象时将发生死锁。
//当多个线程同时依赖一个资源的时候会产生死锁。
//多个同步代码块嵌套会导致死锁
public class DealLock {
static String left="筷子左";
static String right="筷子右";
public static void main(String[] args) {
new Thread() {
public void run(){
while(true) {
synchronized (left) {
System.out.println(this.getName()+"拿到了"+left+",等待"+right);
synchronized (right) {
System.out.println(this.getName()+"拿到了"+right+",开始吃饭");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized (right) {
System.out.println(this.getName()+"拿到了"+right+",等待"+left);
synchronized (left) {
System.out.println(this.getName()+"拿到了"+left+",开始吃饭");
}
}
}
}
}.start();
}
}