用两个线程交替打印数字和字母

前一段时间听马士兵老师讲课,讲到某公司的一个面试,两个线程,其中一个线程输出ABC,另一个线程输出123,如何控制两个线程交叉输出1A2B3C,由于本人多线程掌握的一直不是很好,所以听完这道题,个人感觉收获良多,这是一个学习笔记。这道题有多种解法,不过有些属于纯炫技,所以只记录常见的三种解法。首先看第一种 park 和 unpark package cn.bridgeli.demo; import com.google.common.collect.Lists; import java.util.List; import java.util.concurrent.locks.LockSupport; /** * @author BridgeLi * @date 2021/2/6 16:14 */ public class Thread_Communication_Park_Unpark { static Thread t1 = null; static Thread t2 = null; public static void main(String[] args) { final List<Integer> integers = Lists.newArrayList(1, 2, 3, 4, 5, 6, 7); final List<String> strings = Lists.newArrayList("A", "B", "C", "D", "E", "F", "G"); t1 = new Thread(() -> integers.forEach(item -> { System.out.print(item); LockSupport.unpark(t2); LockSupport.park(); }), "t1"); t2 = new Thread(() -> strings.forEach(item -> { LockSupport.park(); System.out.print(item); LockSupport.unpark(t1); }), "t2"); t1.start(); t2.start(); } } 这个是最简单的实现方法,LockSupport.park() 使当前线程阻塞,而 LockSupport.unpark() 则表示唤醒一个线程,所以他需要一个参数,表示你要唤醒哪个线程,很好理解,也比较简单。 ...

February 7, 2021 · 2 min · 413 words · Bridge Li

Java Thread 同步

之前遇到一个问题,就是如何让线程同步,由于自己多线程的东西实在不懂,所以不知道怎么办,但感觉应该是一个很简单的东西,所以就从网上搜一下资料,原来如此简单,直接调用 join 方法就好了。写篇博客记录一下 join 的使用方法。 作用 Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。具体看代码: package cn.bridgeli.demo; public class ThreadTest { public static void main(String[] args) throws InterruptedException { ThreadJoinTest t1 = new ThreadJoinTest("bridgeli"); ThreadJoinTest t2 = new ThreadJoinTest("liqiao"); t1.start(); /** * join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是: * 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕 * 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会 * * join方法可以传递参数,join(10000)表示main线程会等待t1线程10毫秒,10毫秒过去后, * main线程和t1线程之间执行顺序由串行执行变为普通的并行执行 */ t1.join(10000); t2.start(); } } package cn.bridgeli.demo; public class ThreadJoinTest extends Thread { public ThreadJoinTest(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + ":" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } 上面注释也大概说明了 join 方法的作用:在 主线程 中调用了 t1 线程的 join() 方法时,表示只有当 t1 线程执行完毕时,主线程 才能继续执行,也就是开始执行 t2。注意,join 方法其实也可以传递一个参数给它的,表示:如果 主线程 在 t1 执行 1000 毫秒之后,继续执行,也就是开启 t2 线程。 ...

May 12, 2018 · 3 min · 446 words · Bridge Li

ThreadLocal类之简单理解

当年实习的时候,当时公司一个相当有经验的工程师zeak带我们,从他那第一次听说了ThreadLocal类,但由于自己基础薄弱,没有理解到底怎么回事,工作中也没有用过,就一直没有太放在心上,刚好这一段时间不太忙,仔细玩了一下,欢迎高手批评。 ThreadLocal,线程本地变量。他为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。简单理解就是,对于非线程安全的变量在线程内部共享不用每次都new,是一种空间换时间的做法。ThreadLocal类提供的几个方法: public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { } 看名字就知道这些方法是干嘛的了,下面我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。首先是get方法的实现: public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } 先取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。那么getMap方法中又做了什么呢? ThreadLocalMap getMap(Thread t) { return t.threadLocals; } 原来是返回当前线程t中的一个成员变量threadLocals,而threadLocals则是: ThreadLocal.ThreadLocalMap threadLocals = null; 那就看看ThreadLocalMap的实现了: ...

