JUC(1-多线程)
|总字数:4.4k|阅读时长:17分钟|浏览量:|
串行、并行和并发
- 串行:所有任务都是按先后顺序进行,一次只能取一个任务,并执行这个任务
 
- 并行:可以同时取得多个任务,并同时去执行所取得的这些任务
 
- 并发:多个程序可以同时运行的一种现象
 
并行和并发的区别
- 并行是指两个或者多个事件在同一时刻发生,而并发是指两个或多个事件在同一时间间隔发生
 
- 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件
 
线程和进程的区别
- 进程和线程的定义
 
- 进程:是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高
 
- 线程:是进程的一个实体,是 CPU 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行
 
- 资源管理的差异
 
- 进程:独立资源意味着每个进程有自己的代码段、数据段和堆;即使一个进程崩溃,其他进程仍然可以正常运行。
 
- 线程:线程共享进程的堆、方法区、全局变量等,但拥有独立的栈和程序计数器。线程共享资源会带来高效的通信,但也需要额外注意同步问题。
 
- 性能对比
 
- 线程创建比进程快:线程复用进程资源,无需重新分配。
 
- 上下文切换:进程切换涉及到切换内存页、文件描述符、资源等,线程切换仅需保存和加载线程的寄存器状态。
 
线程状态及转换

1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public enum State {          NEW,          RUNNABLE,          BLOCKED,          WAITING,          TIMED_WAITING,          TERMINATED; }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | public class TestThread09 {     public static void main(String[] args) throws InterruptedException {         Thread thread = new Thread(() -> {             for (int i = 0; i < 5; i++) {                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }             System.out.println("///////");         });
                   Thread.State state = thread.getState();         System.out.println(state);
                   thread.start();         state=thread.getState();         System.out.println(state);
          while (state!=Thread.State.TERMINATED){             Thread.sleep(100);             state=thread.getState();             System.out.println(state);         }     } }
  | 
 
创建线程的方式
- 继承 Thread 类创建线程
 
1 2 3 4 5 6 7 8 9 10 11 12
   |  public class MyThread extends Thread {      public void run(){                } }
  public class Main {       public static void main(String[] args){         new MyThread().start();     }                }
 
  | 
 
- 实现 Runnable 接口创建线程
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   |  public class MyThread2 implements Runnable {   public void run(){ 		   } }
  public class Main {   public static void main(String[] args){          MyThread2 myThread=new MyThread2();     Thread thread=new Thread(myThread);     thread().start();        } }
 
  | 
 
- 通过 Callable 和 FutureTask 创建线程
 
1 2 3 4
   | @FunctionalInterface public interface Callable<V> {     V call() throws Exception;  }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | public class Demo01 implements Callable {     int i=10;
      public static void main(String[] args) {         FutureTask futureTask=new FutureTask(new Demo01());         new Thread(futureTask).start();         try {             System.out.println(futureTask.get());         } catch (InterruptedException e) {             e.printStackTrace();         } catch (ExecutionException e) {             e.printStackTrace();         }     }
      @Override     public Object call() throws Exception {         System.out.println("子线程:"+i--);         return i;     } }
  | 
 
线程写法(lambda 表达式)
1 2 3 4 5 6 7 8 9 10 11
   |  interface ILike{     void like(); }
 
  class Like implements ILike{     public void like() {         System.out.println("I Love You!");     } }
 
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | public class TestLambda01 {          static class Like2 implements ILike{         public void like() {             System.out.println("I Love You Too!");         }     }
      public static void main(String[] args) {         ILike like1 = new Like();         like1.like();
          Like2 like2 = new Like2();         like2.like();
                   class Like3 implements ILike{             public void like() {                 System.out.println("I Love You More!");             }         }
          Like3 like3 = new Like3();         like3.like();
                   ILike iLike4 = new ILike() {             public void like() {                 System.out.println("I Love You More Than You!");             }         };         iLike4.like();
                   like1=()->System.out.println("I Love You More Than You Hi Hi...!");         like1.like();     } }
  | 
 
能否重复启动线程?
只能对处于新建状态的线程调用 start()方法,否则将引发 IllegalThreadStateException 异常
run()和 start()的区别
- start():启动一个线程,这时无需等待 run()方法体代码执行完毕,可以直接继续执行下面的代码,这时此线程处于就绪状态,并没有运行
 
