CS-Notes/notes/Java 并发.md at master · CyC2018/CS-Notes (github.com)
【【狂神说Java】多线程详解】 www.bilibili.com/video/BV1V4…
开辟线程
Thread
public class TestThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("countdown tik : " + i);
}
}
public static void main(String[] args) {
TestThread1 t = new TestThread1();
t.start();
for (int i = 0; i < 200; i++) {
System.out.println("okok" + i);
}
}
}
Runable
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("countdown tik : " + i);
}
}
public static void main(String[] args) {
TestThread3 testThread3 = new TestThread3();
// Thread thread = new Thread(testThread3);
// thread.start();
new Thread(testThread3).start();
for (int i = 0; i < 200; i++) {
System.out.println("okok" + i);
}
}
}
小结
继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
并发问题
// 下面会多个线程一起操作,会产生并发问题,日期错乱
public class TestThread4 implements Runnable{
private int ticketNumber = 10;
@Override
public void run() {
while (true) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticketNumber <= 0)break;
System.out.println(Thread.currentThread().getName() +"拿到了第" + ticketNumber -- + "张票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"cfd").start();
new Thread(ticket,"glz").start();
new Thread(ticket,"lh").start();
}
}
多个线程并发访问变量ticketNumber,导致出现并发错误
代理模式
总结:
代理者和被代理者需要实现相同的接口
被代理者必须是真实对象
好处:
代理对象可以做很多被代理对象做不了的事情
真实对象就专注做自己的事情
public class staticProxy {
public static void main(String[] args) {
// 匿名闭包函数 lambda λ
new Thread(() -> System.out.println("I Love you Concurrently")).start();
WeddingCompany weddingCompany = new WeddingCompany(new CFD());
weddingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
// 真实角色
class CFD implements Marry {
@Override
public void HappyMarry() {
System.out.println("CFD is Happy");
}
}
// 代理对象
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("布置婚礼");
}
private void after() {
System.out.println("收拾婚礼现场");
}
}
lambda
() -> System.out.println("I Love you Concurrently");
(params)->expression[表达式](params)>statement[语句](params)->fstatements }
函数式接口
理解FunctionalInterface(函数式接口)是学习Java8 lambda表达式的关键所在
函数式接口的定义:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
public interface Runnable {
public abstract void run();
}
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象,他会自动推导使用该方法
lambda表达式在Java8以前没有出现的替换品
public class lambda {
//3. 静态内部类
static class Like2 implements People {
@Override
public void Study() {
System.out.println("I'm studying static lambda");
}
}
public static void main(String[] args) {
People like = new Like();
like.Study();
People like2 = new Like2();
like2.Study();
// 4. 局部内置类
class Like3 implements People {
@Override
public void Study() {
System.out.println("I'm studying local lambda");
}
}
People like3 = new Like3();
like3.Study();
//5. 匿名内部类,没有内部类,必须借助接口或者父类
like = new People() {
@Override
public void Study() {
System.out.println("I'm studying anonymous lambda");
}
};
like.Study();
//6. lambda简化
like = ()-> {
System.out.println("I'm studying anonymous lambda");
};
like.Study();
}
}
// 1.定义一个函数式接口
interface People{
void Study();
// void Stupid();
}
// 2.实现类
class Like implements People {
@Override
public void Study() {
System.out.println("I'm studying lambda");
}
}
lambda 表示的用法示例
public class lambda2 {
public static void main(String[] args) {
// 简化再简化,简化的地方只能有一个变量或者语句
Feelings fellings = (num)->{
System.out.println("I appreciate you " + num);
};
fellings = num->{
System.out.println("I appreciate you " + num);
};
fellings = num->System.out.println("I appreciate you " + num);
fellings.express(10);
}
}
interface Feelings {
void express(int num);
}
class appreciate implements Feelings{
@Override
public void express(int num) {
System.out.println("I appreciate you" + num);
}
}
线程相关api
join
详解java Thread中的join方法 - 知乎 (zhihu.com)
守护进程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕,如main函数
- 虚拟机不用等待守护线程执行完毕,如后台记录操作日志,监控内存,gc垃圾回收等待
public class ThreadDaemon {
public static void main(String[] args) {
God god = new God();
People people = new People();
Thread thread = new Thread(god);
thread.setDaemon(true); //false Norm;true Daemon
thread.start(); // 一直运行,直到全部线程运行结束后,自动结束
new Thread(people).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("God bless you");
}
}
}
class People implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("living passionately");
}
System.out.println("Goodbye World");
}
}
线程同步synchronized隐式锁
锁 + 队列,就可以实现
锁
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入 锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待使用后释放锁即可.存在以下问题
一个线程持有锁会导致其他所有需要此锁的线程挂起
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
public class UnsafeAdd {
static class Add implements Runnable {
public int sum = 0;
public boolean addOk = false;
@Override
public synchronized void run() {
if (addOk)return;
sum ++;
if (sum == 10000)addOk = true;
}
}
}
给函数区域加上 synchronized 即可
块锁
给一整个 status 对象加上锁
static class Add implements Runnable {
@Override
public void run() {
synchronized (status) { // 通过synchronized把临界区包起来,即使对象不在这里,也可以保护到
while (!status.addOk) {
status.sum++;
if (status.sum >= 100000) {
status.addOk = true;
}
}
}
}
}
使用闭包时加锁,保证 List 的线程安全
List<String> list = new ArrayList<String>(10);
for (int i = 0; i < 1000; i++) {
new Thread(()->{
// list.add(Thread.currentThread().getName()); 并发错误
synchronized (list){
list.add(Thread.currentThread().getName());
}
}
).start();
}
JUC
ArrayList的线程安全变体,其中所有可变操作(添加、设置等)都通过制作底层数组的新副本来实现。
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
死锁
定义不再赘述,操作系统里面有
Lock 显式锁
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了 作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
public class Lock {
public static void main(String[] args) {
Add add = new Add();
new Thread(add,"cfd").start();
new Thread(add,"lh").start();
new Thread(add,"glz").start();
}
}
class Add implements Runnable{
int countdown = 20;
// 无法被继承final
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if (countdown > 0){
System.out.println(Thread.currentThread().getName() + " got the lock ,countdown is " + countdown --);
}else{
break;
}
}finally {
lock.unlock();
}
}
}
}
线程通信
生产者与消费者——管程
类似 go 语言的 channel ,下面是有缓冲版本
// 管程法
// 生产者,消费者,缓冲区,数据
public class ProductorAndConsumer {
public static void main(String[] args) {
SyncArea syncArea = new SyncArea();
Consumer consumer = new Consumer(syncArea);
Productor productor = new Productor(syncArea);
consumer.start();
productor.start();
}
}
class Productor extends Thread{
SyncArea syncArea;
public Productor(SyncArea syncArea){
this.syncArea = syncArea;
}
@Override
public void run() {
for (int i = 0; i < 250; i++) {
System.out.println("生产了" + i + "个商品");
syncArea.push(new Products(i));
}
}
}
class Consumer extends Thread{
SyncArea syncArea;
public Consumer(SyncArea syncArea){
this.syncArea = syncArea;
}
@Override
public void run() {
for (int i = 0; i < 250; i++) {
syncArea.pop();
System.out.println("消费了第" + i + "个商品");
}
}
}
class SyncArea {
Products[] products = new Products[20];
int count = 0;
public synchronized void push(Products product){
if (products.length == count){
try {
// 等待消费者
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products[count ++] = product;
// 通知消费者无需等待了
this.notifyAll();
}
public synchronized Products pop(){
if (count == 0){
try {
// 等待生产者
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Products product = products[--count];
// 通知消费者无需等待了
this.notifyAll();
return product;
}
}
class Products {
int id;
public Products(int id){
this.id = id;
}
}
信号量
下面是无缓冲版本
// 管程法
// 生产者,消费者,缓冲区,数据
// 无缓冲
// 播放了:第0集
//观看了:第0集
//播放了:第1集
//观看了:第1集
//播放了:第2集
//观看了:第2集
// ...
public class ProductorAndConsumer2 {
public static void main(String[] args) {
TV tv = new TV("老三国");
new Interviewer(tv).start();
new Interviewee(tv).start();
}
}
class Interviewer extends Thread{
TV tv;
public Interviewer(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 250; i++) {
tv.show("第"+i+"集");
}
}
}
class Interviewee extends Thread{
TV tv;
public Interviewee(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 250; i++) {
tv.watch();
}
}
}
class TV{
String name ;
boolean flag = true;
public TV(String name){
this.name = name;
}
public synchronized void show(String name){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("播放了:"+name);
this.notifyAll();
this.name=name;
flag = !flag;
}
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("观看了:" + name);
this.notifyAll();
flag = !flag;
}
}
线程池
- 背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)便于线程管理(....)
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
execute 需要的是 implements Runnable的类的对象
public static void main(String[] args) {
// 1.创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2.加入线程
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3.关闭线程池
service.shutdown();
}
submit 需要的是 implements Callable<Object> 的对象
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://commons.apache.org/io/images/commons-logo.png","1.png");
TestCallable t2 = new TestCallable("https://pic1.zhimg.com/80/v2-2b6f88d8ff119c91d8802bf8092f056c_720w.webp","2.png");
TestCallable t3 = new TestCallable("https://pic3.zhimg.com/80/v2-197402121bc8256c2b4d7bc815ac40de_720w.webp","3.png");
// 创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交执行
Future<Object> r1 = ser.submit(t1);
Future<Object> r2 = ser.submit(t2);
Future<Object> r3 = ser.submit(t3);
// 获取结果
Object res1 = r1.get();
Object res2 = r2.get();
Object res3 = r3.get();
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
// 关闭服务
ser.shutdown();
}