6 实现 Callable 接口
Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口
Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁
6.1 创建线程的四种方式
无返回
- 实现Runnable接口,重写run()
- 继承Thread类,重写run()
有返回
- 实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数
-
6.2 Callable的使用
创建执行线程的方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常
- 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
```java
class ThreadDemo implements Callable
{ @Override public Integer call() throws Exception {
} }int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
// 1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
// 2.接收线程运算后的结果
try {
Integer sum = result.get(); // FutureTask 可用于 闭锁
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
---
<a name="3d06b53a"></a>
## 7 -Lock 同步锁
在 Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized 和 volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内 置锁不适用时,作为一种可选择的高级功能。<br />ReentrantLock 实现了 Lock 接口,并提供了与 synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。<br />解决多线程安全问题的三种方式
- jdk 1.5 前
- synchronized:隐式锁<br />1.同步代码块<br />2.同步方法
- jdk 1.5 后
- 3.同步锁 Lock:显式锁<br />注意:是一个显示锁,需要通过 lock() 方法上锁,必须通过 unlock() 方法进行释放锁
```java
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
}
}
class Ticket implements Runnable {
private int tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
lock.lock(); // 上锁
try {
if(tick > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()
+ " 完成售票,余票为:" + --tick);
}
}finally{
lock.unlock(); // 必须执行 因此放在finally中 释放锁
}
}
}
}
8 Condition 控制线程通信
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
8.1 使用Condition
使用Condition控制线程通信
- 如果不使用 synchronized 关键字保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用 wait() notify() notifyAll() 来进行线程通信了
- 当使用 lock 对象来保证同步时,Java提供了一个 Condition 类来保持协调,使用Condition可以让那些已经得到 lock 对象却无法继续执行的线程释放lock对象,Condition对象也可以唤醒其他处于等待状态的进程。
Condition 实例被绑定在一个 Lock 对象上。要获得 Lock 实例的 Condition 实例,调用 Lock 对象的newCondition() 方法即可
8.2 生产者和消费者案例
使用synchronized方式 ```java public class TestProductorAndConsumer { public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者D").start();
} }
class Clerk { // 店员 private int product = 0;
public synchronized void get() { // 进货
while (product >= 1) { // 为了避免虚假唤醒问题,应该总是使用在循环中而不用if
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
public synchronized void sale() { // 销售
while (product <= 0) { // 为了避免虚假唤醒问题,应该总是使用在循环中
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
class Productor implements Runnable { // 生产者 private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
class Consumer implements Runnable { // 消费者 private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
使用Lock方式
```java
public class TestProductorAndConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者D").start();
}
}
class Clerk {//店员
private int product = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void get() { // 进货
lock.lock();
try {
while (product >= 1) {
System.out.println("产品已满!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
condition2.signalAll();
} finally {
lock.unlock();
}
}
public void sale() { // 销售
lock.lock();
try {
while (product <= 0) {
System.out.println("缺货!");
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
class Productor implements Runnable { // 生产者
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
class Consumer implements Runnable { // 消费者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
8.3 线程按序交替
要求:编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要 求输出的结果必须按顺序显示。 如:ABCABCABC…… 依次递归
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
ad.loopA(i);
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
ad.loopB(i);
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
ad.loopC(i);
}
}
}, "C").start();
}
}
class AlternateDemo {
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
/**
* @param totalLoop 循环第几轮
*/
public void loopA(int totalLoop) {
lock.lock();
try {
if (number != 1) {
condition1.await();
}
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName()
+ "\t" + i + "\t" + totalLoop);
}
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopB(int totalLoop) {
lock.lock();
try {
if (number != 2) {
condition2.await();
}
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName()
+ "\t" + i + "\t" + totalLoop);
}
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopC(int totalLoop) {
lock.lock();
try {
if (number != 3) {
condition3.await();
}
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName()
+ "\t" + i + "\t" + totalLoop);
}
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
9 ReadWriteLock 读写锁
ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的
ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性 可以完全不需要考虑加锁操作
- 写写/读写 需要“互斥”
读读 不需要互斥 ```java public class TestReadWriteLock { public static void main(String[] args) {
ReadWriteLockDemo rw = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
rw.set((int)(Math.random() * 101));
}
}, "write").start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.get();
}
}).start();
}
} }
class ReadWriteLockDemo { private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock();
public void get() { // 读
lock.readLock().lock(); // 上锁
try {
System.out.println(Thread.currentThread().getName() + " : " + number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock(); // 释放锁
}
}
public void set(int number) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number = number;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
}
---
<a name="e1202f16"></a>
## 10 线程8锁
判断打印的 "one" or "two" ?
1. 两个普通同步方法,两个线程,标准打印, 打印结果?
1. 新增 Thread.sleep() 给 getOne(),打印结果?
1. 新增普通方法 getThree() , 打印结果?
1. 两个普通同步方法,两个 Number 对象,打印结果?
1. 修改 getOne() 为静态同步方法,打印结果?
1. 修改两个方法均为静态同步方法,一个 Number 对象,打印结果?
1. 一个静态同步方法,一个非静态同步方法,两个 Number 对象,打印结果?
1. 两个静态同步方法,两个 Number 对象,打印结果?
要想知道上面线程8锁的答案,需要知晓关键所在
- ① 非静态方法的锁默认为 this(实例对象), 静态方法的锁为对应的 Class 对象(类对象)
- ② 某一个时刻,同一个对象,只能有一个线程持有锁,无论几个方法
- ③ 锁静态方法,某一个时刻,不同实例对象也只能有一个对象持有锁
```java
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// number.getTwo();
number2.getTwo();
}
}).start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number {
public static synchronized void getOne() { // Number.class
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
System.out.println("one");
}
public synchronized void getTwo() { // this
System.out.println("two");
}
public void getThree() {
System.out.println("three");
}
}
答案
- 两个普通同步方法,两个线程,一个 Number 对象,标准打印, 打印结果? //one two
- 新增 Thread.sleep() 给 getOne() ,打印结果? // —过了3秒— one two
- 新增普通方法 getThree() , 打印结果? //three —过了3秒— one two
- 两个普通同步方法,两个 Number 对象,打印结果? //two —过了3秒— one
- 修改 getOne() 为静态同步方法,打印结果? //two —过了3秒— one
- 修改两个方法均为静态同步方法,一个 Number 对象,打印结果? //—过了3秒— one two
- 一个静态同步方法,一个非静态同步方法,两个 Number 对象,打印结果? //two —过了3秒— one
- 两个静态同步方法,两个 Number 对象,打印结果? //—过了3秒— one two