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):基础知识及用法(问答形式)_第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; // 定义标记 默认第一次为false

    public 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:尝试关闭所有线程,但不一定会成功

11.3 合理配置线程池
任务特性
CPU密集型:CPU在不断计算的——配置线程数不能超过CPU核心数
Runtime.getRuntime().availableProcessors()
IO密集型:与网络进行通讯,有读写磁盘操作的——机器CPU核心数*2
混合型:

12 面试题汇总

你可能感兴趣的:(Java基础知识及进阶)