线程的基础知识(四)

本博客整理来源于《疯狂java讲义》。

线程同步

  1. 因为run()方法的方法体不具有同步安全性,为了解决这个问题,Java的多线程引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。格式如下:
synchronized (obj) {
				...
				//同步代码块
}
  1. 任何时刻只有一个线程可以获得对同步监视器的锁定,当同步代码块执行完毕后,该线程会释放对该同步监视器的锁定。通常推荐使用可能被并发访问的共享资源充当同步监视器。
  2. 与同步代码块对应的是同步方法。对于synchronized修饰的实例方法而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。

关于释放同步监视器的锁定:

线程会在如下几种情况下释放锁:

  1. 当前线程的同步方法,同步代码块执行结束;
  2. 当前线程的同步方法,同步代码块遇到break,return终止了改代码块,该方法的继续执行;
  3. 同步代码块,同步方法中出现了error或Exception,导致改代码块,该方法异常结束;
  4. 同步代码块,同步方法中执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

线程不会释放锁的情况:

  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。

同步锁:Lock

从Java 5 开始,Java提供了一种功能更强大的线程同步机制:通过显式定义同步锁对象来实现同步,在这种机制下,同步锁有Lock对象充当。
代码演示如下:

package com.dalingjia.thead;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Account {

	//定义锁对象(可重入锁),避免使用对象锁
	private final Lock lock = new ReentrantLock();
	
	private String accountNo;
	private Double balance;
	
	public String getAccountNo() {
		return accountNo;
	}
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	
	public Double getBalance() {
		return balance;
	}
	
	public Account(String accountNo, Double balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public Account() {
		super();
	}

	//提供一个线程安全的draw()方法来完成取钱操作
	public void draw(Double drawAmount){
		//加锁
		lock.lock();
		try {
			if (balance >= drawAmount) {
				System.out.println(Thread.currentThread().getName() + "取钱成功,吐钞票:" + drawAmount);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				balance -= drawAmount;
				System.out.println("余额为:" + balance);
			} else {
				System.out.println("余额不足,取钱失败");
			}
			//修改完成后,释放锁
		} finally {
			lock.unlock();
		}
	}
}

package com.dalingjia.thead;

public class AmountTest extends Thread{

	//创建一个账户
	private static final Account ACCOUNT = new Account("100001",10000.00);
	
	public AmountTest(String name){
		super(name);
	}
	
	public void run(){
		ACCOUNT.draw(8000.00);
	}
	
	public static void main(String[] args) {
		new AmountTest("甲").start();
		new AmountTest("乙").start();
	}
}
  1. 使用Lock与使用同步方法有点相似,只是使用Lock时显式使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器。
  2. ReentrantLock锁具有可重入性,即一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪Lock方法的嵌套调用,线程在每次调用lock()加锁后,必须显示调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

死锁:

当两个线程相互等待对方释放同步监视器时就会发生死锁,代码演示如下:

package com.dalingjia.thead;

public class DeadLockDemo {
    private static final String A = "A";
    private static final String B = "B";

    private static void deadLock(){
        Thread thread1 = new Thread(() -> {
            synchronized (A){
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("1");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (B){
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){
                    System.out.println("2");
                }
            }
        });
        thread1.start();
        thread2.start();
    }

    public static void main(String[] args) {
        DeadLockDemo.deadLock();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值