- run():是在本线程里的,只是线程里的一个函数,如果直接调用 run(),其实就相当于调用了一个普通函数而已
 

sleep()和 wait()的区别
- sleep()方法指正在执行的线程主动让出 CPU(然后 CPU 就可以去执行其他任务),在 sleep 指定时间后 CPU 再回到该线程继续往下执行(注意: sleep 方法只让出了 CPU,而并不会释放同步资源锁);wait()方法指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了 notify()方法,之前调用 wait()的线程才会解除 wait 状态,可以去参与竞争同步资源锁,而并不会给它分配任务
 
- sleep()方法可以在任何地方使用;而 wait()方法则只能在同步方法或同步块中使用
 
- sleep()是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是 Object 的方法,调用会放弃对象锁,进入等待队列,待调用 notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池准备获得对象锁进入运行状态
 
- sleep()方法必须捕获 InterruptedException 异常;而 wait()不需要捕获异常
 
为什么 wait() 方法不定义在 Thread 中?
wait() 是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object)而非当前的线程(Thread)。
为什么 sleep() 方法定义在 Thread 中?
因为 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁
sleep()和 yield()的区别
- yield()方法
 
- 让出当前线程的 CPU 执行权,但并不阻塞线程。
 
- 当前线程从运行状态(Running) 切换到就绪状态(Runnable),等待重新被调度。
 
- 不保证当前线程一定会暂停执行,也不保证其他线程会获得 CPU 执行权。
 
- sleep() 方法
 
- 强制让当前线程进入阻塞状态(Timed Waiting),在指定时间内暂停执行。
 
- 线程在睡眠时间结束后重新进入就绪状态(Runnable)。
 
- sleep()方法一定会暂停当前线程。
 
notify()和 notifyAll()的区别
- notify():用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到 CPU 的执行
 
- notifyAll():用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到 CPU 的执行
 
Runnable 和 Callable 的区别
- Runnable 接口中的 run()方法的返回值是 void,它做的事情只是纯粹地去执行 run()方法中的代码而已;Callable 接口中的 call()方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获取异步执行的结果
 
- 线程类只是实现了 Runnable 或 Callable 接口,还可以继承其他类;而使用 Thread 的话,就不能再继承其他父类
 
停止、中断线程
- 使用 JDK 提供的 stop()、destroy()方法(不推荐)
 
- 使用一个标志位进行终止变量,当 flag = false,则终止线程运行
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | public class TestStop implements Runnable{     private boolean runThread = true;
      public void run(){         int i=0;         while (runThread){             System.out.println("run..."+i++);         }     }          public void stop(){         this.runThread = false;     }
      public static void main(String[] args) {         TestStop testStop = new TestStop();         new Thread(testStop).start();
          for (int i = 0; i < 10000; i++) {             System.out.println("main");             if (i==9000){                                  testStop.stop();                 System.out.println("线程停止了");             }         }     } }
  | 
 
- 使用 interrupt 中断线程
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | public class ThreadTest07 {     public static void main(String[] args) throws InterruptedException {         Thread t1 = new Thread(new Processor07());         t1.setName("t1");         t1.start();         Thread.sleep(5000);         t1.interrupt();     } }
  class Processor07 implements Runnable {     @Override     public void run() {         try {             Thread.sleep(10000000000000L);             System.out.println("Sleep is end");         } catch (Exception e) {             e.printStackTrace();         }         for(int i=0; i<20; i++){             System.out.println(Thread.currentThread().getName() + "-->" + i);         }     } }
  | 
 
线程休眠(sleep)
sleep(时间)指定当前线程阻塞的毫秒数,时间达到后线程进入就绪状态;sleep 存在 InterruptedException 异常;每一个对象都有一个锁,sleep 不会释放锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | public class TestSleep {     public static void main(String[] args) {                  Date date = new Date(System.currentTimeMillis());
          while (true){             try {                 Thread.sleep(1000);                 System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));                 date = new Date(System.currentTimeMillis());             } catch (InterruptedException e) {                 e.printStackTrace();             }         }     } }
  | 
 
