多线程
- 继承Thread
- 实现一个接口Runnable
- 对比实现Runnable接口和继承Thread
- 获取线程的名称
- 线程的中断
- 守护线程
- 线程不安全问题
- 处理线程不安全问题
- 同步代码块
- 同步方法
- 显示锁
- 公平锁和非公平锁
- 线程的六种状态
- 特殊线程的创建方法:Callable
- 线程池
- 缓存线程池
- 定长线程池
- 单线程线程池
- 周期定长线程池
- Lambda表达式
继承Thread
需要提前掌握,Java中是抢占式多线程。所以主线程与子线程或子线程与子线程谁先执行完,每次结果都不一样。
MyThread m = new MyThread();
m.start();开启子线程,这个执行子线程的命令不是调用子线程中的run方法,而是通过thread对象中的start()来启动任务。
由一个线程执行的方法,方法也会执行在这个线程里面,每个线程都有自己的栈空间,共用一份堆内存。
实现一个接口Runnable
第二种实现的方法是实现一个Runnable的接口,
创建步骤 如下:
- 创建一个任务对象(MyRunnable继承Runnable这个接口)
MyRunnable r = new MyRunnable(); - 创建一个线程并分配一个任务
Thread t = new Thread®; - 执行这个线程
t.start();
对比实现Runnable接口和继承Thread
实现Runnable与继承Thread相比有如下优势:
- 通过创建任务,然后给线程分配的方式实现的多线程,更适合多个线程同时执行相同任务的情况。
- 可以避免单继承所带来的局限性。
- 任务与线程本身是分离的,提高了程序的健壮性
- 后续的线程池学习,接受Runnable类型的线程,不接受Thread类型的线程。
通过匿名内部类实现线程的方式:
new Thread(){
public void run(){
for(int i=0;i<10;i++){
System.out.println("一二三四五"+i);
}
}
}.start();
获取线程的名称
public static void main(String[] args){
System.out.println(Thread.currentThread().getName());
Thread t = new Thread(new MyRunnable());
t.setName("Thread-0");
t.getName();
}
static class MyRunnable implements Runnable{
public void run(){
System.out.println(Thread,currentThread().getName());
}
}
线程的中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定,试想一种场景,当主线程执行完毕,调用Thread类中stop方法,子线程收到stop方法发来的指令,不管子线程是否还在运行就会停止,这样不符合逻辑。
现在就是要用一种方式,能让主线程在运行完毕的时候,并不影响子线程的运行,这时候就用到了interupt()方法,主线程会反馈执行完毕的信息给子线程,由子线程进行try-catch捕获中断异常来选择是否主动关闭线程,释放资源。
具体代码如下:
public static void main(String[] args){
Threat t1 = new Threat(new MyRunnable);
t1.start();
//循环的次数小于子线程,主线程早于子线程结束
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
//通知子线程中断
t1.interrupt();
}
staic class MyRunnable implements Runnable(){
public void run(){
for(int i=0;i<10;i++){
System.out.println(Thread.current().getName()+i);
//但检测到中断标记之后,能通过try-catch决定代码是否能正常运行下去
try{
Thread.sleep();
}catch(InterruptException e){
System.out.println("收到了中断消息,但子线程不停止");
//return;如果加上return代表线程结束,可以在这块进行资源释放
}
}
}
}
守护线程
线程:分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进程结束。直接创建的线程都是用户线程。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
要想达到,当上一个case的主线程结束的时候,子线程也跟随结束的话。
具体操作方法为:
在t1.start()开启子线程之前,将子线程设置成为主线程的守护线程,即是添加一行代码:t1.setDaemon(true),这样哪怕事情没有做完,子线程也会停止运行。
线程不安全问题
下面的case是线程不安全例子的体现。
当三个线程都对ticket进行操作的时候,有可能在count=1的情况下,3个线程都进入到了子线程当中,这样就会造成3次减法运算,所以打印出来的count数值就会造成出现负值的情况。
public static void main(String[] args) {
//线程不安全
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//总票数
private int count = 10;
@Override
public void run() {
while (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("卖票结束,余票:"+count);
}
}
}
处理线程不安全问题
需要设置一段代码让线程排队进入操作。叫线程同步,有三种线程同步方法(加锁机制)。
同步代码块
格式:synchronized(){}
大括号里面放入同步代码块,线程排队执行
小括号里面需要传参,传入的是锁对象。
这样的解决方式是三个线程看同一把锁,这样效率就变低了。注意的点就是,3个线程必须看同一把锁,不能创建多个锁,这样线程就会只看自己的锁,就会造成线程不安全。
while (true) {
**synchronized (o)** {
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
}else {
break;
}
}
同步方法
以方法为单位进行加锁,在以代码为例例子的时候就很清楚的能看出来与同步代码块的区别。
static synchronized class Ticket implements Runnable{
//总票数
private int count = 10;
@Override
public void run() {
while (count>0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("卖票结束,余票:"+count);
}
}
那么问题来了,同步代码块根据对象是否释放资源锁来进入到代码块中进行线程操作,那么同步方法是怎么做到线程排队的呢?
在方法内部操作this,那个这个锁对象就是谁。就是被创建的对象。
显示锁
这是处理多线程问题的第三种方法,同步代码块和同步方法都属于隐式锁,通俗来讲,显示锁就是自己创建锁,
创建锁的代码,
private lock l = new ReentrantLock();
.....
l.lock();
被锁住的代码
l.unlock();
公平锁和非公平锁
公平锁:先来先到,排队,A线程先过来等待前一个线程释放资源锁
非公平锁:同步代码块,同步方法,显式锁默认的都是非公平锁,等到前一个线程释放资源锁的时候,大家一起去抢。
private lock l = new ReentrantLock(true);
lock子类有一个构造代码块,当设置成true的时候,就是公平锁
线程的六种状态
new:表示线程刚被创建但是还未启动
Runnable:Java虚拟机中执行的线程处于此状态
Blocked:被阻塞等待监视器锁定的线程处于此状态,排队执行
Waiting:无限期等待另一线程执行特定操作的线程处于此状态
TimedWaiting:正在等待另一线程,指定时间休眠
Trminated:线程死亡
特殊线程的创建方法:Callable
Callable方法与之前创建多线程的方法,区别在于,Callable会返回一个integer数值,等到子线程执行完成之后,主线程才回继续执行
有三个步骤:
首先创建对象
Callable<Integer> c = new MyCallable();
然后创建任务:
FutureTask<Integer> task = new FutureTask<>(c);
启动任务:
new Thread(task).start();
获取子线程返回数值:
task.get();
线程池
线程池,池是一种容器,用来装线程,在开发的时候,不可避免需要创建大量的线程,有时候线程需要执行一个很小的事情,执行完之后线程就消失了。创建线程和关闭线程需要消耗大量的时间,因此会降低系统的效率。
执行的时候,创建流程就是:
1.创建线程。2.创建任务。3.执行任务。4.关闭线程
Java中有4中常见的线程池:
1.缓存线程池,非定长线程池,线程数量可以根据任务的多少进行扩容或者删减。
2.定长线程池:当有空闲线程的时候,任务进入线程池中。当没有空闲线程池的时候,任务进入等待队列。
3.单线程线程池:判断线程是否空闲,空闲则使用,不空闲则等待
4.周期性任务定长线程池:定时间隔周期重复执行
缓存线程池
//向线程池中加入新的任务
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
定长线程池
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
单线程线程池
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
周期定长线程池
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次
//参数1:定时执行的任务
//参数2:时长数字
//参数3:2的时间单位 Timeunit的常量指定
/* scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5, TimeUnit.SECONDS); //5秒钟后执行*/
/*
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位
* **/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1,TimeUnit.SECONDS);
}
}
Lambda表达式
函数式编程思想:不关注过程,只注重思想。实现Lambda表达式有一个注意的点,需要的接口中必须只有一个方法
public static void main(String[] args) {
//冗余的Runnable编写方式
/* Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("锄禾日当午");
}
});
t.start();*/
Thread t = new Thread(() -> System.out.println("锄禾日当午"));
t.start();
}
}