thread
线程相关的基本概念
程序(program):
是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是写的代码
进程:
-
进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
-
进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
线程:
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行
并发并行可同时存在
线程的基本使用
创建线程的两种方式:
- 继承Thread类,重写run方法
当一个类继承了Thread类,该类就可以当成线程来使用。
然后重写run方法,写入自己的业务逻辑
run Thread类实现了 Runnable接口的run方法
- 实现Runnable接口,重写run方法
-
java是单迷承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
-
java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
package com.thread_;
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog); //静态代理模式
thread.start();
}
}
class Dog implements Runnable {
@Override
public void run() {
int count = 0;
while (true){
System.out.println("hi" + (++count) + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if (count == 10){
break;
}
}
}
}
继承Thread 与 实现Runable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到Thread类本身就实现了Runnable接口start()->start0()
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
线程终止
-
当线程完成任务后,会自动退出
-
还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
线程常用的方法
-
setName //设置线程名称,使之与参数name相同
-
getName //返回该线程的名称
-
start //使该线程开始执行;Java虚拟机底层调用该线程的start()方法
-
run //调用线程对象run方法;
-
setPriority //更改线程的优先级
-
getPriority //获取线程的优先级
-
sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)8. interrupt //中断线程
-
yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
-
join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
注意事项:
-
start底层会创建新的线程,调用run, run就是一个简单的方法调用,不会启动新线程
-
线程优先级的范围
-
interrupt:中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
-
sleep:线程的静态方法,使当前线程休眠
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束守护线程自动结束
- 常见的守护线程:垃圾回收机制
线程的生命周期
线程转换图:
Synchronized
线程同步机制:
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
- 理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码;
}
synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
互斥锁:
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时。表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法((静态的)的锁为当前类本身
注意
1.同步方法如果没有使用static修饰:默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
-
需要先分析上锁的代码
-
选择同步代码块或同步方法,通常优先选择同步代码块,一般来说同步区域越小程序效率越高
-
要求多个线程的锁对象为同一个即可!
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
Object object = new object();
public /*synchronized*/ void sell() {
synchronized (new Object()/*object*/) { //new Object()和object前者仍然出现同步问题,因为多个线程的锁对象不为同一个
if (ticketNum <= 0) {
System.out.println("票已经售完");
loop = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "余票" + (--ticketNum));
}
}
@Override
public void run() {
while (loop){
sell();
}
}
}
//synchronized关键字简单的解决售票同步问题
package com.thread_;
@SuppressWarnings({"all"})
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
// SellTicket02 sellTicket02 = new SellTicket02();
// new Thread(sellTicket02).start();
// new Thread(sellTicket02).start();
// new Thread(sellTicket02).start();
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
public /*synchronized*/ void sell() {
synchronized (this) {
if (ticketNum <= 0) {
System.out.println("票已经售完");
loop = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "余票" + (--ticketNum));
}
}
@Override
public void run() {
while (loop){
sell();
}
}
}
class SellTicket01 extends Thread {
private static int ticketNum = 100;
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("票已经售完");
break;
}
try {
sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "余票" + (--ticketNum));
}
}
}
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("票已经售完");
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "余票" + (--ticketNum));
}
}
}
线程死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁
释放锁
下列操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下列操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。应尽量避免使用suspend()和resume()来控制线程
补充内容
Lock锁
-
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
synchronized 与Lock 的对比:
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
线程协作
生产者消费者模式
实例:
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库﹐消费者将仓库中产品取走消费.
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止.
- 如果仓库中放有产品﹐则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止.
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件:
- 对于生产者,没有生产产品之前,要通知消费者等待﹒而生产了产品之后﹐又需要马上通知消费者消费
- 对于消费者﹐在消费之后,要通知生产者已经结束消费﹐需要生产新的产品以供消费.
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信)
Java提供了几个方法解决线程之间的通信问题:
解决方法1:管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
解决方法2:信号灯法
可以理解为缓冲区为1的管程法。
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(....)
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
JDK 5.0起提供了线程池相关API: ExecutorService和 Executors
-
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command)︰执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable - void shutdown()∶关闭连接池
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

浙公网安备 33010602011771号