多线程——基础
程序:用语言编写的一组指令的集合,指一段静态的代码
进程:程序的一次执行过程,或是正在运行的一个程序
线程:一个程序内部的一条执行路径
一个进程可以有多个线程
2.实现多线程的方式
继承Thread类
public class TestThread1 extends Thread{
Thread实现了Runnable接口
不建议使用:避免OOP单继承局限性
实现Runnable接口
package com.yl.demo;
public class TestThread2 implements Runnable{
private int id;
public TestThread2() {
}
public TestThread2(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
对比thread和runnable
相同点:都要重写run方法
不同点:Thread不适合资源共享,Runnable适合,使用Runnable,可以避免java中的单继承限制,代码可以被多个线程共享,代码和数据独立;线程池只能仿佛实现Runnable或Callable类线程,不能直接放入继承Thread的类
实现Callable接口
-
需要返回值类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务
-
提交执行
-
获取结果
-
关闭服务
package com.yl.demo;
import java.util.concurrent.*;
public class TestThread3 implements Callable {
private int id;
public TestThread3(int id){
this.id = id;
}
好处:可以定义返回值、可以抛出异常
使用线程池
3.对比Thread和Runnable创建方式:
相同点:都要重写run方法
不同点:Thread不适合资源共享,Runnable适合,使用Runnable,可以避免java中的单继承限制,代码可以被多个线程共享,代码和数据独立;线程池只能仿佛实现Runnable或Callable类线程,不能直接放入继承Thread的类
4.线程状态
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 阻塞(BLOCKED):表示线程阻塞于锁。 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。 终止(TERMINATED):表示该线程已经执行完毕。
这6种状态定义在Thread类的State枚举中,可查看源码进行一一对应。

5.线程休眠
-
sleep(时间)指定当前线程阻塞的毫秒数
-
sleep存在异常InterruptedException
-
sleep时间达到后线程进入就绪状态
-
sleep可以模拟网络延时倒计时
-
每一个对象都有锁,线程不会释放锁
sleep()和wait()
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。wait声明在同步代码块或同步方法中
6.线程礼让
-
让当前正在执行的线程暂停,但不阻塞
-
将线程从运行状态转为就绪状态
-
让cpu重新调度,礼让不一定成功,取决于cpu
7.线程停止
-
建议线程正常停止——>利用次数,不建议死循环
-
建议使用标志位——>设置一个标志位boolean
-
不要用stop或destroy
8.Join
-
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
-
可以想象成插队
9.线程优先级
-
优先级范围1-10
-
优先级越高,分配的资源越多
-
setPriority(int xxx)
-
getPriority()
10.守护线程
-
线程分为用户线程和守护线程
-
虚拟机必须保证用户线程执行完毕
-
虚拟机不用等待守护线程执行完毕
-
如:后台记录操作日志、监控内存、垃圾回收等待
Thread thread = new Thread(god);
thread.setDaemon(true); //默认是false
11.线程同步
多个线程操作同一个资源
并发:同一个对象被多个线程同时操作
加入锁机制synchronized
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行就独占该锁,直到该方法返回才释放锁。
当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一下问题:
-
一个线程持有锁会导致其他所有需要此锁的线程挂起
-
多线程竞争下,加锁释放锁会导致比较多的上下文切换和调度延时,引起性能问题
-
如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
-
方法里需要修改的内容才需要锁,锁太多会浪费资源
-
普通同步方法(实例方法),锁是当前实例对象this ,进入同步代码前要获得当前实例的锁
-
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
-
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
-
同步方法仍然涉及到同步监视器(锁),只是不需要我们显示的声明
synchronized(同步监视器) {
需要同步运行的代码片段
}
同步监视器是java中任意的一个对象,只要保证多个线程看到的该对象是”同一个“,即可保证同步块中的代码是并发安全的
可重入实现:
每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。
Lock
-
通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当
-
ReentranLock(可重入锁)实现了Lock,可以显示加锁、释放锁
synchronized和Lock的对比
-
Lock是显示锁,synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,具有更好的扩展性(提供更多的子类)
-
优先使用顺序:
-
Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(方法体之外)
-
-
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
-
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
-
Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用
好处:
-
提高响应速度
-
降低资源消耗
-
便于线程管理:
-
corePoolSize:核心池的大小
-
maxPoolSize:最大线程数
-
keepAliveTime:线程没有任务时最多保持多长时间后终止
-
线程池相关API: ExecutorService和Executors
-
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package com.yl.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool:参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//2.执行
service.execute(new myThread());
service.execute(new myThread());
service.execute(new myThread());
//3.关闭连接
service.shutdown();
}
}
class myThread implements Runnable{
浙公网安备 33010602011771号