June 11, 2017 · 2 min · 222 words · Bridge Li

关于synchronized用法的简单理解

synchronized 关键字既可以用于声明方法,也可以用于声明代码块,他们之间有什么区别呢?下面让我们逐一测试一下。 先看以第一个例子: package demo; public class SynchronizedDemo1 { public synchronized static void foo1() { } public synchronized static void foo2() { } } 在这个例子中,foo1 和 foo2 是类的两个静态方法。在不同的线程中,这两个方法的调用时互斥的,不仅是他们之间,任何两个不同的线程的调用也互斥。下面看第二个例子: package demo; public class SynchronizedDemo2 { public synchronized void foo3() { } public synchronized void foo4() { } } 在这个例子中,foo3 和 foo4 是类的两个成员方法,在多线程环境中,调用同一个对象的 foo3 或者 foo4 是互斥的,与上一个例子的差别在于,这是针对同一个对象的多线程方法调用互斥。下面再看最后一个例子: package demo; public class SynchronizedDemo3 { public void foo5() { synchronized (this) { } } public void foo6() { synchronized (SynchronizedDemo3.class) { } } } 在这个例子中,synchronized 用来修饰代码块,需要注意的是:synchronized 后面会有一个参数,其实这个就是用于同步的锁所属的对象。在这个例子中 synchronized (this) 与 SynchronizedDemo2 中加 synchronized 的成员方法是互斥的,而 synchronized (SynchronizedDemo3.class) 与 SynchronizedDemo1 中加 synchronized 的静态方法是互斥的。synchronized 用于修饰代码块会更加灵活,因为除了前面的这个例子外,synchronized 后面的参数可以是任意对象。

May 14, 2017 · 1 min · 99 words · Bridge Li

多线程应用之批量数据处理

我们都知道多线程是为了加快数据处理的,但至于怎么用,因为在工作中,我一直很少用,所以对多线程不是很了解。之前处理一个功能时,由于没有经验,导致速度很慢,前一段时间经老大提示,可以用多线程解决,突然发现原来多线程可以这么用可以来处理这一类问题,今天记录一下,作为笔记也作为一个给读者的参考,好了先说一下问题:公司的业务的业务不仅分模块而且是分库分表的,这样就导致一个问题,当我们要查询一个数据时,不能连表查询,不能只通过一个接口获得数据,最容易想到的常规做法就是: public List<Data> queryDatas() { List<Data> datas = queryDataFromDB(); if(null != datas && datas.size() > 0) { for(Data data : datas) { Object object = getObjectFromDb(data.getId()); data.setAttr1(object.getAttr); } } return datas; } 这么做,虽然可以满足业务需求,但效率实在是太低了,尤其是列表数据越大时,如果不只一个属性要这么做时,速度是会慢到要死人的。所以经老大提示参考同事的实现就采用了如下方法: private static final ExecutorService executor = Executors.newFixedThreadPool(20); public List<Data> queryDatas() { List<Data> datas = queryDataFromDB(); if(null != datas && datas.size() > 0) { batchSetAttr(datas); } return datas; } private boolean batchSetAttr(final List<Data> datas) { final CompletionService<Data> completionService = new ExecutorCompletionService<>(executor); for (final Data data : datas) { completionService.submit(new Callable<Data>() { @Override public Data call() throws Exception { Object object = getObjectFromDb(data.getId()); data.setAttr1(object.getAttr); return data; } }); } try { for (int i = 0, size = datas.size(); i < size; i++) { Future<Data> future = completionService.take(); Data d = future.get(); } } catch (InterruptedException e) { logger.error("InterruptedException", e); return false; } catch (ExecutionException e) { logger.error("ExecutionException", e); return false; } return true; } 利用多线程批量查询,返回时一一设置值,最终达到提高速度的目的。 最后需要说明一点:线程池的大小,大家可以根据自己的实际情况来设置,并不是越大越好;

July 24, 2016 · 1 min · 148 words · Bridge Li