t16.2
线程3种方式
Thread
Runnable
Callable
FutureTask实现类
callable中的get() 将导致程序阻塞,必须等到子线程结束后才会得到返回值
线程状态:
新建New
就绪Runnable
运行Running
阻塞Blocked
死亡Dead
new->runnable:
start()
runnable->blocked:
sleep时间到
IO方法返回
获得同步锁
收到通知
resume()
blocked->runnable:
sleep()
IO阻塞
等待同步锁
等待通知
suspend()
runnable->running:
得到处理器资格
running->runnable:
yield() 或 失去处理器资格
running->dead:
stop() error exception run/call()完成
join()方法:优先子线程,阻塞主线程
1.1 main
1.2 sub.start()
1.3 sub2.start()
....
1.1.1 sub2.join()
1.3.2 ...
1.4...
package com.lee.test.ligang.unit16.t4.status;
import java.util.Date;
public class JoinThread extends Thread {
// 提供一个有参数的构造器,用于设置该线程的名字
public JoinThread(String name) {
super(name);
}
// 重写run()方法,定义线程执行体
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws Exception {
// 启动子线程
new JoinThread("新线程").start();
for (int i = 0; i < 100; i++) {
if (i == 20) {
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
// main线程调用了jt线程的join()方法,main线程
// 必须等jt执行结束才会向下执行
System.out.println(new Date());
sleep(5000);
System.out.println(new Date());
// 等同调用无限大sleep,直到子线程结束,sleep效果才消失
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
.setDaemon(true):将某个线程设置为后台线程。当主线程或者后台所在的前台线程结束后,后台线程也结束。
package com.lee.test.ligang.unit16.t4.status;
public class DaemonThread extends Thread {
// 定义后台线程的线程执行体与普通线程没有任何区别
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
DaemonThread t = new DaemonThread();
// 将此线程设置成后台线程
t.setDaemon(true);
// 启动后台线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
// -----程序执行到此处,前台线程(main线程)结束------
// 后台线程也应该随之结束
}
}
.yield():假sleep,不足赛线程。根据优先级更高的yield将最先被调度执行
同步机制
1.同步代码块 即 以对象作为同步监视器
2.同步方法 即 同步监视器是this
3.Lock 更灵活
死锁:
1.A a; B b两个对象
3.b传入aM的同步方法中,a作为同步监视器被锁定
3.a进入bM的同步方法中,b作为同步监视器被锁定
4.调用b.m方法,b被锁定需等待b释放锁才能进行,当前线程阻塞
5.调用a.m方法,a被锁定需等待a释放锁才能进行,当前线程阻塞
t16.6.1 传统通信线程
package com.lee.test.ligang.unit16.t6.synchronized_1;
public class Account {
// 封装账户编号、账户余额的两个成员变量
private String accountNo;
private double balance;
// 标识账户中是否已有存款的旗标
private boolean flag = false;
public Account() {
}
// 构造器
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
// accountNo的setter和getter方法
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public String getAccountNo() {
return this.accountNo;
}
// 因此账户余额不允许随便修改,所以只为balance提供getter方法,
public double getBalance() {
return this.balance;
}
public synchronized void draw(double drawAmount) {
try {
// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
if (!flag) {
wait();
} else {
// 执行取钱
System.out.println(Thread.currentThread().getName() + " 取钱:"
+ drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:" + balance);
// 将标识账户是否已有存款的旗标设为false。
flag = false;
// 唤醒其他线程
notifyAll();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
public synchronized void deposit(double depositAmount) {
try {
// 如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
if (flag) // ①
{
wait();
} else {
// 执行存款
System.out.println(Thread.currentThread().getName() + " 存款:"
+ depositAmount);
balance += depositAmount;
System.out.println("账户余额为:" + balance);
// 将表示账户是否已有存款的旗标设为true
flag = true;
// 唤醒其他线程
notifyAll();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
// 下面两个方法根据accountNo来重写hashCode()和equals()方法
public int hashCode() {
return accountNo.hashCode();
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj != null && obj.getClass() == Account.class) {
Account target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
package com.lee.test.ligang.unit16.t6.synchronized_1;
public class DrawTest {
public static void main(String[] args) {
// 创建一个账户
Account acct = new Account("1234567", 0);
new DrawThread("取钱者", acct, 800).start();
new DepositThread("存款者甲", acct, 800).start();
new DepositThread("存款者乙", acct, 800).start();
new DepositThread("存款者丙", acct, 800).start();
}
}
package com.lee.test.ligang.unit16.t6.synchronized_1;
public class DepositThread extends Thread {
// 模拟用户账户
private Account account;
// 当前取钱线程所希望存款的钱数
private double depositAmount;
public DepositThread(String name, Account account, double depositAmount) {
super(name);
this.account = account;
this.depositAmount = depositAmount;
}
// 重复100次执行存款操作
public void run() {
for (int i = 0; i < 100; i++) {
account.deposit(depositAmount);
}
}
}
package com.lee.test.ligang.unit16.t6.synchronized_1;
public class DrawThread extends Thread {
// 模拟用户账户
private Account account;
// 当前取钱线程所希望取的钱数
private double drawAmount;
public DrawThread(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
// 重复100次执行取钱操作
public void run() {
for (int i = 0; i < 100; i++) {
account.draw(drawAmount);
}
}
}
t16.6.2
Condition控制线程通信
package com.lee.test.ligang.unit16.t6.t2.condition;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
// 显式定义Lock对象
private final Lock lock = new ReentrantLock();
// 获得指定Lock对象对应的Condition
private final Condition cond = lock.newCondition();
// 封装账户编号、账户余额的两个成员变量
private String accountNo;
private double balance;
// 标识账户中是否已有存款的旗标
private boolean flag = false;
public Account() {
}
// 构造器
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
// accountNo的setter和getter方法
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public String getAccountNo() {
return this.accountNo;
}
// 因此账户余额不允许随便修改,所以只为balance提供getter方法,
public double getBalance() {
return this.balance;
}
public void draw(double drawAmount) {
// 加锁
lock.lock();
try {
// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
if (!flag) {
cond.await();
} else {
// 执行取钱
System.out.println(Thread.currentThread().getName() + " 取钱:"
+ drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:" + balance);
// 将标识账户是否已有存款的旗标设为false。
flag = false;
// 唤醒其他线程
cond.signalAll();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// 使用finally块来释放锁
finally {
lock.unlock();
}
}
public void deposit(double depositAmount) {
lock.lock();
try {
// 如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
if (flag) // ①
{
cond.await();
} else {
// 执行存款
System.out.println(Thread.currentThread().getName() + " 存款:"
+ depositAmount);
balance += depositAmount;
System.out.println("账户余额为:" + balance);
// 将表示账户是否已有存款的旗标设为true
flag = true;
// 唤醒其他线程
cond.signalAll();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// 使用finally块来释放锁
finally {
lock.unlock();
}
}
// 下面两个方法根据accountNo来重写hashCode()和equals()方法
public int hashCode() {
return accountNo.hashCode();
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj != null && obj.getClass() == Account.class) {
Account target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
package com.lee.test.ligang.unit16.t6.t2.condition;
public class DepositThread extends Thread {
// 模拟用户账户
private Account account;
// 当前取钱线程所希望存款的钱数
private double depositAmount;
public DepositThread(String name, Account account, double depositAmount) {
super(name);
this.account = account;
this.depositAmount = depositAmount;
}
// 重复100次执行存款操作
public void run() {
for (int i = 0; i < 100; i++) {
account.deposit(depositAmount);
}
}
}
package com.lee.test.ligang.unit16.t6.t2.condition;
public class DrawTest {
public static void main(String[] args) {
// 创建一个账户
Account acct = new Account("1234567", 0);
new DrawThread("取钱者", acct, 800).start();
new DepositThread("存款者甲", acct, 800).start();
new DepositThread("存款者乙", acct, 800).start();
new DepositThread("存款者丙", acct, 800).start();
}
}
package com.lee.test.ligang.unit16.t6.t2.condition;
public class DrawThread extends Thread {
// 模拟用户账户
private Account account;
// 当前取钱线程所希望取的钱数
private double drawAmount;
public DrawThread(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
// 重复100次执行取钱操作
public void run() {
for (int i = 0; i < 100; i++) {
account.draw(drawAmount);
}
}
}
【原则】不同的线程,装在着同一个对象,则这个对象应有Lock成员变量来保证不同的线程持有同一个对象锁即同步监视器,Condition来保证
t16.6.3 阻塞队列 BlockingQueue 控制线程通信
Array
Linked
Prority
Synchronous
Delay
package com.lee.test.ligang.unit16.t6.t3.blockqueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer extends Thread {
private BlockingQueue<String> bq;
public Producer(BlockingQueue<String> bq) {
this.bq = bq;
}
public void run() {
String[] strArr = new String[] { "Java", "Struts", "Spring" };
for (int i = 0; i < 999999999; i++) {
System.out.println(getName() + "生产者准备生产集合元素!");
try {
Thread.sleep(200);
// 尝试放入元素,如果队列已满,线程被阻塞
bq.put(strArr[i % 3]);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(getName() + "生产完成:" + bq);
}
}
}
class Consumer extends Thread {
private BlockingQueue<String> bq;
public Consumer(BlockingQueue<String> bq) {
this.bq = bq;
}
public void run() {
while (true) {
System.out.println(getName() + "消费者准备消费集合元素!");
try {
Thread.sleep(200);
// 尝试取出元素,如果队列已空,线程被阻塞
bq.take();
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println(getName() + "消费完成:" + bq);
}
}
}
public class BlockingQueueTest2 {
public static void main(String[] args) {
// 创建一个容量为1的BlockingQueue
BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
// 启动3条生产者线程
new Producer(bq).start();
new Producer(bq).start();
new Producer(bq).start();
// 启动一条消费者线程
new Consumer(bq).start();
}
}
Thread-2生产者准备生产集合元素!
Thread-1生产者准备生产集合元素!
Thread-0生产者准备生产集合元素!
Thread-3消费者准备消费集合元素!
Thread-0生产完成:[Java]
Thread-1生产完成:[Java]
Thread-0生产者准备生产集合元素!
Thread-1生产者准备生产集合元素!
Thread-3消费完成:[Java]
Thread-3消费者准备消费集合元素!
Thread-3消费完成:[]
Thread-3消费者准备消费集合元素!
Thread-2生产完成:[Java]
Thread-2生产者准备生产集合元素!
Thread-3消费完成:[]
t16.7 ThreadGroup 线程组和未处理的异常
t16.8 线程池
Executors
Executor
ExecutorService extends Executor
ScheduledExecutorService extends ExecutorService
package com.lee.test.ligang.unit16.t8.t1.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) throws Exception {
// 创建足够的线程来支持4个CPU并行的线程池
// 创建一个具有固定线程数(6)的线程池
ExecutorService pool = Executors.newFixedThreadPool(6);
// 使用Lambda表达式创建Runnable对象
Runnable target = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "的i值为:"
+ i);
}
};
// 向线程池中提交两个线程
pool.submit(target);
pool.submit(target);
// 关闭线程池
pool.shutdown();
}
}
package com.lee.test.ligang.unit16.t8.t2.forkjoinpool;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;
// 继承RecursiveAction来实现"可分解"的任务
class PrintTask extends RecursiveAction {
// 每个“小任务”只最多只打印50个数
private static final int THRESHOLD = 50;
private int start;
private int end;
// 打印从start到end的任务
public PrintTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
// 当end与start之间的差小于THRESHOLD时,开始打印
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
System.out.println(Thread.currentThread().getName() + "的i值:"
+ i);
}
} else {
// 如果当end与start之间的差大于THRESHOLD时,即要打印的数超过50个
// 将大任务分解成两个小任务。
int middle = (start + end) / 2;
PrintTask left = new PrintTask(start, middle);
PrintTask right = new PrintTask(middle, end);
// 并行执行两个“小任务”
left.fork();
right.fork();
}
}
}
public class ForkJoinPoolTest {
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool();
// 提交可分解的PrintTask任务
pool.submit(new PrintTask(0, 300));
pool.awaitTermination(2, TimeUnit.SECONDS);
// 关闭线程池
pool.shutdown();
}
}
package com.lee.test.ligang.unit16.t8.t2.forkjoinpool;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
// 继承RecursiveTask来实现"可分解"的任务
class CalTask extends RecursiveTask<Integer> {
// 每个“小任务”只最多只累加20个数
private static final int THRESHOLD = 100;
private int arr[];
private int start;
private int end;
// 累加从start到end的数组元素
public CalTask(int[] arr, int start, int end) {
this.arr = arr;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
int sum = 0;
// 当end与start之间的差小于THRESHOLD时,开始进行实际累加
if (end - start < THRESHOLD) {
for (int i = start; i < end; i++) {
sum += arr[i];
}
return sum;
} else {
// 如果当end与start之间的差大于THRESHOLD时,即要累加的数超过20个时
// 将大任务分解成两个小任务。
int middle = (start + end) / 2;
CalTask left = new CalTask(arr, start, middle);
CalTask right = new CalTask(arr, middle, end);
// 并行执行两个“小任务”
left.fork();
right.fork();
// 把两个“小任务”累加的结果合并起来
int left1 = left.join();
int right1 = right.join();
return left1 + right1; // ①
}
}
}
public class Sum {
public static void main(String[] args) throws Exception {
int[] arr = new int[100];
Random rand = new Random();
int total = 0;
// 初始化100个数字元素
for (int i = 0, len = arr.length; i < len; i++) {
int tmp = rand.nextInt(20);
// 对数组元素赋值,并将数组元素的值添加到sum总和中。
total += (arr[i] = tmp);
}
System.out.println(total);
// 创建一个通用池
ForkJoinPool pool = ForkJoinPool.commonPool();
// 提交可分解的CalTask任务
Future<Integer> future = pool.submit(new CalTask(arr, 0, arr.length));
System.out.println(future.get());
// 关闭线程池
pool.shutdown();
}
}
t16.9 线程相关类
t16.9.1 ThreadLocal 线程局部变量 ThreadLocalVar
隔离多线程的竞争资源
ThreadLocal:将需要并发的资源复制成多个副本,每个线程拥有一份资源,即自己的副本也就没有必要对该变量进行同步了。
对比---
同步机制:为了同步多个线程对相同资源的并发访问;若多线程之间需要共享资源达到通信则用该方式
ThreadLocal:为了隔离多个线程的数据共享;若仅需要隔离多个线程的共享冲突则用该方式
t16.9.2 集合线程安全
Collections
.synchronizedCollection
ConcurrentHashMap 线程安全的集合类