[Java 08] 初级多线程
Java多线程
一个程序运行有一个进程,进程里有多个线程:main函数,JC(垃圾回收线程)。
1. 线程创建
1. 继承Thread类(Thread类也继承了Runnable接口)
使用:
//1. 自定义类继承Thread类
//2. 重写run()方法
//3. .start()开始运行线程(调用run()则不是多线程,是顺序运行)
2. 实现Runnable接口
因为Java单继承的原因,接口方便同一个对象被多个线程使用
使用:
//1. 定义MyRun实现Runnable接口(重写run()方法)
MyRun implements Runnable{}
//2. 创建实现类对象
MyRun myRun = new MyRun();
//3. 创建代理类对象,并启动
new Thread(myRun()).start();
//or
Thread thread = new Thread(myRun);//代理
thread.start();
3. 实现Callable接口
好处:可以抛出异常,可以带返回值
使用:
//1. 自定义类实现callable接口
MyCallable implements callable<返回值类型>{}
//2. 重写call方法(必须有返回值,和上面的范形一样)
//3. 创建执行服务
ExcutorService ser = Excutors.newFixedThreadPool(3);//创建的线程个数
//4. 提交执行
Future<> f1 = ser.submit(myCallable);//有几个就提交几个
//5. 获取结果
返回值类型 rf1 = f1.get();
//6. 关闭服务
ser.shutdownNow();
2. 并发问题(火车票)
对数据number处理问题
public class TichetProblem implements Runnable{
private int number = 10;
@Override
public void run() {
while(true){
if(number<= 0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "拿到了第" + number-- +"张ticket");}
}
public static void main(String[] args) {
//只有一个实例对象
TichetProblem tichetProblem = new TichetProblem();
//四个人抢票:
new Thread(tichetProblem, "北京人").start();
new Thread(tichetProblem, "广东人").start();
new Thread(tichetProblem, "新疆人").start();
new Thread(tichetProblem, "台湾人").start();
}
}
3. 静态代理模式
类似python装饰器,重点:真实对象和代理对象都要实现相同的接口, 类似于组合
一个实现了结婚接口的人,去找实现了结婚接口的婚前公司包装,结婚公司代理这个人结婚。
真实对象专注于一件事,
代理对象可以对真实对象进行包装。
public class StaticProxy {
public static void main(String[] args) {
new weddingCompany(new Person()).getMarry();
}
}
interface Marry {
void getMarry();
}
class Person implements Marry{
@Override
public void getMarry() {
System.out.println("today");
}
}
class weddingCompany implements Marry{
private Person person;
public weddingCompany(Person person) {
this.person=person;
}
@Override
public void getMarry() {
before();
this.person.getMarry();
after();
}
private void before(){
System.out.println("before");
}
private void after(){
System.out.println("after");
}
}
4. lambda表达式
函数式接口:只有一个方法的接口:如Runnable接口只有一个run()方法。
对函数式接口,可以使用lambda表达式创建实现该接口的对象。
(可以用接口类型去new对象,但此时对象只有接口中所定义的方法,和父类引用指向子类对象一样)
InterfaceType user1 = new User1();//其中user1没有User1中定义的其他函数
简化流程:
public class LambdaTest {
public static void main(String[] args) {
//1. 接口下方写class实现接口,再调用 普通调用
//2. main方法前面写静态类,然后再调用 静态内部类
//3. 把类写到main方法里 然后调用 局部内部类
//4. 用接口创建,匿名内部类
IL il = new IL() {
@Override
public void l(int a) {
System.out.println(a);
}
};
il.l(123);
//5. lambda 表达式
IL il2 = (int a) -> { System.out.println(a); };
il2.l(123);
//简化一,去掉参数类型//常用的表达式
IL il_1= a->{ System.out.println(a); };
//简化二,去掉括号(如果有多行,则不能简化{},只能简化()
IL il_2 = a-> System.out.println(a);
}
}
interface IL{
void l(int a);
}
class il implements IL{
@Override
public void l(int a) {
System.out.println(a);
}
}
5. 线程状态
5个状态,创建状态,就绪状态,运行状态,阻塞状态,死亡状态
.start()从创建到就绪,
1. 线程停止
//1. 实现runnalble接口的自定义类中定义一个flag
//2. run方法中写当flag为true时运行
//3. 自定义类中写一个方法使flag为False
//4. main函数中调用3方法,使得线程停止
2. 线程休眠(模拟网络延时,倒计时)
会抛出异常,休眠时间到达之后进入就绪状态
每个对象都有一把锁,sleep不会释放锁
1000ms = 1s
//获取当前时间
Date startData = new Date(System.currentTimeMillis());
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startData));
3. 线程礼让 yield
礼让是指,CPU正在运行的线程从运行状态转变为就绪状态,不是阻塞状态!,
然后让两个线程继续竞争,所以礼让不一定成功,看CPU调度哪一个
4. 线程插队 Join
Thread thread = new Thread(new Myrun());
thread.start();
//在主线程中可写:
thread.join();//来强制执行此线程
5. 获取线程状态
写主函数里,要有实例化的线程或者代理名。
死亡之后的线程不能再次被启动
Thread.State state = new thread.getState();//新建一个state,new的是已经实例化的Thread
state = thread.getState();//状态更新
6. 线程优先级
java提供一个线程调度器监控启动后所有进入就绪状态的线程
优先级只是一个参考,并不是完全按照优先级进行调度的性能倒置,
优先级范围:1-10 Thread.MIN_PRIORITY =1, Thread.MAX_PRIORITY = 10, Thread.NORM_PRIORITY = 5,参考jdk文档
获取和改变:
getPriority(), setPriority(int priorityNumber)
7. 守护线程 Daemon /'di: men/
线程分为用户线程和守护线程,(用户线程有:main,自己的线程;守护线程有gc垃圾回收,自己设置的守护线程)
虚拟机必须确保用户线程执行完毕,不需要确保守护线程执行完毕。
所有的线程默认都是用户线程,thread.setDaemon 默认值为 false,
当值设置为true时,设置为守护线程
虚拟机不需要等待守护线程执行完毕
//thread时实例化的线程
thread.setDaemon(true);
6. 线程同步
多个线程对同一个资源进行操作
同步解决: 队列+ 锁机制 synchronized
ticket问题出现负数的问题:所有人将1拷贝到自己的线程内存中,线程内存操作之后再修改station中的余额,出现-1问题
线程的不安全:对同一资源操作覆盖问题
1. synchronized 修饰符
通过修饰,当获取到对象的锁的时候,才能被执行。缺点:当一个大方法被修饰时,运行效率很低
同步方法和同步块锁的对象时变化的统一资源
//1. 同步方法
// 同步方法锁了之后,默认锁的是该方法所在的对象,如果修改的统一资源存在于该对象中,则用此方法
public synchronized void method(){}
//2. 同步块
// 把同步的对象名放在obj位置,后面是修改统一资源的方法体
synchronized(obj){}
2. JUC包,java.util.concurrent
CopyOnWriteArrayList//JUC里的安全类型list
7. 死锁
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,再未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
(口红,镜子问题)
8. 显式定义锁接口:Lock
ReentrantLock类实现了Lock锁
可重入锁
class A{
private final ReentrantLock lock = new ReentrantLock():
public void m(){
lock.lock();
try{
//主代码
}finally{
lock.unlock();
}
}
}
9. 线程协作(生产者消费者问题)
- 利用缓冲区解决:管程法
wait();//线程一直等待,知道有其他线程唤醒
notifyAll();//唤醒别的所有等待的线程
10. 线程池
ExecutorService excutor = Executors.newFixedThreadPool(10);
excutor.execute();
excutor.shutdown();