线程礼让(yield)
礼让线程,将线程从运行状态转为就绪状态,让 CPU 重新调度(礼让不一定成功,看 CPU 心情)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | public class TestThread07 {
      public static void main(String[] args) {         MyYield myYield = new MyYield();         new Thread(myYield,"a").start();         new Thread(myYield,"b").start();     } }
  class MyYield implements Runnable{
      @Override     public void run() {         System.out.println(Thread.currentThread().getName()+"线程开始执行");         Thread.yield();         System.out.println(Thread.currentThread().getName()+"线程停止执行");     } }
  | 
 
线程阻塞(join)
待此线程执行完成后,再执行其他线程,此时其他线程阻塞
如何实现子线程先执行,主线程再执行?启动子线程后,立即调用该线程的 join()方法,则主线程必须等待子线程执行完成后再执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
   | public class TestThread08 implements Runnable{     @Override     public void run() {         for (int i = 0; i < 1000; i++) {             System.out.println("线程VIP来了......."+i);         }     }
      public static void main(String[] args) throws InterruptedException {         TestThread08 testThread08 = new TestThread08();         Thread thread = new Thread(testThread08);         thread.start();
          for (int i = 0; i < 1000; i++) {             if (i==200){                 thread.join();             }             System.out.println("main......."+i);         }     } }
  | 
 
用户线程和守护线程
- 用户线程:指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程
 
- 守护线程:指在程序运行的时候在后台提供一种通用服务的线程,用来服务于用户线程;不需要上层逻辑介入,也可以手动创建一个守护线程(当用户线程死亡,守护线程也会随之死亡)。在 JVM 中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
   |  public class TestThread10 {     public static void main(String[] args) {         You you = new You();         God god = new God();
          Thread thread = new Thread(god);         thread.setDaemon(true);          thread.start();
          new Thread(you).start();     } }
  class God implements Runnable{     @Override     public void run() {         while (true){             System.out.println("hi.........");         }     } }
  class You implements Runnable{     @Override     public void run() {         for (int i = 0; i < 36500; i++) {             System.out.println("hello........");         }         System.out.println("=============");     } }
 
  | 
 
线程优先级
线程的优先级用数字表示,范围从 1~10
- Thread.MIN_PRIORITY = 1;
 
- Thread.MAX_PRIORITY = 10;
 
- Thread.NORM_PRIORITY = 5;
 
使用以下方式改变或获取优先级:getPriority().setPriority(int xxx)
怎么保证线程安全?
当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调用和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么称这个对象是线程安全的
怎么保证多线程的运行安全?
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(atomic、synchronized)
 
- 可见性:一个线程对主内存的修改可以及时地被其他线程看到(synchronized、volatile)
 
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序(happens-before 原则)
 
使用案例
1 2 3 4 5 6 7 8 9 10 11
   | public class Demo01 {     public static void main(String[] args) {         List<String> list=new ArrayList<>();         for (int i = 0; i < 20; i++) {             new Thread(()->{                 list.add(UUID.randomUUID().toString());                 System.out.println(list);             },"线程"+i).start();         }     } }
  | 
 
会报一个 ConcurrentModificationException 的异常,中文名为:并发修改异常
Vector
1 2 3 4 5 6 7 8 9 10 11
   | public class Demo02 {     public static void main(String[] args) {         List<String> list=new Vector<>();         for (int i = 0; i < 20; i++) {             new Thread(()->{                 list.add(UUID.randomUUID().toString());                 System.out.println(list);             },"线程"+i).start();         }     } }
  | 
 
add 方法上加了 synchronized 关键字,让这个方法成为了同步方法块
1 2 3 4 5
   | public synchronized boolean add(E e) {     modCount++;     add(e, elementData, elementCount);     return true; }
  | 
 
Collections
Collections 提供了方法 synchronizedList 保证 list 是同步线程安全的,Collections 仅包含对集合进行操作或返回集合的静态方法
1 2 3 4 5 6 7 8 9 10 11
   | public class Demo03 {     public static void main(String[] args) {         List<String> list= Collections.synchronizedList(new ArrayList<>());         for (int i = 0; i < 20; i++) {             new Thread(()->{                 list.add(UUID.randomUUID().toString());                 System.out.println(list);             },"线程"+i).start();         }     } }
  | 
 
