多线程
多线程:我认为就是多条执行路径
原来程序的执行是一条路径走到底,现在多线程是多个路径同时走,提高了程序的运行效率,程序原来是一步步的执行,现在可能有一个以上的线程并发运行,具体哪一个我们不知道。
线程:
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作
单位。一条线程指的是进程中一个单一顺序的控制流。
-
一个进程中的线程共享相同的内存单元/内存地址空间可以访问相同的变量和对象,而且它们从同一堆中
分配对象通信、数据交换、同步操作
-
一个进程中可以并发多个线程,每条线程并行执行不同的任务。
-
多线程是指在同一进程下,充分利用资源,多条执行路径,共享资源。
线程的创建
1.继承Thread类实现
- 创建线程类: 继承
Thread
类 +重写run()
方法 - 构造线程类对象: 创建 子类的对象
- 启动线程: 通过子类对象调用
start()
方法
public class TestThread {
public static void main(String[] args) {
// 创建线程类对象
SomeThread oneThread = new SomeThread();
// 启动线程
oneThread.start();
}
}
// 创建线程类
class SomeThead extends Thread{
@Override
public void run()
{
//do something here
}
}
**该方法的特点:**如果当前类已经有继承父类,那么其无法继承Thread
类,run()
方法中出现的异常只能捕获,不能抛出。
2.实现Runable接口
- 通过类实现
Runable
接口,重写run()
方法 - 创建一个实现类对象,利用实现类对象创建一个
Thread
类对象 - 启动线程
public class ThreadDemo implements Runnable{
@Override
public void run() {
for(int i = 1;i<=20;i++){
System.out.println("一边看电视.....");
try {
// 线程休眠
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
//1.创建线程
Thread th = new Thread(demo);
//2.开启线程
th.start();
for(int i = 1;i<=20;i++){
System.out.println("一边吃零食.....");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.实现Callable接口
- 创建实现 Callable 接口的实现类 + 重写 call() 方法
- 创建一个实现类对象
- 由 Callable 创建一个 FutureTask 对象
- 由 FutureTask 创建一个 Thread 对象
- 启动线程
一个简易版龟兔赛跑的多线程代码:
public class TestNewThread implements Callable {
/**
* 创建一个Lock实例对象,实现加锁lock(),释放锁unlock()
* */
private Lock lock = new ReentrantLock();
@Override
public Integer call(){
try {
this.method(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}
/**
* @Description: 线程加锁测试
* @Param: [t]
* @Return: void
**/
private void method(Thread t) throws InterruptedException {
// 加锁,lock()方法后面必须紧跟try/catch语句块,中间不能出现抛出异常或者
// 使得代码无法运行到finally语句块的语句块
// lock.lock();无法获取资源就一直等待
// trylock()方法,返回一个Boolean,无法获取资源就返回false。可以设置其等待时间
if(lock.tryLock(20, TimeUnit.SECONDS)){
try {
System.out.println("锁"+t.getName()+"开启");
// 休眠 2 秒
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁,unlock()必须在finally语句的第一行,先解锁,其它线程才可以使用资源
lock.unlock();
System.out.println("锁"+t.getName()+"释放");
}
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个实现Callable接口的类实例
TestNewThread t = new TestNewThread();
// 使用FutureTask对象调用get()方法获取call方法返回的值
FutureTask ft = new FutureTask(t);
Thread th1 = new Thread(ft);
//Thread th2 = new Thread(ft);// 只会运行上一个线程
// 通过实现Callable接口来创建线程,无法实现多线程的功能,只能是单线程的运行
th1.start();
//th2.start();
// call方法返回值
Object o = ft.get();
System.out.println(Thread.currentThread().getName()+"=="+o);// 输出结果:main=-1
}
}
4.使用线程池来创建线程
使用线程池的方式:
背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。(数据库连接池)
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
corePoolSize
:核心池的大小
maximumPoolSize
:最大线程数
keepAliveTime
:线程没有任务时最多保持多长时间后会终止
JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService
:真正的线程池接口。常见子类ThreadPoolExecutor.
void execute(Runnable coommand)
:执行任务/命令,没有返回值,一般用来执行Runnable
Future.submit(Callable task)
:执行任务,有返回值,一般又来执行Callable
void shutdown()
:关闭连接池。
Executors | 工具类,线程池的工厂类,用于创建并返回不同类型的线程池 |
---|---|
Executors.newCachedThreadPool() | 创建一个可根据需要创建新线程的线程池 |
Executors.newFixedThreadPool(n) | 创建一个可重用固定线程数的线程池 |
Executors.newSingleThreadExecutor() | :创建一个只有一个线程的线程池 |
Executors.newScheduledThreadPool(n) | 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
public class TestNewThread implements Callable {
/**
* 创建一个Lock实例对象,实现加锁lock(),释放锁unlock()
* */
private Lock lock = new ReentrantLock();
@Override
public Integer call(){
try {
this.method(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
return -1;
}
/**
* @Description: 线程加锁测试
* @Param: [t]
* @Return: void
**/
private void method(Thread t) throws InterruptedException {
// 加锁,lock()方法后面必须紧跟try/catch语句块,中间不能出现抛出异常或者
// 使得代码无法运行到finally语句块的语句块
// lock.lock();无法获取资源就一直等待
// trylock()方法,返回一个Boolean,无法获取资源就返回false。可以设置其等待时间
if(lock.tryLock(20, TimeUnit.SECONDS)){
try {
System.out.println("锁"+t.getName()+"开启");
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁,unlock()必须在finally语句的第一行,先解锁,其它线程才可以使用资源
lock.unlock();
System.out.println("锁"+t.getName()+"释放");
}
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1、创建固定线程个数为十个的线程池
ExecutorService es = Executors.newFixedThreadPool(10);
// 2、new一个Runable或Callable接口的对象
TestNewThread t = new TestNewThread();
// 3、执行线程,最多十个
/*
es.execute(Runable r);常用来执行实现Runable接口的对象
submit(Callable<t> c)常用来实现Callable接口的对象
System.out.println(es.submit(t));返回一个FutureTask对象
*/
Future ft = es.submit(t);
System.out.println(Thread.currentThread().getName()+"="+ft.get());// 输出:main=-1
// 4、关闭线程池
es.shutdown();
}
}
线程的状态
- 新建状态
使用new
关键字和Thread
类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状
态直到程序start()
这个线程。
个人理解:就是new一个Thread对象出来
-
就绪状态
当线程对象调用了start()
方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待
JVM里线程调度器的调度。 -
运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线
程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 -
阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行
状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
可以分为三种:
等待阻塞:行状态中的线程执行 wait()
方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized
同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep()
或 join()
发出了 I/O请求时,线程就会进入到阻塞状态。
当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。注意: 一个线程如果进入阻塞状态,阻塞解除没有办法直接恢复到运行,会直接恢复就绪 一个线程如果一旦终止,没有办法恢复
sleep
线程休眠|睡眠
参数:毫秒ms
- 是一个静态方法
- 带着资源一起休眠
- 当一个执行到sleep方法,当前线程就会休眠指定时间,在休眠过程中,让出cpu的资源
礼让线程yield
:
让出cpu的资源,同时恢复就绪状态,等待cpu重新调度,是否还是会调度到原线程,不一定
插队线程join
void join() 等待该线程终止。
让当前线程进入阻塞状态
判断线程状态Thread.State
getState() 获取线程状态 返回值是一个枚举类型
线程优先级
每一个线程都有优先级别问题 优先执行谁 ,概率问题,但是 不能控制一个线程是否先执行
1~10
1为最小 10为最大 默认为5
static int MAX_PRIORITY
线程可以具有的最高优先级。
static int MIN_PRIORITY
线程可以具有的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。
getPriority() 获取优先级
setPriority() 设置优先级
线程安全
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
“同”字从字面上容易理解为一起动作,其实不是,“同”字应是指协同、协助、互相配合。
在Java里面,通过 synchronized
进行同步的保证。它包括两种用法:synchronized
方法和 synchronized
块
死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
// 口红
class Lipstick{
}
// 镜子
class Mirror{
}
class Makeup extends Thread {
int flag;
String girl;
static Lipstick lipstick=new Lipstick();
static Mirror mirror= new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup(){
if(flag==0){
synchronized (lipstick) {
System.out.println(girl+"拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(girl+"拿着镜子!");
}
}
}
else{
synchronized (mirror) {
System.out.println(girl+"拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(girl+"拿着口红!");
}
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup(); m1.girl="大丫"; m1.flag=0;
Makeup m2 = new Makeup(); m2.girl="小丫"; m2.flag=1;
m1.start();
m2.start();
}
}
如何解决死锁问题:
- 往往是程序逻辑的问题。需要修改程序逻辑。
- 尽量不要同时持有两个对象锁。