线程通信(代码几乎不用)
经典问题:生产者与消费者问题
生产不能过剩,消费不能停止(消息队列)
this.notifyAll()
this.wait()
线程状态
public class Thread {
public static enum State {
NEW,
RUNNABLE, // 可运行
BLOCKED, // 锁阻塞
WAITING, // 无限等待
TIMED_WAITING, // 计时等待
TERMINATED; // 被终止
private State(){}
}
}
新建状态:
// 只是线程对象,没有线程特征
Thread t = new MyThread();
可运行状态:(就绪/运行)
// 只代表可运行(有资格竞争CPU,得到CPU才进入运行状态)
t.start();
// 被其他线程调用唤醒,要抢锁
t2.notifyAll();
被终止状态:
执行完毕或出现异常
无限等待:
// 被唤醒要去抢锁
this.wait();
计时等待:
// 线程休眠不会放锁,时间到不用抢锁
t.sleep(3000);
// 时间到自己醒,抢锁
this.wait(4000);
线程池
线程池在java中的代表类:ExecutorService
// 创建线程池
ExecutorService pools = Executors.newFixedThreadPool(3);
// 添加线程任务
Runnable target = new MyRunnable();
pools.submit(target);
pools.submit(target);
pools.submit(target);
pools.submit(target); // 复用线程
pools.shutdown(); // 等执行完关闭线程池
pools.shutdownNow(); // 立即关闭,无论是否执行完毕
class MyRunnable implements Runnable {
@Override
public void run() {
for () {
System.out.println(Thread.currentThread().getName());
}
}
}
// 创建线程池
ExecutorService pools = Executors.newFixedThreadPool(3);
// 添加线程任务
Future<String> t1 = pools.submit(new MyCallable(1));
Future<String> t2 = pools.submit(new MyCallable(2));
Future<String> t3 = pools.submit(new MyCallable(3));
Future<String> t4 = pools.submit(new MyCallable(4)); // 复用线程
try {
String s1 = t1.get();
String s1 = t1.get();
String s1 = t1.get();
String s1 = t1.get();
} catch (Exception e) {
e.printStackTrace();
}
pools.shutdown(); // 等执行完关闭线程池
pools.shutdownNow(); // 立即关闭,无论是否执行完毕
class MyCallable implements Callable<String> {
private int x;
public MyCallable(int x) {
this.x = x;
}
@Override
public String call() {
return Thread.currentThread().getName();
}
}
死锁
死锁产生的四个必要条件:
- 互斥,当一个资源被占用,别的线程不能使用;
- 资源请求者不能强制资源占有者释放资源,资源只能由占有者主动释放;
- 资源请求者在请求其他资源时把持对原有资源的占有
- 循环等待,p1要p2的资源,p2要p1的资源
写一个必然死锁的程序:
new Thread(new Runnable() {
@Override
public void run() {
// 占用资源1
synchronized (resource01) {
Thread.sleep(1000);
// 请求资源2
synchronized (resource02) {
System.out.println("success01");
}
}
}
});
new Thread(new Runnable() {
@Override
public void run() {
// 占用资源2
synchronized (resource02) {
Thread.sleep(1000);
// 请求资源1
synchronized (resource01) {
System.out.println("success02");
}
}
}
});
Volatile关键字(不保证线程安全)
线程间变量不可见性
一个线程修改了值,另一个线程不可见
Java内存模型:
主内存 + 每个线程有独立的工作内存(保存共享变量的工作副本)
线程间变量的值的传递需要通过主内存中转
两种常见的解决方式:
-
加锁(得到锁后会清空工作内存)
-
对共享变量进行Volatile关键词修饰
Volatile修饰的变量被修改后会通知所有工作内存值失效
###原子性
是指一批操作是一个整体。同时成功、同时失败
Volatile只能保证线程间变量的可见性,不能保证变量的原子性
保证原子性操作:
-
加锁 — 》 还能保证可见性
加锁机制性能较差,别人连读都读不了
-
使用原子类(CAS机制):atomic包(且性能好 && 安全)
AtomicInteger帮助更新数量的原子更新操作
CAS:Compare And Swap(比较再交换)
在修改变量前 比较复制的值和原来的值是否相同,相同才能修改
不相同重新获取值