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