java 多线程
process进程 thread线程
三种创建方式
Thread class 继承Thread类
创建线程方式一:继承Thread类,重写run()方法,调用start开启线程(如果是直接调用run方法,实际上是在main线程中进行的)
继承Thread类,首先重写run方法,然后在主程序中创建一个线程对象,调用start方法开启线程
线程开启不一定立即执行,由CPU调度
理解:在主程序中先实例化Thread的继承类,由于继承了Thread,所以可以直接使用他实例化出来的对象的start方法来开启线程
不建议使用:避免OOP单继承局限性
Runnable接口 实现Runnable接口
创建线程方式2:实现2runnable接口,重写run方法,执行线程需要丢入runnable的实现类(实例化一个Thread对象,用含参构造器),调用start方法
作为Runnable接口的实现类,首先重写run方法,然后再主程序中创建一个runnable接口的实现类,然后创建线程对象,通过线程对象来开启线程,需要用到代理
理解:在主程序中实例化runnable接口的实现类,由于没有继承Thread,所以要实例化Thread。然后运用Thread实例化出来的对象的start方法
推荐使用:避免单继承局限性,方便于同一对象被多个线程使用。同时适合共享资源
TestThread3 testThread3 = new TestThread3();
Thread thread = new Thread(testThread3);
thread.start();
//Thread实例化时要把实现类投入Thread()中
//后两句可以合并为
new Thread(实现类对象名,name).start( )//name类似于对象.name,借此实现同一对象被多个线程使用
Callable接口 实现Callable接口
了解即可
静态代理
真实对象与代理对象都要实现同一个接口
代理对象要代理真实角色,需要将真实角色传入。然后调用代理对象的方法
好处:代理对象可以做很多真实对象做不了的事情,真实对象可以做自己的东西
Lambda表达式
使用目的:避免匿名内部类定义过多,可以让代码更简洁,去掉了没有意义的代码只留下了核心逻辑
函数式接口
任何接口,如果只包含唯一一个抽象方法,那么他就是函数式接口(Runnable就是)
public interface Runnable{
public abstract void run();
}
//在接口里,void ilike默认包含public abstract
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
对于一个方法的简化进化史
- 定义一个函数式接口,然后用实现类
- 为避免在主类外部,采用在主类内部使用静态内部类
- 为避免在主方法外部,采用在主方法内部使用局部内部类
- 为避免命名类,采用匿名内部类(没有类的名称,必须借助接口或者父类实现,最好要加分号)
- 由于只有一个方法,为避免多余的无意义代码,采用lambda表达式
like = ()->{
System.out.println("I like lambda5");
};
like.Lambda();
lambda表达式简化
lambda表达式只能有一行代码时才能简化,不然要用代码块包裹
- 可以去掉参数类型(多个也可以去掉,要去掉就都去掉)
- 可以去掉括号,只能是只有一个参数时才可以
- 可以去掉花括号,
ILove love=(int a)-> {
System.out.println("i love you"+a);
};
//在前面ILove love = null;就可以省去ILove
love=(a)-> {
System.out.println("i love you"+a);
};
love=(a)-> System.out.println("i love you"+a);
线程的五大状态
创建状态:线程对象的创建 new
就绪状态:当调用start()方法,线程进入就绪状态,但是不会立刻被调度执行
阻塞状态:当调用sleep,wait或同步锁定时,进入阻塞状态
运行状态:运行时,线程才真正执行线程体的代码块
死亡状态:线程中断或者结束,一旦进入死亡状态就不能再次启动
线程方法
- setName设置线程名称,使之与参数name相同
- getName返回该线程的名称
- start使线程开始执行
- run,只是一个方法,并不能建立新的线程
- setPriority(int newPriority) 更改线程的优先级
- getPriority获取线程的优先级
- static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
- void interrupt( ) 中断线程,别用这个方法
void join( ) 等待该线程终止
static void yield( )暂停当前正在执行的线程对象,并执行其他线程
boolean isAlive( ) 测试线程受处于活动状态
停止线程
- 不推荐使用JDK提供的stop( ),destroy( )方法
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
线程休眠_sleep
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延迟,倒计时
- 每个对象都有一个锁,sleep不会释放锁
模拟网络延迟可以放大问题的发生性
线程礼让_yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 让线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,要看CPU
线程强制执行_join
- join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可以想象为插队
运行Thread对象vip方法vip.join( )的是被插队的一方,先让vip对象线程执行结束
观察线程状态
get.State( )获取当前状态
state!=Thread.State.TERMINATED 判断变量state是不是TERMINATED
线程优先级
优先级范围是1-10 数字越大,优先级越高
优先级高,不一定先执行,看CPU。但是权重会更大一点
守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台记录操作日志,监控内存,垃圾回收等待
thread.setDeamon(true)设置线程为守护线程,默认为false表示用户线程
用户线程执行完后需要一段时间虚拟机才会停止
线程同步
并发:同一对象被多个线程同时操作
形成条件:队列+锁(synchronized) 才能保证安全性
问题:多线程竞争下损失性能。 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
不加锁:多个线程获得一样的内存,都认为可以取票,导致最后剩余票数为负数
同步方法
synchronized相当于一个修饰符。可以生成同步方法和同步块
缺陷:将一个大的方法声明为synchronized会影响效率
方法里面需要修改的内容才需要锁
synchronized非静态同步方法锁的其实是this
以下为一个取钱的代码,不进行同步的话会存在错误
同步方法 加上synchronized修饰方法
同步块synchronized(同步监视器){ 代码 } 同步监视器可以是任何对象,推荐使用共享资源作为同步监视器
同步方法的同步监视器就是this,就是这个对象本身或者是class
如果用同步方法锁住run方法,实际上是synchronized(run),代指的是他所在的类Drawing。Drawing有两个对象you和girl,并不是被多个线程同时操作的对象,且Drawing类不涉及增删改,所以没有用(举实际离子的话就是把银行锁住了,两个人排队进了银行,但是没有解决取钱冲突的问题。应该锁住他们两个同时操作的银行卡账户)。
所以采用同步块,将变化的对象account锁住,同时将关于account增删改的代码移入同步块中
package demo04;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing girl = new Drawing(account,100,"GIRL");
you.start();
girl.start();
}
}
class Account{
int money;
String name;
public Account(int money,String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread{
Account account;
int darwingMoney;
int nowMoney;
public Drawing(Account account,int darwingMoney,String name){
super(name);
this.account=account;
this.darwingMoney=darwingMoney;
}
@Override
public void run() {
if(account.money-darwingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,去不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=darwingMoney;
nowMoney+=darwingMoney;
System.out.println(account.name+"余额为:"+account.money);
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
//把run中的增删改代码放入同步块中
@Override
public void run() {
synchronized (account){
if(account.money-darwingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=darwingMoney;
nowMoney+=darwingMoney;
System.out.println(account.name+"余额为:"+account.money);
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,就会导致两个以上的线程都在等待对方释放资源。即某一个同步块同时拥有两个以上对象的锁。
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁
System.out.println(this.girlname + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) {//一秒钟后获得镜子的锁
System.out.println(this.girlname + "获得镜子的锁");
}
} else {
synchronized (mirror) {//获得镜子的锁
System.out.println(this.girlname + "获得镜子的锁");
Thread.sleep(2000);
}
synchronized (lipstick) {//一秒钟后获得口红的锁
System.out.println(this.girlname + "获得口红的锁");
这样不会出现死锁,choic为0的情况获得口红锁将其执行完,然后释放口红锁。
如果将镜子锁写到口红锁内部,那么在执行完输出口红语句后由于无法获得已经被else语句执行体锁上的镜子所以无法走完程序,进而无法释放口红锁
Lock锁(接口)
//定义Lock锁
private final ReentrantLock lock=new ReentrantLock();
lock.lock();//加锁
try{
}finally {
//解锁
lock.unlock();
}
lock是显式锁,要手动开启和关闭
lock只有代码块锁,没有方法锁
优先使用顺序:lock>同步代码块>同步方法
线程协作 生产者消费者模式
wait( )线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait( long timeout) 指定等待的毫秒数
notify( ) 唤醒一个处于等待状态的线程
notifyAll( ) 唤醒同一个对象上所有调用wait方法的线程,优先级别高的线程优先调度
1.管程法 设置一个数据缓存区,生产者将生产好的数据放入缓存区中,消费者从缓存区拿出数据
线程池
corePoolSize:核心池大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
目前讲的不是很清晰