Java 线程与进程(1)(基础知识及用法(问答形式))

1 Java 中有几种新起线程的方式?run和start的区别? 方式一:自定义线程继承Thread
方式二:实现Runnable接口

public static void main(String[] args) { // 方式一 NewThread1 newThread1 = new NewThread1(); newThread1.start(); // 方式二 NewThread2 newThread2 = new NewThread2(); new Thread(newThread2).start(); }public static class NewThread1 extends Thread{ @Override public void run() { super.run(); System.out.println("new thread by extends Thread"); } }public static class NewThread2 implements Runnable{ @Override public void run() { System.out.println("new thread by implements Runnable"); } }

run是函数调用 和线程没有任何关系;
start会走底层,走系统层 最终调度到 run函数,这才是线程。
2 怎么让Java线程安全停止工作? stop()还是interrupt()的选择:
  • stop() : 暴力方式,不要用(过时), 如下载一部分终止危险,且线程机制中有来不及释放的碎片
  • interrupt():协作方式,可以安全停止(以下有Thread和Runnable两种方式的中断处理代码)
public class EndThread { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); myThread.start(); MyRunnable myRunnable = new MyRunnable(); Thread runableThread = new Thread(myRunnable); Thread.sleep(1000); runableThread.interrupt(); myThread.interrupt(); // 发出中断信号 但不会自己使线程停止 需线程中通过isInterrupted()做判断处理 }public static class MyThread extends Thread{ @Override public void run() { super.run(); String name = Thread.currentThread().getName(); while (!isInterrupted()) { System.out.println(name + " ==== is run state" + isInterrupted()); } } }public static class MyRunnable implements Runnable{ @Override public void run() { String name = Thread.currentThread().getName(); // 获取当前线程的中断信息状态 while (!Thread.currentThread().isInterrupted()) { System.out.println(name + " ==== is run state" + Thread.currentThread().isInterrupted()); } } } }

3 多线程中的并行和并发的理解 并行:类比几个车道就可以有几辆车并行行驶
并发:和时间有关系,计算吞吐量,类比车流量
4 线程常用的方法和线程状态(图),各个方法使用场景及流程 Java 线程与进程(1)(基础知识及用法(问答形式))
文章图片

4.1 sleep与wait的区别
sleep 是休眠,等休眠时间一过,才有执行权的资格(无条件可以休眠)
wait 是等待,需要人家来唤醒,唤醒后,才有执行权的资格(某些原因与条件需要等待一下)
注意:只是又有资格了,并不代表马上就会被执行,什么时候又执行起来,取决于操作系统调度
另外,sleep在 catch异常时会被 InterruptedException e 清除中断标记
4.2 如何控制线程的顺序——join控制
public class JoinThreadTest { public static void main(String[] args) throws InterruptedException{ JoinThread joinThreadA = new JoinThread("A"); JoinThread joinThreadB = new JoinThread("B"); joinThreadA.start(); //放弃当前线程的执行,并返回对应的线程的执行,joinThreadA执行完了,main线程才有执行的机会 joinThreadA.join(); joinThreadB.start(); }public static class JoinThread extends Thread{ public JoinThread(String name){ super(name); } @Override public void run() { super.run(); for(int i=0; i<100; i++){ System.out.println(this.getName() + ":" + i); } } } }

4.3 如何让出当前线程执行权—yield(几乎不用)
用法与上面的 join 一样,只不过执行效果是等其他线程执行完了最后才执行设置 yield 的线程。
4.4 关于守护线程----场景?:
// 主线程执行完毕后守护线程也跟着一起结束 public class DaemonThread { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { for (int i = 0; i < 50; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "---" + i); } } }; t.setDaemon(true); // 设置了守护线程 t.start(); // 谁调用的 main, main管了我 我就守护main// 主线程,是为了 等 Thread t 10秒钟 Thread.sleep(10000); // 走到这里,代表主线程结束,主线程结束不管t线程有没有结束都必须结束,因为t线程是守护线程,守护了main } }

5 对锁的使用和区分(类锁/对象锁/显示锁)—死锁 5.1 类锁------隐式锁
public class GPSEngine { private static GPSEngine gpsEngine; public GPSEngine getGpsEngine(){ if (gpsEngine == null){ gpsEngine = new GPSEngine(); } return gpsEngine; }// 持有GPSEngine.class的类锁 public static synchronized GPSEngine getGpsEngine1(){ if (gpsEngine == null){ // 其他任何线程不能进来,效率低 gpsEngine = new GPSEngine(); } return gpsEngine; }// 标准单例模式------DCL public static synchronized GPSEngine getInstance(){ if (gpsEngine == null){ // 持有类锁 synchronized (GPSEngine.class){ if (gpsEngine == null){ gpsEngine = new GPSEngine(); } } } return gpsEngine; } }

5.2 对象锁------隐式锁
public class SynTest { private long count =0; private Object obj = new Object(); // 作为一个锁 对象锁obj public long getCount() { return count; } public void setCount(long count) { this.count = count; } public void incCount(){ synchronized (obj){ // 使用一把锁------对象锁 count++; } } // synchronized == 类锁 public synchronized void incCount2(){ count++; } // this == 类锁 public void incCount3(){ synchronized (this){ count++; } } // 线程 private static class Count extends Thread{ private SynTest simplOper; public Count(SynTest simplOper) { this.simplOper = simplOper; }@Override public void run() { for(int i=0; i<10000; i++){ simplOper.incCount(); // count = count+10000 } } } public static void main(String[] args) throws InterruptedException { SynTest simplOper = new SynTest(); // 启动两个线程 Count count1 = new Count(simplOper); Count count2 = new Count(simplOper); count1.start(); count2.start(); Thread.sleep(50); System.out.println(simplOper.count); //20000 } }

5.3 显示锁 Lock/ReentrantLock
// 声明一个显示锁之可重入锁new 可重入锁------ 非公平锁 private Lock lock = new ReentrantLock(); public void incr(){ // 使用 显示锁 的规范 lock.lock(); try{ count++; } finally {// 打死都要执行最后一定会执行 lock.unlock(); } } // 可重入锁 意思就是递归调用自己,锁可以释放出来 // synchronized == 天生就是 可重入锁 // 如果是非重入锁 ,就会自己把自己锁死 public synchronized void incr2(){ count++; incr2(); }

6 生产者消费者案例(产生问题—解决方案) 6.1 初始版代码
public class CommunicationDemo { public static void main(String[] args) { // 创建资源对象 Res res = new Res(); // 创建生产者任务 ProduceRunnable produceRunnable = new ProduceRunnable(res); // 创建消费者任务 ConsumeRunnable consumeRunnable = new ConsumeRunnable(res); // 启动生产者任务 new Thread(produceRunnable).start(); // 启动消费者任务 new Thread(consumeRunnable).start(); } }// 生产者任务 class ProduceRunnable implements Runnable { private Res res; ProduceRunnable(Res res) { this.res = res; }@Override public void run() {//执行线程任务 for (int i = 0; i < 20; i++) { res.put("面包"); } } }// 消费者任务 class ConsumeRunnable implements Runnable { private Res res; ConsumeRunnable(Res res) { this.res = res; }@Override public void run() {//执行线程任务 for (int i = 0; i < 20; i++) { res.out(); } } }class Res { private String name; private int id; public void put(String name) { // 生产一个面包 id += 1; this.name = name + " 商品编号:" + id; System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.name); }public void out() {// 消费 id -= 1; System.out.println(Thread.currentThread().getName() +">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.name); } }

6.2 内置锁解决安全问题------先全部生产再消费
// 对操作共享数据的地方加入同步锁的方式来解决安全问题 public synchronized void put(String name) { id += 1; System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.id); }public synchronized void out() { System.out.println(Thread.currentThread().getName() +">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.id); id -= 1; }

6.3 实现生产一个消费一个------ wait/notify等待唤醒机制
class Res2 { private String name; private int id; private boolean flag; // 定义标记 默认第一次为falsepublic synchronized void put(String name) { // 生产一个面包 if (!flag) { id += 1; System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.id); flag = true; // 修改标记//唤醒 wait(); 冻结的线程,如果没有就是空唤醒,Java是支持的 notify(); // 注意:?? wait(); notify(); 这些必须要有同步锁包裹着//当前自己线程 冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了 try { wait(); // 生产好一个,休息下 } catch (InterruptedException e) { e.printStackTrace(); } } }public synchronized void out() {// 消费 // 消费之前判断标记 if (flag) { System.out.println(Thread.currentThread().getName() +">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.id); flag = false; //修改标记 notify(); try {wait(); // 消费完休息下 } catch (InterruptedException e) { e.printStackTrace(); } } } }

7 ThreadLocal——隔离线程(待完善) 设置的值只对当前设置的线程有用,类似于副本,不会全局修改,Handler中有用到
ThreadLocalMap: 性能最高
8 并发基础补充知识点(待完善) 8.1 线程的生命周期
8.2 第三种创建方式的实质
8.3 死锁的条件及解决方案
8.4 活锁
9 CAS(Compare And Swap) 9.1 CAS含义及原理
原子操作:全部完成或全部未做,不可再分,如synchronized
含义:比较并交换
原理:循环指令直到成功
9.2 悲观锁和乐观锁
悲观锁:上下文切换 一次切换3-5ms 效率低
乐观锁:一次指令0.6ns
9.3 CAS问题
ABA问题:期间被换了但保持原样(本质已变)——加个版本戳解决
开销问题
只能保证一个共享变量的原子操作
9.4 原子操作类的使用(凡是以Atomic开头的)
更新基本类型类
更新数组类
更新引用类型
10 队列和阻塞队列 10.1 含义
队列:先进先出
阻塞队列:BlockingQueue接口
10.2 常见阻塞队列
有界
ArrayBlockingQueue:
LinkedBlockingQueue:
无界
PriorityBlockingQueue:
DelayBlockingQueue:
LinkedTranceferQueue:
其他
SychrononsQueue:不存储元素的阻塞队列
LinkedBlockingDeque:
11 线程池 11.1 什么是线程池?为什么要用线程池?
缩短任务的总执行时间
11.2 ThreadPoolExcutor 线程池
各个参数的含义:
corePoolSize:核心线程数
maxnumPoolSize:最大线程数
keepAliveTime:空闲线程存活时间
unit:存活时间单位
workQueue:阻塞队列
threadFactory:
handler:拒绝策略,四种
拒绝策略名称 说明
DiscardOldestPolicy 排在最前面最老的的丢弃
CallerRunsPolicy 你行你来做,谁往线程池提交任务谁来做
DiscardPolicy 最新提交的任务直接丢弃
AbortPolicy 抛出异常,默认策略
流程:
核心线程==》阻塞队列==》最大线程数==》拒绝策略
提交任务
submit
关闭线程池
shutdown:中断未在执行的线程
shutdownNow:尝试关闭所有线程,但不一定会成功
【Java 线程与进程(1)(基础知识及用法(问答形式))】11.3 合理配置线程池
任务特性
CPU密集型:CPU在不断计算的——配置线程数不能超过CPU核心数
Runtime.getRuntime().availableProcessors()
IO密集型:与网络进行通讯,有读写磁盘操作的——机器CPU核心数*2
混合型:
12 面试题汇总

    推荐阅读