Java进阶——线程与多线程


线程和多线程

概念

  • 程序

    程序是一段静态代码。
  • 进程

    进程是程序的一次动态执行过程(从代码加载、执行、执行完毕的完整过程)。进程是资源分配的最小单位。
  • 线程

    线程是CPU调度的最小执行单位。程序执行过程中可以产生多个线程。

进程和线程的区别

  1. 进程:一个应用程序对应一个进程;进程是资源分配的最小单位;通过多线程占据系统资源;进程之间数据状态完全独立。
  2. 线程:一个进程可以有多个线程;线程是执行程序的最小单元;线程是占用CPU的基本单位;线程之间共享一块内存空间。

线程的生命周期

  • 新建状态

    线程对象创建,还未调用start()方法。
  • 就绪状态

    调用start()方法,但调度程序还未将其选为可运行线程。
  • 运行状态

    线程调度程序从可运行池中选择一个线程作为当前线程。
  • 阻塞状态

    • 等待状态
    • 阻塞状态
    • 睡眠状态
      线程是活的,但没有条件运行;当某事件发生后,可返回到就绪状态。
  • 死亡状态

    线程run()方法完成。线程一旦死亡,不可复生(调用start()会抛异常)。

Java的线程

主线程

每个Java程序都有一个默认的主线程。

当JVM加载代码,发现main方法后,会立即启动一个线程(主线程)
主线程特点

  1. 产生其他子线程的线程
  2. 不一定是最后完成执行的线程

创建线程

  1. 继承Thread类
    • 重写run()方法
    • new一个线程对象
    • 调用对象的start()方法启动线程
  2. 实现Runnable接口
    • 实现run()方法
    • 创建一个Runnable类的对象
    • 创建Thread类对象,将Runnable对象作为参数
    • 调用Thread对象的start()方法启动线程
  • 一个线程只能被启动一次run()方法执行结束后,线程结束。
  • 一个程序多个线程,线程只能保证开始时间,结束时间和执行顺序无法确定。
  • 线程调度采用队列形式;JVM线程调度程序决定执行就绪状态的某个线程。
  • 运行的线程有名字
    • 可通过JVM默认线程名字
    • 自定义线程名字setName()
//给子线程命名
//默认为Thread-
Thread1 t1=new Thread1();
Thread t=new Thread(t1);
t.start();
t.setName("t1");

//获取主线程,并命名
Thread.currentThread().setName("mm");
System.out.println(Thread.currentThread().getName());

创建线程方法对比

  1. 继承Thread类
    1. 编写简单,访问当前线程this
    2. java是单继承机制,不能继承其他父类;没有达到资源共享
  2. 实现Runnable接口
    1. 编程复杂,访问当前线程Thread.currentThread()
    2. java是多接口实现,可继承其他类;可多个线程共享同一个目标对象。
//Runnable
//多线程解决同一问题
Thread1 t1 = new Thread1();
new Thread(t1).start();
new Thread(t1).start();
new Thread(t1).start();

//Thread
//资源不共享
Thread t1 = new Thread1();
Thread t2 = new Thread2();
Thread t3 = new Thread3();
t1.start();
t2.start();
t3.start();

线程的方法

方法名 功能
start() 启动线程,让线程从新建状态进入就绪状态队列
run() 普通方法,线程对象被调度后执行的操作
sleep() 暂停线程的执行,休眠线程
yield() 暂停正在执行的线程,让同等优先级的线程运行
join() 暂停当前线程的执行,等调用该方法的线程执行完后,线程返回就绪状态
interrupt() 唤醒休眠的线程
stop() 终止线程
isAlive() 测试线程的状态;新建/死亡状态=false
currentThread() 返回当前正在执行线程对象的引用
//输出 (新建状态、死亡状态为false)
//false  false
//join方法将主线程暂停,先执行Thread1线程
Thread1 t1=new Thread1();
Thread t=new Thread(t1);
System.out.print(t.isAlive());
t.start();
t.join();
System.out.print(t.isAlive());

设置线程优先级

  • 通过Thread的setPriority()方法设置线程优先级
    • Thread.MIN_PRIORITY ——1
    • Thread.NORM_PRIORITY ——5
    • Thread.MAX_PRIORUTY ——10
  • 通过thread的getPriority()方法得到线程优先级
  • 线程默认优先级为创建其的运行状态线程的优先级

线程让步

当线程池中的线程具有相同优先级

  1. 选择一个线程运行,知道线程阻塞或运行结束
  2. 时间分片,为线程池中每个线程提供均等运行机会

多线程运行时,JVM按优先级调度,级别相同的由操作系统按时间片分配。

阻止线程执行

线程睡眠 sleep()

当线程睡眠时,暂停执行;苏醒前不会回到就绪状态;
当睡眠时间到期,线程回到就绪状态。

  • 线程睡眠可帮助其他线程获得运行机会的最好方法
  • 线程苏醒后,返回到就绪状态
  • sleep()指定时间为最短睡眠时间
  • sleep()为静态方法,只能控制当前运行的线程

线程等待 yield()

线程让步,暂停当前正在执行的线程对象,并执行同等优先级的其他线程
yield()使线程从运行状态——>就绪状态;让步的线程也有可能被线程调度程序选中。

线程阻塞 join()

线程A中调用线程B的join()方法,让线程A置于线程B的尾部。
在线程B执行完毕之前,线程A一直处于阻塞状态,只有当B线程执行完毕时,A线程才能继续执行

