1 synchronized同步
public class Account {
private int balance;
public Account(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
/*
* add方法和withdraw方法不是同步方法的情况下:
* 两个线程操作同一个Account对象,并访问Account对象中add和withdraw方法时获取到的num值并不是实时的。保证不了数据的安全性。
* 同步方法的情况下:
* 同步方法,方法含有锁,那么这个锁就是该方法所在的Account类对象
* 当同一个Account对象调用add方法时就不会再执行withdraw方法,只有add方法释放掉Account类锁后才能执行withd方法
* 这样保证balance数据的安全性。
*
* 注意:每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才会有意义,
* 在并发环境下,一个没有共享的对象作为锁是没有意义的。
* 例如:每个线程中都创建一个Test对象,而Test对象中的同步方法是不起作用的。
*
* 专业描述synchronized(lock):
* 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,
* 就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,
* 当一个被线程被唤醒 (notify)后,才会进入到就绪队列,等待cpu的调度。
* 当一开始线程a第一次执行account.add方法时,jvm会检查锁对象account 的就绪队列是否已经有线程在等待,如果有则表明account的锁已经被占用了,由于是第一次运行,account的就绪队列为空,所以线程a获得了锁, 执行account.add方法。
* 如果恰好在这个时候,线程b要执行account.withdraw方法,因为线程a已经获得了锁还没有释放,所以线程 b要进入account的就绪队列,等到得到锁后才可以执行。
* 一个线程执行临界区代码过程如下:
* 1 获得同步锁
* 2 清空工作内存
* 3 从主存拷贝变量副本到工作内存
* 4 对这些变量计算
* 5 将变量从工作内存写回到主存
* 6 释放锁
* 可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
*
*
*/
public synchronized void add(int num) {
balance = balance + num;
}
public synchronized void withdraw(int num) {
balance = balance - num;
}
public static void main(String[] args) throws InterruptedException {
Account account = new Account(1000);
Thread a = new Thread(new AddThread(account, 20), "add");
Thread b = new Thread(new WithdrawThread(account, 20), "withdraw");
a.start();
b.start();
a.join();
b.join();
System.out.println(account.getBalance());
}
static class AddThread implements Runnable {
Account account;
int amount;
public AddThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
for (int i = 0; i < 200000; i++) {
account.add(amount);
}
}
}
static class WithdrawThread implements Runnable {
Account account;
int amount;
public WithdrawThread(Account account, int amount) {
this.account = account;
this.amount = amount;
}
public void run() {
for (int i = 0; i < 100000; i++) {
account.withdraw(amount);
}
}
}
}
2 join用法
线程的join方法:
子线程执行join方法,后主线程必须等待子线程执行完之后才能继续往下执行。
public class ThreadJoin extends Thread {
public void run() {
Thread aThread = new Thread(new Son());
Thread bThread = new Thread(new Son());
aThread.start();
bThread.start();
// try {
// aThread.join();
// bThread.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("主线程结束");
}
public static void main(String[] args) {
ThreadJoin threadJoin = new ThreadJoin();
threadJoin.start();
}
}
class Son extends Thread {
public void run() {
System.out.println("子线程执行");
}
}
子线程不使用join结果:
主线程结束
子线程执行
子线程执行
子线程使用join结果:
子线程执行
子线程执行
主线程结束
3 多线程保证数据安全性(生产者与消费者模式经典案例)
import java.util.ArrayList;
import java.util.List;
public class Plate {
List<Object> eggs = new ArrayList<Object>();
public synchronized Object getEgg() {
while (eggs.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
}
}
Object egg = eggs.get(0);
eggs.clear();// 清空盘子
notify();
System.out.println("拿到鸡蛋");
return egg;
}
public synchronized void putEgg(Object egg) {
while (eggs.size() > 0) {
try {
System.out.println("执行了putEgg中的wait方法");
wait();
} catch (InterruptedException e) {
}
}
eggs.add(egg);// 往盘子里放鸡蛋
System.out.println("执行了putEgg中的notify方法");
notify();
System.out.println("putEgg中的notfy方法执行结束");
System.out.println("放入鸡蛋");
}
static class AddThread extends Thread {
private Plate plate;
private Object egg = new Object();
public AddThread(Plate plate) {
this.plate = plate;
}
public void run() {
for (int i = 0; i < 5; i++) {
plate.putEgg(egg);
}
}
}
static class GetThread extends Thread {
private Plate plate;
public GetThread(Plate plate) {
this.plate = plate;
}
public void run() {
for (int i = 0; i < 5; i++) {
plate.getEgg();
}
}
}
public static void main(String args[]) {
try {
Plate plate = new Plate();
Thread add = new Thread(new AddThread(plate));
Thread get = new Thread(new GetThread(plate));
/**
* 线程执行过程:
* 有两个线程add线程和get线程同时共享Plate类对象。
* add.start(); // add线程首先进入就绪队列,等待执行
get.start(); // get线程进入就绪队列,等待执行
* add线程执行putEgg方法:
* add线程启动,执行putEgg方法时获取Plate类对象锁,此时get线程不能获取Plate类对象锁,因此执行不了getEgg方法。
* 当add线程执行putEgg方法中的notify()方法后,add线程的锁并没有释放掉,而是唤醒锁(Plate类对象)的阻塞队列的线程(程序开始并没有阻塞队列的线程)到就绪队列,putEgg继续执行,打印出放入鸡蛋
* 当add线程执行完putEgg方法方法后,释放掉Plate类锁对象,此时盘子中有一个鸡蛋。这时get线程有可能获取锁对象开始执行(要不要执行取决于cpu会不会分配资源给get线程)。
* add线程执行getEgg方法:
* 当add线程释放掉锁后,不一定执行get线程
* 1.如果仍然执行get线程,get线程获取锁执行getEgg方法,此时盘子中有鸡蛋,会执行clear方法,盘子中鸡蛋被清空,继续执行notify方法,将会唤醒add线程进入就绪队列,get线程继续执行,打印出拿到鸡蛋。释放掉锁,get线程执行结束。
* 2.如果执行add线程,add线程获取锁执行putEgg方法,此时盘子中有鸡蛋,会执行wait方法,这时add线程释放掉锁,进入阻塞队列等待get线程唤醒add线程,此时盘子中有鸡蛋,add线程执行结束。
* 这时get线程获取锁执行getEgg方法,清空盘子中的鸡蛋后,继续执行notify方法,add线程进入就绪队列,等待执行,get线程继续执行getEgg方法,打印出拿到鸡蛋,并释放掉锁,此时盘子清空了。
*/
add.start();
get.start();
add.join();
get.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("测试结束");
}
}
执行结果是有序的
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
执行了putEgg中的notify方法
putEgg中的notfy方法执行结束
放入鸡蛋
拿到鸡蛋
测试结束