练习1:三个售票窗口同时出售20张票
public class Sale extends Thread {
private String name;
private static int num = 20;
private static Object lock = new Object();
public Sale(String name) {
this.name = name;
}
@Override
public void run() {
while (num > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (num > 0) {
System.out.println(name + "售出第" + num + "张票...");
num--;
} else
System.out.println("票已经售尽...");
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Sale("第" + (i + 1) + "个窗口").start();
}
}
}
练习2:两个人AB通过一个账户A在柜台取钱和B在ATM机取钱
分析:两个人AB实际上就是两个线程,操作同一个账号实际上为了避免出现问题则必须使用锁
public class Account {
private double balance=10000;
public synchronized void sub(double amount) {
if (balance > amount) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = balance - amount;
}
}
}
public class Sub extends Thread {
private Account account;
private double amount;
public Sub(Account a, double am) {
this.account = a;
this.amount = am;
}
@Override
public void run() {
account.sub(amount);
}
}
练习3:过山洞问题:请按要求编写多线程应用程序,模拟多个人通过一个山洞:
1、这个山洞每次只能通过一个人,每个人通过山洞的时间为2秒;
2、随机生成10个人,同时准备过此山洞,并且定义一个变量用于记录通过人的信息
public class 山洞 {
private StringBuffer logs = new StringBuffer();
private int num=0;
private int counter =0;
private static final Object lock=new Object();
public void 过山洞(){
logs.append("第"+(++num)+"人"+Thread.currentThread().getName()+"到达山洞\n");
synchronized (lock) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
logs.append("第"+(++counter)+"人"+Thread.currentThread().getName()+"穿过山洞\n");
}
}
public void show(){
System.out.println(logs);
}
}
public class Test山洞 {
public static void main(String[] args) {
String[] arr = new String[] { "Rose", "Black", "Tomos", "KKK", "AAA", "CCC" };
Thread[] ts = new Thread[arr.length];
Random r = new Random();
山洞 shan = new 山洞();
for (int i = 0; i < ts.length; i++) {
String name = null;
while (name == null) {
int pos = r.nextInt(arr.length);
String temp = arr[pos];
if (temp != null) {
name = temp;
arr[pos] = null;
}
}
ts[i] = new Thread(() -> {
shan.过山洞();
}, name);
ts[i].start();
}
for (Thread temp : ts) {
if (temp != null) {
try {
temp.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
shan.show();
}
}
练习4:模拟实现乌龟和兔子赛跑
总长2000米
规则:兔子每 0.1 秒 5 米的速度,每跑20米休息1秒
乌龟每 0.1 秒跑 2 米,不休息
当有一方到达终点时,另外一方立即终止。
public abstract class Animals extends Thread {
public static final int 总长度 = 150;
protected int 剩余长度 = 总长度;
protected 结束接口 end = null;
@Override
public void run() {
while (剩余长度 > 0)
跑步();
}
public abstract void 跑步();
static interface 结束接口 {
void 结束();
}
public void setEnd(结束接口 end) {
this.end = end;
}
}
public class Rabbit extends Animals {
@Override
public void 跑步() {
int dis = 5;
剩余长度 -= dis;
System.out.println("兔子跑了" + dis + "米,距离终点还有" + 剩余长度 + "米");
if (剩余长度 <= 0) {
剩余长度 = 0;
System.out.println("兔子胜利了");
if (end != null) {
end.结束();
}
}
try {
if ((总长度 - 剩余长度) % 50 == 0) {
sleep(1100);
} else
sleep(100);
} catch (Exception e) {
}
}
public class Tortoise extends Animals {
@Override
public void 跑步() {
int dis = 2;
剩余长度 -= dis;
System.out.println("乌龟跑了" + dis + "米,距离终点还有" + 剩余长度 + "米");
if (剩余长度 <= 0) {
剩余长度 = 0;
System.out.println("乌龟胜利了");
if (end != null) {
end.结束();
}
}
try {
sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Rabbit tuzi = new Rabbit();
Tortoise wugui = new Tortoise();
tuzi.setEnd(new 结束接口的实现类(wugui));
wugui.setEnd(new 结束接口的实现类(tuzi));
tuzi.start();
wugui.start();
}
}
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印 10次ABC
public class T1 {
public static void main(String[] args) throws Exception {
Object a = 123;
Object b = 456;
Object c = 789;
Thread aa = new MyThread("A", c, a);
Thread bb = new MyThread("B", a, b);
Thread cc = new MyThread("C", b, c);
aa.start();
Thread.sleep(100);
bb.start();
Thread.sleep(100);
cc.start();
}
}
class MyThread extends Thread {
private String name;
private Object prve;
private Object self;
public MyThread(String name, Object prve, Object self) {
this.name = name;
this.prve = prve;
this.self = self;
}
@Override
public void run() {
int count = 20;
while (count > 0) {
synchronized (prve) {
synchronized (self) {
System.out.println(name);
count--;
self.notify();
}
try {
prve.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Lock的使用
Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。本质上Lock仅仅是一个接口,可以通过显式定义同步锁对象来实现同步,能够提供比synchronized更广泛的锁定操作,并支持多个相关的Condition对象
- void lock();尝试获取锁,获取成功则返回,否则阻塞当前线程
void lockInterruptibly() throws InterruptedException;尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常
boolean tryLock();尝试获取锁,获取锁成功则返回true,否则返回false
boolean tryLock(long time, TimeUnit unit)尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常 - void unlock();释放锁
- Condition newCondition();返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
Lock有三个实现类,一个是ReentrantLock,另两个是ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock。
使用方法:多线程下访问(互斥)共享资源时, 访问前加锁,访问结束以后解锁,解锁的操作推荐放入finally块中。
private final ReentrantLock lock=new ReentrantLock();
在具体方法中lock.lock() try{}finally{lock.unlock}
样例1:启动4个线程,对一个int数字进行各50次加减操作,要求2个加,2个减,保证输出的线程安全
public class OperNum {
private int num = 0;
private final static Lock lock = new ReentrantLock(); // 构建锁对象
public void add() {
try {
lock.lock(); // 申请加锁操作,如果能加上则立即返回,否则阻塞当前线程
num++;
System.out.println(Thread.currentThread().getName() + "add..." + num);
} finally {
lock.unlock(); // 具体实现采用的是重入锁,所以持有锁的线程可以多次申请同一个锁,但是申请加锁次数必须和释放锁的次数一致
}
}
public void sub() {
try {
lock.lock();
num--;
System.out.println(Thread.currentThread().getName() + "sub..." + num);
} finally {
lock.unlock();
}
}
}
写2个线程,其中一个打印1-52,另一个打印a-z,打印顺序应该是12a34b56c……5152线程1执行2次数后,线程2执行1次输出
public class T1 {
private static final Lock lock = new ReentrantLock();
private static final Condition conNum = lock.newCondition();
private static final Condition conChar = lock.newCondition();
private boolean flag = false;
public void printNum() {
try {
lock.lock();
for (int i = 1; i <= 52; i++) {
while (flag)
try {
conNum.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(i);
if (i % 2 == 0) {
flag = !flag;
conChar.signal();
}
}
} finally {
lock.unlock();
}
}
public void printChar() {
try {
lock.lock();
for (int i = 'a'; i <= 'z'; i++) {
while (!flag)
try {
conChar.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print((char) i);
flag = !flag;
conNum.signal();
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
T1 tt = new T1();
new Thread(() -> {
tt.printNum();
}).start();
new Thread(() -> {
tt.printChar();
}).start();
}
}