java多线程基础

一、进程、线程、协程的概念

  • 进程:

    是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

  • 线程:

    是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。

  • 协程:

    是一种比线程更加轻量级的存在。一个线程也可以拥有多个协程。其执行过程更类似于子例程,或者说不带返回值的函数调用。

二、进程和线程的区别

  • 地址空间:

    线程共享本进程的地址空间,而进程之间是独立的地址空间。

  • 资源:

    线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。

  • 健壮性:

    多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

  • 执行过程:

    每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。

  • 可并发性:

    两者均可并发执行。

  • 切换时:

    进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

  • 其他:

    线程是处理器调度的基本单位,但是进程不是。

三 线程的生命周期

 

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

     

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

四 创建线程

1 Runnable 接口

class RunnableDemo implements Runnable {     private Thread t;     private String threadName;      RunnableDemo( String name) {         threadName = name;         System.out.println(Creating  +  threadName );     }      @Override     public void run() {         System.out.println(Running  +  threadName );         try {             for(int i = 4; i > 0; i--) {                 System.out.println(Thread:  + threadName + ,  + i);                 // 让线程睡眠一会                 Thread.sleep(50);             }         }catch (InterruptedException e) {             System.out.println(Thread  +  threadName +  interrupted.);         }         System.out.println(Thread  +  threadName +  exiting.);     }      public void start () {         System.out.println(Starting  +  threadName );         if (t == null) {             t = new Thread (this, threadName);             t.start ();         }     } }  public class TestThread {      public static void main(String args[]) {         RunnableDemo R1 = new RunnableDemo( Thread-1);         R1.start();          RunnableDemo R2 = new RunnableDemo( Thread-2);         R2.start();     } }  ##################################  Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-2, 1 Thread: Thread-1, 1 Thread Thread-2 exiting. Thread Thread-1 exiting.

2 继承Thread

Thread实现了Runnable接口,并提供了一些方法如setPriority(int priority)更改线程的优先级。 public final boolean isAlive()测试线程是否处于活动状态。

class ThreadDemo extends Thread {     private Thread t;     private String threadName;      ThreadDemo( String name) {         threadName = name;         System.out.println(Creating  +  threadName );     }      @Override     public void run() {         System.out.println(Running  +  threadName );         try {             for(int i = 4; i > 0; i--) {                 System.out.println(Thread:  + threadName + ,  + i);                 // 让线程睡眠一会                 Thread.sleep(50);             }         }catch (InterruptedException e) {             System.out.println(Thread  +  threadName +  interrupted.);         }         System.out.println(Thread  +  threadName +  exiting.);     }      @Override     public void start () {         System.out.println(Starting  +  threadName );         if (t == null) {             t = new Thread (this, threadName);             t.start ();         }     } }  public class TestThread2 {      public static void main(String args[]) {         ThreadDemo T1 = new ThreadDemo( Thread-1);         T1.start();          ThreadDemo T2 = new ThreadDemo( Thread-2);         T2.start();     } }  ################################  Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-2 Thread: Thread-2, 4 Running Thread-1 Thread: Thread-1, 4 Thread: Thread-2, 3 Thread: Thread-1, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.

3 通过 Callable 和 Future 创建线程

  • 1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

  • 2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

  • 3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

  • 4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

 
public class CallableThreadTest implements Callable<String> {
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<String> ft = new FutureTask<>(ctt);
System.out.println(Thread.currentThread().getName()+主线程);
new Thread(ft,子线程).start();
try
{
System.out.println(子线程的返回值:+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}

}
@Override
public String call()
{
return Thread.currentThread().getName() + 执行中;
}
}
###############################################

main主线程
子线程的返回值:子线程执行中

五 线程锁

class Count implements Runnable {     private static int sCount = 0;     @Override     public void run() {         for (int i = 0; i < 10; i++) {                 sCount++;             System.out.println(Thread.currentThread().getName()+---+sCount);          }     } }  public class TestThreadBlock {     public static void main(String[] args) throws InterruptedException {         Count count = new Count();         Thread t1 = new Thread(count);         Thread t2 = new Thread(count);         t1.start();         t2.start();     } } #########################
Thread-0---2
Thread-1---2
Thread-0---3
Thread-1---4
Thread-0---5
Thread-0---7
Thread-0---8
Thread-0---9
Thread-0---10
Thread-0---11
Thread-0---12
Thread-0---13
Thread-1---6
Thread-1---14
Thread-1---15
Thread-1---16
Thread-1---17
Thread-1---18
Thread-1---19
Thread-1---20

synchronized

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象; 
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象; 
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象; 
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