1 2 3 4 5
   | public void add(int index, E element) {     synchronized (mutex) {         list.add(index, element);     } }
  | 
 
CopyOnWriteArrayList
- CopyOnWriteArrayList 和 ArrayList 一样,是个可变数组,线程安全的,更新操作开销大(add()、set()、remove()等),因为要复制整个数组
 
- 独占锁效率低,采用读写分离思想,写线程获取到锁,其他写线程阻塞
 
1 2 3 4 5 6 7 8 9 10 11
   | public class Demo04 {     public static void main(String[] args) {         List<String> list=new CopyOnWriteArrayList<>();         for (int i = 0; i < 20; i++) {             new Thread(()->{                 list.add(UUID.randomUUID().toString());                 System.out.println(list);             },"线程"+i).start();         }     } }
  | 
 
思想:当要添加一个元素的时候,不直接往当前容器中添加,而是应该先将当前容器复制一份,然后在新的容器中进行添加操作,等到添加完成后,再让原容器的引用指向新的容器
问题:如果写线程还没来得及写进内存,那么其他的线程就会读到了脏数据
CopyOnWriteArrayList 在涉及到更新操作时,都会新建数组,所以 CopyOnWriteArrayList 效率都会很低,但是如果只是进行遍历查找的话,效率还是比较高的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | public boolean add(E element) {     synchronized (lock) {         checkForComodification();         CopyOnWriteArrayList.this.add(offset + size, element);         expectedArray = getArray();         size++;     }     return true; }
  public void add(int index, E element) {     synchronized (lock) {         Object[] es = getArray();         int len = es.length;         if (index > len || index < 0)             throw new IndexOutOfBoundsException(outOfBounds(index, len));         Object[] newElements;         int numMoved = len - index;         if (numMoved == 0)             newElements = Arrays.copyOf(es, len + 1);         else {             newElements = new Object[len + 1];             System.arraycopy(es, 0, newElements, 0, index);             System.arraycopy(es, index, newElements, index + 1, numMoved);         }         newElements[index] = element;         setArray(newElements);     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
   | public class Demo07 implements Runnable{
 
                private static List<String> list=new CopyOnWriteArrayList<>();     static {         list.add("aaa");         list.add("bbb");         list.add("ccc");     }
      @Override     public void run() {         Iterator<String> iterator = list.iterator();         while (iterator.hasNext()){             System.out.println(iterator.next());             list.add("ddd");         }     }
      public static void main(String[] args) {         Demo07 demo07 = new Demo07();         for (int i = 0; i < 10; i++) {             new Thread(demo07).start();         }     } }
  | 
 
volatile 和 synchronized
- 通过 volatile 数组来保存数据。一个线程读取 volatile 数组时,总能看到其他线程对该 volatile 变量最后的写入,保证读取到的数据总是最新的
 
- 通过互斥锁来保护数据。在更新操作时,都会率先去获取互斥锁,在修改完毕之后,先将数据更新到 volatile 数组中,然后再释放互斥锁,这样就能保证数据的安全
 
进程间通信的方式
- 管道(Pipe):提供单向或双向通信,适用于父子进程间的数据传输。
 
- 消息队列(Message Queue):允许进程通过消息传递来通信,具有队列特性,可以实现异步通信。
 
- 共享内存(Shared Memory):通过共享一块内存区域实现数据共享,效率高,但需要同步机制防止并发问题。
 
- 信号(Signal):用于进程间发送简单的通知或控制信号,轻量级但功能有限。
 
- 套接字(Socket):支持网络通信,也可用于同一主机上的进程通信,提供可靠的全双工通信。
 
- 文件(File):进程通过读写共享的文件交换数据,简单但速度较慢。
 
Java 线程同步和通信
线程同步:指多线程通过特定的东西(如互斥量)来控制线程之间的执行顺序,即线程之间通过同步建立起执行顺序的关系
线程同步的方法
- 使用 synchronized 关键字
 
- wait 和 notify
 
- 使用特殊域变量 volatile 实现线程同步
 
- 使用可重入锁(ReentrantLock)实现线程同步
 
- 使用阻塞队列实现线程同步
 
- 使用信号量 Semaphore
 
多线程之间的通信方法
- wait()、notify()、notifyAll()
 
- await()、signal()、signalAll()
 
- BlockingQueue