Java多线程编程 - 基础

多线程

进程是一个应用程序,线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程

java HelloWord 回车后,会先启动一个主线程调用main方法,同时启动垃圾回收线程负责看护回收垃圾;

多个线程 堆内存和方法区内存共享,栈内存独立,一个线程一个栈。

假如启动10个线程,会有10个栈空间,每个栈间互不干扰,各执行各的,这就是多线程并发

  1. 实现线程的第一种方式

编写一个类直接继承java.lang.Thread,重写run方法
调用对象的start()方法,启动一个分支线程,开辟一个新的栈空间,start()方法随后就结束了。启动成功的线程会自动调用run方法
如果直接调用run方法,还是在同一个线程,不会创建新的线程,不能并发

  1. 实现线程的第二种方式

编写一个类,实现 java.lang.Runnable 接口,实现run方法
创建一个对象,调用 Thread t = new Thread(对象) 封装一个线程对象,使用 t.start() 启动线程

第二种方式可改造成 匿名内部类
使用第二种方式居多,因为第二种类可以继承其他类,第一种则不可以

  1. 线程生命周期

1、新建状态:new 线程对象
2、就绪状态:调用start()方法,线程具有抢夺CPU时间片的权利(执行权),抢夺到执行权就执行run方法进入运行状态
3、运行状态:run方法的开始执行进入运行状态;当之前占有的CPU时间片用完后重新回到就绪状态继续抢夺执行权,当再次抢到执行时间片后,会进入run方法继续执行上一次代码
4、阻塞状态:当线程遇到阻塞事件,例如接收用户键盘输入、sleep、join方法等,线程会进入阻塞状态,线程会放弃之前占有的CPU时间片; 当阻塞结束会进入就绪状态抢夺时间片
5、 锁池lockpool(属于阻塞状态):线程遇到synchronized关键字,会让出执行权,到锁池找对象锁,如果没找到会一直找,找到了进入就绪状态
6、JVM会在就绪状态和运行状态之间进行调度
7、死亡状态:run方法执行结束

  1. 其它内容

Thread t = new Thread();

获取当前线程对象:Thread.currentThread()
获取线程对象名字:t.getName()
线程默认名称 Thread-0/Thread-1/Thread-2…
修改线程对象名字:t.setName()
让当前线程进入阻塞状态:Thread.sleep(毫秒)
终止线程的休眠:t.interrupt() 依靠异常机制,线程中sleep方法会抛出异常,终止睡眠,继续执行后面的方法

强行终止线程的执行:t.stop();已过时,缺点是容易丢数据
常用的终止线程方法:在run方法中打一个成员变量标记flag,主线程的修改flag值,如果为true,则return run方法,来终止线程

线程合并:t.join()方法,等待子线程运行,当前线程进入阻塞状态,不是子线程栈消失,而是两个栈发生了等待关系

  1. 线程调度模型

抢占式调度模型:线程优先级高,大概率抢到的CPU时间片多一些,java采用该模型
均分式调度模型:平均分配CPU时间片,每个线程执行的时间片长度一样,有些编程语言的线程调度模型采用这种方式

void setPriority(int newPriority) 设置线程优先级
int getPriority() 获取线程优先级
最低优先级1,默认优先级5,最高优先级10

  1. 线程安全(重点)

多线程并发的情况下,数据的安全问题

服务器是支持多线程的,服务器已经定义了线程,线程对象的创建、启动等,这些代码我们都不用写。而我们编写的程序放到多线程环境下运行,更应该注重的是多线程下数据安全问题

线程不安全:多线程同时间对同一个数据修改,会出现数据不安全问题

解决线程不安全:线程排队执行,称为线程同步机制。会牺牲一部分效率,但是数据安全第一位

线程同步机制:

synchronized(共享对象){
    //线程同步代码块
} 范围越小,效率越高

synchronized出现在实例方法上

对象锁默认为this对象,切不能更改为其它对象
缺点:不能选择对象锁,方式不灵活;同步整个方法体,可能无辜扩大同步范围,导致效率低
优点:代码少了,节简了

synchronized 出现在静态方法上

找类锁,类锁只有一把,无论创建多少对象
  1. synchronized 关键字

每个对象都有把锁,对 共享对象 进行加锁,执行线程同步代码块前都会检查对象是否加锁,如果有锁需要等待

  1. 死锁

线程1 synchronized 中锁住第一个对象,嵌套 synchronized 锁第二个对象时,发现被其他线程占用需要等待,
线程2 synchronized 中锁住第二个对象,嵌套 synchronized 锁第一个对象需要等待。

上述场景,造成死锁,程序一直等待中,不会出异常,这种最难调试

synchronized最好不要嵌套使用,容易发生死锁

  1. 解决线程安全问题

1、尽量使用局部变量代替“实例变量和静态变量”
2、如果是实例变量,可以考虑创建多个对象,对象不共享,就不存在数据安全问题
3、如果没办法,就只能使用线程同步机制了

  1. 用户线程和守护线程

垃圾回收器就是守护线程,可以理解为后台线程

守护线程一般是一个死循环,所有的用户线程结束,守护线程也结束

main方法是一个用户线程

t.start()之前将用户线程设置为守护线程 t.setDaemon(true)
true表示为守护线程,false表示用户线程

  1. 定时器

间隔一定的时间去执行特定的程序

1、使用sleep()休眠一定的时间(最原始方式)
2、使用 java.util.Timer,开发中很少用,很多高级框架都支持定时任务,例如SpringTask框架,底层也是用Timer

Timer timer = new Timer()
timer.schedule(task)
  1. 实现线程的第三种方式(JDK8新特性)

java.util.concurrent.FutureTask 方式,实现java.util.concurrent.Callable 接口,可以获取线程返回值

myClass implements Callable 实现call方法,相当于run方法
FutureTask task = new FutureTask(myClass);
Thread t = new Thread(task);
t.start(); 启动线程
Object o = task.get(); 获取返回值,会等待线程的执行,当前线程处于阻塞状态
  1. Object类中的 wait 和 notify 方法(生产者和消费者模式)

Object中自带这两种方法

wait()作用:
比如Object o = new Object()
o.wait(); 让正在o对象上活动的线程进入等待状态,无限期等待直到被唤醒为止

notify()作用:
o.notify(); 唤醒正在o对象等待的线程
o.notifyAll(); 唤醒o对象处于等待的所有线程

生产者和消费者模式为了专门解决某个特定需求的
一个线程负责生产,一个线程负责消费,生产满了不能再生产,必须等消费线程消费;消费完了就不能消费,必须等生产线程进行生产

这两个方法的使用建立在synchronized线程同步的基础上

o.wait() 会让正在o对象上活动的线程进入等待状态,并且释放之前占有o对象上的锁;o.notify() 只会通知,不会释放之前占有的的o对象锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值