网站怎样改logo,wordpress角色权限管理,长尾关键词什么意思,php成品网站源码前言
对于程序员来说#xff0c;线程一直是我们开发中最常出现的技术#xff0c;可以说#xff0c;使用起来完全没问题#xff0c;通过百度以及熟悉度可以顺手拈来#xff0c;但是对于深入理解#xff0c;却不是所有人都能做到#xff0c;写这篇文章的目的#xff0c;…前言
对于程序员来说线程一直是我们开发中最常出现的技术可以说使用起来完全没问题通过百度以及熟悉度可以顺手拈来但是对于深入理解却不是所有人都能做到写这篇文章的目的主要用于自己进行复习总结未来也会持续修改该文目前作者对多线程并不深入因此文中摘抄了很多大佬的一些文章感谢大佬们的开源。文中附有相关链接可自行跳转感谢呦 线程、进程 进程
指在系统中正在运行的一个应用程序程序一旦运行就是进程是系统进行资源分配的基本单位 线程
进程之内独立执行的一个单元执行流。线程是CPU调度和执行的最小单位包含在进程之中是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流一个进程中可以并发多个线程每条线程并行执行不同的任务。 为什么使用多线程
主线程不能执行耗时较长的任务否则会阻塞UI线程引起ANR、卡顿等问题只能在UI线程操作UI视图不能在子线程中操作Android 强制要求开发者在发起网络请求时必须在工作线程不能在主线程否则抛出NetworkOnMainThreadException 多线程场景
在Android中App从一启动。就算是一个空白demo它也是多线程应用原因是因为App是运行在art上art自带GC线程再加上App必有的主线程UI线程就组成了一个多线程应用日常使用的三方库比如Okhttp、Glide、RxJava处理耗时任务删除数据清空缓存操作数据库等等 UI 线程为什么不会结束因为它在初始化完毕后会执⾏死循环循环的内容是刷新界⾯ 并发和并行 **并发和并行最开始都是操作系统中的概念表示的是CPU执行多个任务的方式。这两个概念极容易混淆。如果使用的是单核CPU那么我们是无法执行并行操作的只能做到并发这样来充分调用CPU的资源。如果我们使用的是多核CPU我们才可以真正的意义上做到并行操作。 并发Concurrent在操作系统中是指一个时间段中有几个程序都处于已启动运行到运行完毕之间且这几个程序都是在同一个处理机上运行。操作系统的时间片分时调度。打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么就可以说听音乐和打游戏是并发的。 并行Parallel当系统CPu有一个以上的内核时当一个内核执行一个进程时另一个内核可以执行另一个进程两个进程互不抢占内核资源可以同时进行这种方式我们称之为并行(Parallel)。 演示在同一个程序中并发跟并行发生的场景 public class TestDemo {public static void main(String[] args) {//并发抢占共有资源TestThread testThread new TestThread();Thread thread new Thread(testThread, thread-01);Thread thread1 new Thread(testThread, thread-02);Thread thread2 new Thread(testThread, thread-03);Thread thread3 new Thread(testThread, thread-04);thread.start();thread1.start();thread2.start();thread3.start();//并行(互相不抢占资源)TestThread testThread1 new TestThread();TestThread testThread2 new TestThread();testThread1.start();testThread2.start();}static class TestThread extends Thread {public TestThread() { super(TestThread); }private int count 10;Overridepublic void run() {super.run();System.out.println(count--);}}}异步和同步 同步和异步关注的是消息通信机制. 同步是指: 发送方发出数据后, 等待接收方发回响应后才发下一个数据包的通讯方式. 就是在发出一个调用时, 在没有得到结果之前, 该调用就不返回, 但是一旦调用返回, 就得到返回值了. 也就是由调用者主动等待这个调用的结果. 异步是指: 发送方发出数据后, 不等待接收方发回响应, 接着发送下个数据包的通讯方式. 当一个异步过程调用发出后, 调用者不会立刻得到结果. 而是在调用发出后, 被调用者通过状态、通知来通知调用者, 或通过回调函数处理这个调用. 使用线程的几种方式 Java Thread Thread thread1 new Thread() {Overridepublic void run() {super.run();System.out.println(直接new出来简单粗暴);}};thread1.start(); ThreadRunnable Runnable target new Runnable() {Overridepublic void run() {System.out.println(实例一个runnable对象交由线程使用方便复用);}};Thread thread new Thread(target);Thread thread2 new Thread(target);thread.start();thread2.start();ThreadFactoryRunnable ThreadFactory threadFactory new ThreadFactory() {Overridepublic Thread newThread(Runnable runnable) {return new Thread(runnable, Thread- new Random().nextInt(1000));}};Runnable target1 new Runnable() {Overridepublic void run() {System.out.println(实例一个runnable对象交由线程工厂使用得到thread对象方便复用);}};threadFactory.newThread(target1);ExecutorServiceRunnable Executor executor Executors.newSingleThreadExecutor();Executor executor1 Executors.newCachedThreadPool();Executor executor2 Executors.newFixedThreadPool(10);Executor executor3 Executors.newScheduledThreadPool(1);Runnable target2 new Runnable() {Overridepublic void run() {System.out.println(实例一个runnable对象交由线程池使用线程池帮助集中管理线程避免资源浪费方便复用);}};executor.execute(target2);executor1.execute(target2);executor2.execute(target2);executor3.execute(target2);FutureCallable CallableString callable new CallableString() {Overridepublic String call() {try {Thread.sleep(1500);} catch (InterruptedException e) {e.printStackTrace();}return 提交一个任务到线程池里面去!;}};ExecutorService executor4 Executors.newCachedThreadPool();FutureString future executor4.submit(callable);try {String result future.get();System.out.println(result: result);} catch (InterruptedException | ExecutionException e){e.printStackTrace();}android专属
HandlerIntentServiceHandlerThreadAsyncTaskRxjava 考虑到篇幅以及这些异步机制都是大家比较熟知的暂不列出使用方式贴上大佬们写过的文章链接供大家了解如果后续有需要在进行列出不然篇幅太长看着看着很容易恶心 kotlin
Coroutine 线程安全 为什么会出现线程安全问题
Java 内存模型规定了所有的变量都存储在主内存中每条线程有自己的工作内存。线程的工作内存中保存了该线程中用到的变量的主内存副本拷贝线程对变量的所有操作都必须在工作内存中进行而不能直接读写主内存。线程访问一个变量首先将变量从主内存拷贝到工作内存对变量的写操作不会马上同步到主内存。不同的线程之间也无法直接访问对方工作内存中的变量线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。 解决方式 解决方式部分摘抄自面试官说说多线程并发问题 本人对于多线程相知不多目前也在学习中该部分暂时引用等后续会加入自己的见解进行改写 保证共享资源在同一时间只能由一个线程进行操作(原子性有序性)。将线程操作的结果及时刷新保证其他线程可以立即获取到修改后的最新数据可见性。 volatile
保证可见性不保证原子性 当写一个volatile变量时JVM会把本地内存的变量强制刷新到主内存中这个写操作导致其他线程中的缓存无效其他线程读会从主内存读。volatile的写操作对其它线程实时可见。 禁止指令重排序 不会对存在依赖关系的指令重排序例如 a 1;b a; a 和b存在依赖关系不会被重排序不能影响单线程下的执行结果。比如a1;b2;cab这三个操作,前两个操作可以重排序但是cab不会被重排序因为要保证结果是3 使用场景
对于一个变量只有一个线程执行写操作其它线程都是读操作这时候可以用 volatile 修饰这个变量。 单例双重锁为什么要用到volatile
public class TestInstance {private static volatile TestInstance mInstance;public static TestInstance getInstance(){ //1if (mInstance null){ //2synchronized (TestInstance.class){ //3if (mInstance null){ //4mInstance new TestInstance(); //5}}}return mInstance;
}假如没有用volatile并发情况下会出现问题线程A执行到注释5 new TestInstance() 的时候分为如下几个几步操作
1、分配内存2、初始化对象3、mInstance 指向内存
这时候如果发生指令重排执行顺序是132执行到第3的时候线程B刚好进来了并且执行到注释2这时候判断mInstance不为空直接使用一个未初始化的对象。所以使用volatile关键字来禁止指令重排序。 volatile 原理
在JVM底层volatile是采用内存屏障来实现的内存屏障会提供3个功能 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置也不会把前面的指令排到内存屏障的后面即在执行到内存屏障这句指令时在它前面的操作已经全部完成它会强制将缓存的修改操作立即写到主内存写操作会导致其它CPU中的缓存行失效写之后其它线程的读操作会从主内存读。 volatile 的局限性
volatile 只能保证可见性不能保证原子性写操作对其它线程可见但是不能解决多个线程同时写的问题。 Synchronized 使用场景
多个线程同时写一个变量。
例如售票余票是100张窗口A和窗口B同时各卖出一张票 假如余票变量用 volatile 修饰是有问题的。 A窗口获取余票是100B窗口获取余票也是100A卖出一张变成99刷新回主内存同时B卖出一张变成99也刷新回主内存会导致最终主内存余票是99而不是98。
前面说到volatile的局限性就是多个线程同时写的情况这种情况一般可以使用Synchronized。
Synchronized 可以保证同一时刻只有一个线程可执行某个方法或某个代码块。 Synchronized 原理
public class SynchronizedTest {public static void main(String[] args) {synchronized (SynchronizedTest.class) {System.out.println(123);}method();
}private static void method() {
}
}将这段代码先用javac命令编译再java p -v SynchronizedTest.class命令查看字节码部分字节码如下
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:stack2, locals3, args_size10: ldc #2 // class com/lanshifu/opengldemo/test/SynchronizedTest2: dup3: astore_14: monitorenter5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc #4 // String 12310: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto 2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: invokestatic #6 // Method method:()V26: return可以看到 4: monitorenter 和 14: monitorexit中间是打印的语句。
执行同步代码块首先会执行monitorenter指令然后执行同步代码块中的代码退出同步代码块的时候会执行monitorexit指令 。 使用Synchronized进行同步其关键就是必须要对对象的监视器monitor进行获取当线程获取monitor后才能继续往下执行否则就进入同步队列线程状态变成BLOCK同一时刻只有一个线程能够获取到monitor当监听到monitorexit被调用队列里就有一个线程出队获取monitor。详情参考www.jianshu.com/p/d53bf830f… 每个对象拥有一个计数器当线程获取该对象锁后计数器就会加一释放锁后就会将计数器减一所以只要这个锁的计数器大于0其它线程访问就只能等待。 Synchronized锁的升级
大家对Synchronized的理解可能就是重量级锁但是Java1.6对 Synchronized 进行了各种优化之后有些情况下它就并不那么重Java1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁。
偏向锁 大多数情况下锁不仅不存在多线程竞争而且总是由同一线程多次获得为了让线程获得锁的代价更低而引入了偏向锁。 当一个线程A访问加了同步锁的代码块时会在对象头中存 储当前线程的id后续这个线程进入和退出这段加了同步锁的代码块时不需要再次加锁和释放锁。 轻量级锁 在偏向锁情况下如果线程B也访问了同步代码块比较对象头的线程id不一样会升级为轻量级锁并且通过自旋的方式来获取轻量级锁。
重量级锁 如果线程A和线程B同时访问同步代码块则轻量级锁会升级为重量级锁线程A获取到重量级锁的情况下线程B只能入队等待进入BLOCK状态。 Synchronized 缺点
不能设置锁超时时间不能通过代码释放锁容易造成死锁 ReentrantLock
上面说到Synchronized的缺点不能设置锁超时时间和不能通过代码释放锁ReentranLock就可以解决这个问题。
在多个条件变量和高度竞争锁的地方用ReentrantLock更合适ReentrantLock还提供了Condition对线程的等待和唤醒等操作更加灵活一个ReentrantLock可以有多个Condition实例所以更有扩展性。 ReentrantLock 的使用
lock 和 unlock ReentrantLock reentrantLock new ReentrantLock();System.out.println(reentrantLock-lock);reentrantLock.lock();try {System.out.println(睡眠2秒...);Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}finally {reentrantLock.unlock();System.out.println(reentrantLock-unlock);}实现可定时的锁请求tryLock public static void main(String[] args) {ReentrantLock reentrantLock new ReentrantLock();Thread thread1 new Thread_tryLock(reentrantLock);thread1.setName(thread1);thread1.start();Thread thread2 new Thread_tryLock(reentrantLock);thread2.setName(thread2);thread2.start();}static class Thread_tryLock extends Thread {ReentrantLock reentrantLock;public Thread_tryLock(ReentrantLock reentrantLock) {this.reentrantLock reentrantLock;}Overridepublic void run() {try {System.out.println(try lock: Thread.currentThread().getName());boolean tryLock reentrantLock.tryLock(3, TimeUnit.SECONDS);if (tryLock) {System.out.println(try lock success : Thread.currentThread().getName());System.out.println(睡眠一下 Thread.currentThread().getName());Thread.sleep(5000);System.out.println(醒了 Thread.currentThread().getName());} else {System.out.println(try lock 超时 : Thread.currentThread().getName());}} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(unlock: Thread.currentThread().getName());reentrantLock.unlock();}}}
打印的日志
try lock:thread1
try lock:thread2
try lock success :thread2
睡眠一下thread2
try lock 超时 :thread1
unlock:thread1
Exception in thread thread1 java.lang.IllegalMonitorStateExceptionat java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)at com.lanshifu.demo_module.test.lock.ReentranLockTest$Thread_tryLock.run(ReentranLockTest.java:60)
醒了thread2
unlock:thread2上面演示了trtLock的使用trtLock设置获取锁的等待时间超过3秒直接返回失败可以从日志中看到结果。 有异常是因为thread1获取锁失败不应该调用unlock。 Condition 条件
public static void main(String[] args) {Thread_Condition thread_condition new Thread_Condition();thread_condition.setName(测试Condition的线程);thread_condition.start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}thread_condition.singal();}static class Thread_Condition extends Thread {Overridepublic void run() {await();}private ReentrantLock lock new ReentrantLock();public Condition condition lock.newCondition();public void await() {try {System.out.println(lock);lock.lock();System.out.println(Thread.currentThread().getName() :我在等待通知的到来...);condition.await();//await 和 signal 对应//condition.await(2, TimeUnit.SECONDS); //设置等待超时时间System.out.println(Thread.currentThread().getName() :等到通知了我继续执行);} catch (Exception e) {e.printStackTrace();} finally {System.out.println(unlock);lock.unlock();}}public void singal() {try {System.out.println(lock);lock.lock();System.out.println(我要通知在等待的线程condition.signal());condition.signal();//await 和 signal 对应Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println(unlock);lock.unlock();}}}运行打印日志
lock
测试Condition的线程:我在等待通知的到来...
lock
我要通知在等待的线程condition.signal()
unlock
测试Condition的线程:等到通知了我继续执行
unlock上面演示了Condition的 await 和 signal 使用前提要先lock。 公平锁与非公平锁
ReentrantLock 构造函数传true表示公平锁。
公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的即先来先得的顺序。而非公平锁就是一种锁的抢占机制是随机获得锁的可能会导致某些线程一致拿不到锁所以是不公平的。 ReentrantLock 注意点
ReentrantLock使用lock和unlock来获得锁和释放锁unlock要放在finally中这样正常运行或者异常都会释放锁使用condition的await和signal方法之前必须调用lock方法获得对象监视器 并发包
通过上面分析并发严重的情况下使用锁显然效率低下因为同一时刻只能有一个线程可以获得锁其它线程只能乖乖等待。
Java提供了并发包解决这个问题接下来介绍并发包里一些常用的数据结构。 ConcurrentHashMap
我们都知道HashMap是线程不安全的数据结构HashTable则在HashMap基础上get方法和put方法加上Synchronized修饰变成线程安全不过在高并发情况下效率底下最终被ConcurrentHashMap替代。
ConcurrentHashMap 采用分段锁内部默认有16个桶get和put操作首先将key计算hashcode然后跟16取余落到16个桶中的一个然后每个桶中都加了锁ReentrantLock桶中是HashMap结构数组加链表链表过长转红黑树。
所以理论上最多支持16个线程同时访问。 LinkBlockingQueue
链表结构的阻塞队列内部使用多个ReentrantLock /** Lock held by take, poll, etc */private final ReentrantLock takeLock new ReentrantLock();/** Wait queue for waiting takes */private final Condition notEmpty takeLock.newCondition();/** Lock held by put, offer, etc */private final ReentrantLock putLock new ReentrantLock();/** Wait queue for waiting puts */private final Condition notFull putLock.newCondition();private void signalNotEmpty() {final ReentrantLock takeLock this.takeLock;takeLock.lock();try {notEmpty.signal();} finally {takeLock.unlock();}}/*** Signals a waiting put. Called only from take/poll.*/private void signalNotFull() {final ReentrantLock putLock this.putLock;putLock.lock();try {notFull.signal();} finally {putLock.unlock();}}源码不贴太多简单说一下LinkBlockingQueue 的逻辑 从队列获取数据如果队列中没有数据会调用notEmpty.await();进入等待。在放数据进去队列的时候会调用notEmpty.signal();通知消费者1中的等待结束唤醒继续执行。从队列里取到数据的时候会调用notFull.signal();通知生产者继续生产。在put数据进入队列的时候如果判断队列中的数据达到最大值那么会调用notFull.await();等待消费者消费掉也就是等待3去取数据并且发出notFull.signal();这时候生产者才能继续生产。 LinkBlockingQueue 是典型的生产者消费者模式源码细节就不多说。 原子操作类AtomicInteger
内部采用CAScompare and swap保证原子性
举一个int自增的例子 AtomicInteger atomicInteger new AtomicInteger(0);atomicInteger.incrementAndGet();//自增源码看一下 /*** Atomically increments by one the current value.** return the updated value*/public final int incrementAndGet() {return U.getAndAddInt(this, VALUE, 1) 1;}U 是 Unsafe看下 Unsafe#getAndAddInt public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 var4));return var5;}通过compareAndSwapInt保证原子性。 线程通信 该部分摘抄自java中的多线程线程使用、线程安全、线程通信 本人对于多线程相知不多目前也在学习中该部分暂时引用等后续会加入自己的见解进行改写 线程有自己的私有空间但当我多个线程之间相互协作的时候就需要进行线程间通信方本节将介绍Java线程之间的几种通信原理。 锁与同步
这种方式主要是对全局变量加锁即用synchronized关键字对对象或者代码块加锁lock来达成线程间通信。
这种方式可详见上一节线程同步中的例子。
等待/通知机制
基于“锁”的方式需要线程不断去尝试获得锁这会耗费服务器资源。
Java多线程的等待/通知机制是基于Object类的wait()方法和notify(), notifyAll()方法来实现的
wait()方法和notify()方法必须写在synchronized代码块里面
wait()和notify()方法必须通过获取的锁对象进行调用因为wait就是线程在获取对象锁后主动释放对象锁同时休眠本线程直到有其它线程调用对象的notify()唤醒该线程才能继续获取对象锁并继续执行。相应的notify()就是对对象锁的唤醒操作因而必须放在加锁的synchronized代码块环境内。
notify()方法会随机叫醒一个正在等待的线程而notifyAll()会叫醒所有正在等待的线程被唤醒的线程重新在就绪队列中按照一定算法最终再次被处理机获得并进行处理而不是立马重新获得处理机。
public class mythread {private static Object lock new Object();static class ThreadA implements Runnable {Overridepublic void run() {synchronized (lock) {for (int i 0; i 5; i) {try {System.out.println(ThreadA: i);lock.notify();lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}lock.notify();}}}static class ThreadB implements Runnable {Overridepublic void run() {synchronized (lock) {for (int i 0; i 5; i) {try {System.out.println(ThreadB: i);lock.notify();lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}lock.notify();}}}public static void main(String[] args) {new Thread(new ThreadA()).start();try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}new Thread(new ThreadB()).start();}
}
join方法
join()方法让当前线程陷入“等待”状态等join的这个线程执行完成后再继续执行当前线程。
当主线程创建并启动了耗时子线程而主线程早于子线程结束之前结束时就可以用join方法等子线程执行完毕后从而让主线程获得子线程中的处理完的某个数据。
join()方法及其重载方法底层都是利用了wait(long)这个方法。
public class mythread {static class ThreadA implements Runnable {Overridepublic void run() {try {System.out.println(子线程睡一秒);Thread.sleep(1000);System.out.println(子线程睡完了一秒);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {Thread thread new Thread(new ThreadA());thread.start();thread.join();System.out.println(如果不加join方法这行就会先打印出来);}
}sleep方法
sleep方法是Thread类的一个静态方法。它的作用是让当前线程睡眠一段时间
Thread.sleep(long)
这里需要强调一下sleep方法是不会释放当前的锁的而wait方法会。这也是最常见的一个多线程面试题。
sleep方法和wait方法的区别
wait可以指定时间也可以不指定而sleep必须指定时间。wait释放cpu资源同时释放锁sleep释放cpu资源但是不释放锁所以易死锁。wait必须放在同步块或同步方法中而sleep可以再任意位置
ThreadLocal类
ThreadLocal是一个本地线程副本变量工具类可以理解成为线程本地变量或线程本地存储。严格来说ThreadLocal类并不属于多线程间的通信而是让每个线程有自己“独立”的变量线程之间互不影响。
ThreadLocal类最常用的就是set方法和get方法。示例代码
public class mythread {static class ThreadA implements Runnable {private ThreadLocalString threadLocal;public ThreadA(ThreadLocalString threadLocal) {this.threadLocal threadLocal;}Overridepublic void run() {threadLocal.set(A);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ThreadA输出 threadLocal.get());}public static void main(String[] args) {ThreadLocalString threadLocal new ThreadLocal();new Thread(new ThreadA(threadLocal)).start();}}
}可以看到ThreadA可以存取自己当前线程的一个值。如果开发者希望将类的某个静态变量user ID或者transaction ID与线程状态关联则可以考虑使用ThreadLocal而不是在每个线程中声明一个私有变量来操作加“重”线程。
InheritableThreadLocal是ThreadLocal的继承子类不仅当前线程可以存取副本值而且它的子线程也可以存取这个副本值。
信号量机制
JDK提供了一个类似于“信号量”功能的类Semaphore。在多个线程超过2个需要相互合作的场景下我们用简单的“锁”和“等待通知机制”就不那么方便了。这个时候就可以用到信号量。JDK中提供的很多多线程通信工具类都是基于信号量模型的。
管道
管道是基于“管道流”的通信方式。JDK提供了PipedWriter、 PipedReader、 PipedOutputStream、 PipedInputStream。其中前面两个是基于字符的后面两个是基于字节流的。
应用场景管道多半与I/O流相关。当我们一个线程需要先另一个线程发送一个信息比如字符串或者文件等等时就需要使用管道通信了。
public class Pipe {static class ReaderThread implements Runnable {private PipedReader reader;public ReaderThread(PipedReader reader) {this.reader reader;}Overridepublic void run() {System.out.println(this is reader);int receive 0;try {while ((receive reader.read()) ! -1) {System.out.print((char)receive);}} catch (IOException e) {e.printStackTrace();}}}static class WriterThread implements Runnable {private PipedWriter writer;public WriterThread(PipedWriter writer) {this.writer writer;}Overridepublic void run() {System.out.println(this is writer);int receive 0;try {writer.write(test);} catch (IOException e) {e.printStackTrace();} finally {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}}public static void main(String[] args) throws IOException, InterruptedException {PipedWriter writer new PipedWriter();PipedReader reader new PipedReader();writer.connect(reader); // 这里注意一定要连接才能通信new Thread(new ReaderThread(reader)).start();Thread.sleep(1000);new Thread(new WriterThread(writer)).start();}
}// 输出
this is reader
this is writer
test致谢
面试官说说多线程并发问题探索 Android 多线程优化方法Java并发面试题漫话如何给女朋友解释什么是并发和并行Java同步和异步阻塞和非阻塞java中的多线程线程使用、线程安全、线程通信 作者BuildF 链接https://juejin.cn/post/7050064181680144392 最后
如果想要成为架构师或想突破20~30K薪资范畴那就不要局限在编码业务要会选型、扩展提升编程思维。此外良好的职业规划也很重要学习的习惯很重要但是最重要的还是要能持之以恒任何不能坚持落实的计划都是空谈。
如果你没有方向这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》帮大家将杂乱、零散、碎片化的知识进行体系化的整理让大家系统而高效地掌握Android开发的各个知识点。 相对于我们平时看的碎片化内容这份笔记的知识点更系统化更容易理解和记忆是严格按照知识体系编排的。 全套视频资料
一、面试合集 二、源码解析合集 三、开源框架合集 欢迎大家一键三连支持若需要文中资料直接点击文末CSDN官方认证微信卡片免费领取↓↓↓