  一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。

 

class Count implements Runnable {     private static int sCount = 0;     @Override     public void run() {         for (int i = 0; i < 10; i++) {             synchronized (this){                 sCount++;                 System.out.println(Thread.currentThread().getName()+---+sCount);             }         }     } }  public class TestThreadBlock {     public static void main(String[] args) throws InterruptedException {         Count count = new Count();         Thread t1 = new Thread(count);         Thread t2 = new Thread(count);         t1.start();         t2.start();     } } ######################### Thread-0---1 Thread-0---2 Thread-0---3 Thread-0---4 Thread-1---5 Thread-1---6 Thread-1---7 Thread-1---8 Thread-1---9 Thread-1---10 Thread-1---11 Thread-1---12 Thread-1---13 Thread-1---14 Thread-0---15 Thread-0---16 Thread-0---17 Thread-0---18 Thread-0---19 Thread-0---20

 

信号量Semaphore

  Semaphore 是用来保护一个或者多个共享资源的访问,Semaphore 内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于 1,意味着有共享资源可以访问,则使其计数器值减去 1,再访问共享资源。

  如果计数器值为 0, 线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加 1,之前进入休眠的线程将被唤醒并再次试图获得信号量。

class Count implements Runnable {     private static int sCount = 0;     Semaphore semaphore = new Semaphore(1);     @Override     public void run() {         for (int i = 0; i < 10; i++) {             try {                 semaphore.acquire();                 sCount++;                 System.out.println(Thread.currentThread().getName()+---+sCount);                 semaphore.release();             } catch (InterruptedException e) {                 e.printStackTrace();             }          }     } }  public class TestThreadBlock {     public static void main(String[] args) throws InterruptedException {         Count count = new Count();         Thread t1 = new Thread(count);         Thread t2 = new Thread(count);         t1.start();         t2.start();     } } ################# Thread-0---1 Thread-0---2 Thread-0---3 Thread-0---4 Thread-0---5 Thread-1---6 Thread-1---7 Thread-1---8 Thread-1---9 Thread-1---10 Thread-1---11 Thread-1---12 Thread-1---13 Thread-1---14 Thread-1---15 Thread-0---16 Thread-0---17 Thread-0---18 Thread-0---19 Thread-0---20

join

join方法使得线程之间的并行执行变为串行执行,通过调用线程的wait方法来达到同步的目的的。

class Count implements Runnable {     private static int sCount = 0;      @Override     public void run() {         for (int i = 0; i < 10; i++) {             sCount++;             System.out.println(Thread.currentThread().getName() + --- + sCount);         }     } }  public class TestThreadBlock {     public static void main(String[] args) throws InterruptedException {         Count count = new Count();         Thread t1 = new Thread(count);         Thread t2 = new Thread(count);         t1.start();         t2.start();         t1.join();         t2.join();     } } ####################### Thread-1---2 Thread-0---2 Thread-0---4 Thread-0---5 Thread-0---6 Thread-0---7 Thread-0---8 Thread-0---9 Thread-0---10 Thread-0---11 Thread-0---12 Thread-1---3 Thread-1---13 Thread-1---14 Thread-1---15 Thread-1---16 Thread-1---17 Thread-1---18 Thread-1---19 Thread-1---20

 

 

六 其他

volatile

使用 volatile 修饰共享变量后,每个线程要操作变量时会从主内存中将变量拷贝到本地内存作为副本,当线程操作变量副本并写回主内存后,会通过 CPU 总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。

volatile 保证了不同线程对共享变量操作的可见性,也就是说一个线程修改了 volatile 修饰的变量,当修改后的变量写回主内存时,其他线程能立即看到最新值。

sleep 和 wait

sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。

等待(wait)和通知(notify)

当在一个对象实例上调用了wait()方法后,当前线程就会在这个对象上等待。直到其他线程调用了这个对象的notify()方法或者notifyAll()方法。notifyAll()方法与notify()方法的区别是它会唤醒所有正在等待这个对象的线程,而notify()方法只会随机唤醒一个等待该对象的线程。

yield

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。