从操作系统的角度来看,线程是CPU分配的最小单位。
就好像我们去食堂打饭,并行就是我们在多个窗口排队,几个阿姨同时打菜;并发就是我们挤在一个窗口,阿姨给这个打一勺,又手忙脚乱地给那个打一勺。
要说线程,必须得先说说进程。
操作系统在分配资源时是把资源分配给进程的, 但是 CPU 资源比较特殊,它是被分配到线程的,因为真正要占用CPU运行的是线程,所以也说线程是 CPU分配的基本单位。
比如在Java中,当我们启动 main 函数其实就启动了一个JVM进程,而 main 函数在的线程就是这个进程中的一个线程,也称主线程。
一个进程中有多个线程,多个线程共用进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈
package com.kfm.it.Paraller;
public class MyThread {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
MyThread1 myThread1 = new MyThread1();
myThread1.setName("子线程");
myThread1.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"运行到第"+i+"次");
}
}
}
class MyThread1 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"运行到第"+i+"次");
}
}
}
package com.kfm.it.Paraller;
public class MyRunnable {
public static void main(String[] args) {
Thread.currentThread().setName("主线程");
Thread thread = new Thread(new MyRunnable1());
thread.setName("子线程");
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"运行到第"+i+"次");
}
}
}
class MyRunnable1 implements Runnable{
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"运行到第"+i+"次");
}
}
}
public class CallerTask implements Callable {
public String call() throws Exception {
return "Hello,i am running!";
}
public static void main(String[] args) {
//创建异步任务
FutureTask task=new FutureTask(new CallerTask());
//启动线程
new Thread(task).start();
try {
//等待执行完成,并获取返回结果
String result=task.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程等待与通知
在Object类中有一些函数可以用于线程的等待与通知。
wait():当一个线程A调用一个共享变量的 wait()方法时, 线程A会被阻塞挂起, 发生下面几种情况才会返回 :
(1) 线程A调用了共享对象 notify()或者 notifyAll()方法;
(2)其他线程调用了线程A的 interrupt() 方法,线程A抛出InterruptedException异常返回。
wait(long timeout) :这个方法相比 wait() 方法多了一个超时参数,它的不同之处在于,如果线程A调用共享对象的wait(long timeout)方法后,没有在指定的 timeout ms时间内被其它线程唤醒,那么这个方法还是会因为超时而返回。
wait(long timeout, int nanos),其内部调用的是 wait(long timout)函数。
上面是线程等待的方法,而唤醒线程主要是下面两个方法:
Thread类也提供了一个方法用于等待的方法:
join():如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才
从thread.join()返回。
线程休眠
让出优先权
线程中断
Java 中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。
Java内存模型定义了程序中各种变量的访问规则:
编译器会对原始的程序进行指令重排序和优化。但不管怎么重排序,其结果都必须和用户原始程序输出的预定结果保持一致。
一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行,这就是原子性操作。
可见性指当一个线程修改了共享变量时,其他线程能够立即得知修改。volatile、synchronized、final 关键字都能保证可见性。
虽然多线程存在并发和指令优化等操作,但在本线程内观察该线程的所有执行操作是有序的
Java 对象底层都会关联一个 monitor,使用 synchronized 时 JVM 会根据使用环境找到对象的 monitor,根据 monitor 的状态进行加解锁的判断。如果成功加锁就成为该 monitor 的唯一持有者,monitor 在被释放前不能再被其他线程获取。
synchronized在JVM编译后会产生monitorenter 和 monitorexit 这两个字节码指令,获取和释放 monitor。这两个字节码指令都需要一个引用类型的参数指明要锁定和解锁的对象,对于同步普通方法,锁是当前实例对象;对于静态同步方法,锁是当前类的 Class 对象;对于同步方法块,锁是 synchronized 括号里的对象。
执行 monitorenter 指令时,首先尝试获取对象锁。如果这个对象没有被锁定,或当前线程已经持有锁,就把锁的计数器加 1,执行 monitorexit 指令时会将锁计数器减 1。一旦计数器为 0 锁随即就被释放。
6、什么是线程池?
线程池: 简单理解,它就是一个管理线程的池子。
线程池参数:
拒绝策略有以下几种:
阻塞队列是生产者消费者的实现具体组件之一。当阻塞队列为空时,从队列中获取元素的操作将会被阻塞,当阻塞队列满了,往队列添加元素的操作将会被阻塞。具体实现有:
ThreadLocal 是线程共享变量。ThreadLoacl 有一个静态内部类 ThreadLocalMap,其 Key 是 ThreadLocal 对象,值是 Entry 对象,ThreadLocalMap是每个线程私有的。
存在的问题:对于线程池,由于线程池会重用 Thread 对象,因此与 Thread 绑定的 ThreadLocal 也会被重用,造成一系列问题。
比如说内存泄漏。由于 ThreadLocal 是弱引用,但 Entry 的 value 是强引用,因此当 ThreadLocal 被垃圾回收后,value 依旧不会被释放,产生内存泄漏。
JDK7采用锁分段技术。首先将数据分成 Segment 数据段,然后给每一个数据段配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。
get 除读到空值不需要加锁。该方法先经过一次再散列,再用这个散列值通过散列运算定位到 Segment,最后通过散列算法定位到元素。put 须加锁,首先定位到 Segment,然后进行插入操作,第一步判断是否需要对 Segment 里的 HashEntry 数组进行扩容,第二步定位添加元素的位置,然后将其放入数组。
JDK8的改进