当join(100)带有参数时,如果A线程中掉用B线程的join(100),则表示A线程会等待B线程执行100毫秒,100毫秒过后,A、B线程并行执行;同时join(0)==join()

join方法必须在线程start方法调用之后调用才有意义

在主线程中执行程序:创建A、B两个子线程,首先调用线程A的start()方法执行线程A;
调用线程A的join()方法,使主线程进入阻塞状态,只有当线程A执行完毕后,才能执行主线程
线程A执行完毕后,主线程才可执行,调用线程B的start()方法执行线程B。

Thread a = new ThreadA();
Thread b = new ThreadB();
//线程A开始执行
a.start();
//线程A调用join()
a.join();
//线程B开始执行
b.start();

多线程

对象互斥锁

Java每个对象都对应一个互斥锁的标记。
每个对象只有一个锁(lock)与之相关联.
synchronized关键字与对象互斥锁联合使用,保证对象在任意时刻只能由一个线程访问。
避免多个线程进行访问导致数据不同步的问题。

  • 修饰代码块,该代码块在任意时刻只能由一个线程访问
    • 作用范围:代码块{}的内容
    • 作用对象:调用该代码块的对象
//实现Runnable接口
public class Thread1 implements Runnable {

	private static int count;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		// 1. 修饰代码块
		// 同步语句块
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				count++;
				System.out.println(Thread.currentThread().getName() + ":" + count);
			}
		}

	}

}
// 同一个对象
//实现资源共享、资源同步
Thread1 thread = new Thread1();
new Thread(thread).start();
new Thread(thread).start();

// 不同对象
//实现资源同步、多线程进行处理
Thread1 thread = new Thread1();
Thread1 thread1 = new Thread1();
new Thread(thread).start();
new Thread(thread1).start();
  • 修饰方法,表示该方法在任意时刻只能由一个线程访问
    • 作用范围:方法内容
    • 作用对象:调用方法的对象
    • 关键字synchronized不可继承
    • 定义接口不可使用关键字synchronized修饰
    • 构造方法不可使用关键字synchronized修饰,但可以用同步代码块
public synchronized void print(){
      //todo
}
  • 修饰静态方法,
    • 作用范围:静态方法内容
    • 作用对象:这个类的所有对象
public synchronized static void print(){
      //todo
}
  • 修饰类,表示该类的所有对象公用一把锁
    • 作用范围:{}包括的所有内容
    • 作用对象:这个类的所有对象
class ClassTest{
      public void method(){
            synchronized(ClassTest.class){
                  //todo
            }
      }
}

多线程同步

为了更好的解决多个交互线程之间的运行进度。
引入wait()方法与notify()方法
wait()方法:使当前线程进行等待状态
notify()方法:通知那些等待该对象锁的其他线程,使其重新获取该对象的对象锁。

  • wait()notify()方法必须配合synchronized关键字使用
  • wait()会释放锁,notify()不会释放锁
  • wait()方法执行后,执行interrupt()方法会报异常

死锁

死锁:当两个或两个以上的线程在执行过程中时,因争夺资源造成互相等待,若无外力作用,线程都无法推进下去的现象。
必要条件

  1. 互斥条件
    • 线程对分配到的资源进行排他性使用;其他线程不可使用。
  2. 请求、保持条件
    • 线程保持至少一个资源,但又提出新的资源请求。
  3. 不可剥夺条件
    • 线程获得的资源在使用完之前,不可被剥夺;只能在使用完后自主释放。
  4. 环路等待条件
    • 发生死锁时,必然存在线程请求资源、资源被另一线程占用的环。

线程池

引入

为什么有线程池

通过在主线程中new一个Thread线程

  1. 新建线程对象性能差
  2. 线程缺乏统一管理,造成多线程之间的死锁、同步、资源协调问题
  3. 缺乏定期执行等功能

线程池优点

  1. 重用线程、减少线程的创建、死亡开销。
  2. 可控制最大并发线程数,提高系统资源利用率
  3. 提供定期执行等功能

线程池

  • newCachedThreadPool

可缓存线程池。
当请求线程数大于线程池长度

  1. 回收空闲线程
  2. 没有空闲线程可回收时,创建新线程
//可缓存的线程
ExecutorService pool = Executors.newCachedThreadPool();
MyThread thread = new MyThread();
//线程池执行子线程
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
//此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
pool.shutdown();
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
  • newFixdThreadPool

定长线程池
可控制线程最大并发数,超出的线程会在队列等待
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

//定长线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
MyThread thread = new MyThread();
//线程池执行子线程
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
//关闭线程池
pool.shutdown();
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
  • newScheduledThreadPool

延迟连接池
支持定时、周期性任务执行

//创建延时线程池-定长
ScheduledThreadPoolExecutor	pool = new ScheduledThreadPoolExecutor(3);
//创建子线程
MyThread thread = new MyThread();
//线程延迟执行
/* 
 * scheduleAtFixedRate(Runnable,long,long,TimeUnit);
 *  1.Runnable:子线程
 *  2.long:该线程池延迟毫秒
 *  3.long:两次执行最短间隔
 *  4.TimeUnit:计时单位
 *  */
pool.scheduleAtFixedRate(thread, 5000, 1000, TimeUnit.MILLISECONDS);
  • newSingleThreadExecutor

单线程化线程池
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

ExecutorService pool = Executors.newSingleThreadExecutor();
MyThread thread = new MyThread();
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.execute(thread);
pool.shutdown();
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1

在此感谢以下博主


Java中Runnable和Thread的区别