线程
Thread类在 API文档中的描述
线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
每个线程都可以或不可以标记为一个守护程序。
当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,
并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。
Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,
还是通过抛出一个传播到 run 方法之外的异常。
线程调度模型
分时调度模型
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调用模型
优先让优先级高的线程使用CPU,如果线程的优先级相同,会随机选择一个
优先级高的线程获取的CPU时间片相对多一些
java使用的是抢占式调用模型
创建线程的方法
方法一 继承 Thread
继承 Thread 重写run()方法
public class ThreadDemo {
public static void main(String args[]) {
//方便演示直接new ,可以创建类 继承自Thread
Thread thread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":"+i);
}
}
};
thread.setName("线程1");
thread.start();
//Thread.currentThread() 可以拿到当前所在线程
System.out.println("main方法所在线程名字是 :"+Thread.currentThread().getName());
}
}

方法二,构造传入Runnable
可以避免单继承带来的局限性
适合多个相同程序的代码去处理同一个资源
//Thread 是实现了 Runnable接口
//Thread 带参构造 Runnable是接口,且只有一个run()方法
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
thread.start();
这样看,可能看不出区别,继续看
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}

线程的优先级
线程优先级高仅仅表示线程获取的CPU时间片的几率高
因为存在随机性,所以并不是优先级高,就一定先执行
默认优先级是5,可以更改优先级,超过范围(1-10)会抛出异常
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
//获取线程优先级
thread1.getPriority();
//设置线程优先级
thread2.setPriority(8);
线程休眠,线程加入,线程礼让
有部分方法是静态方法,实际使用可能与下面示例不一致
try {
//线程休眠1秒
thread1.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
//线程加入 等待该线程终止,才执行其他线程
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程礼让 暂停当前正在执行的线程对象,并执行其他线程
thread1.yield();
守护线程
将该线程标记为守护线程或用户线程
当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用
有个例子,坦克大战,如果老窝被攻击,那就都死了
如果坦克都死了,老窝可以还活着
//设置线程为守护线程
thread1.setDaemon(true);
//设置线程为守护线程
thread2.setDaemon(true);
thread1.start();
thread2.start();
//判断线程是否为守护线程
thread1.isDaemon();
thread2.isDaemon();
线程中断
stop()方法已经过时,因为具有不安全性
interrupt 中断线程,把线程设置为终止状态,线程在调用其他方法时抛出异常 InterruptedException
通过抛出异常来中断线程
//中断线程
thread1.interrupt();
thread2.interrupt();
线程的生命周期
新建 创建线程对象
就绪 有执行资格,资源准备好,但没有执行权
运行 拿到执行权
阻塞:由于一些操作,让线程处于该状态,没有执行资格,没有执行权
而由于另一些操作却可以把它激活,激活后处于就绪状态
死亡 线程对象变成垃圾,等待被回收

线程安全问题
哪些原因会导致线程安全问题
- 是否多线程环境
- 是否有共享资源
- 是否有多条语句操作共享数据
可以通过同步代码块,同步方法解决,但会降低运行效率
同步代码块的锁可以是--任意对象
同步方法的锁是--this,静态同步方法的锁是--class
//同步代码块
synchronized (new Object()){
...
}
集合想要线程安全,可以直接通过集合工具类的Collections的方法

JDK5之后可以用Lock锁
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,此实现允许更灵活的结构
java.util.concurrent.locks 接口Lock
所有已知实现类:
ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
//锁定和取消锁定出现在不同作用范围中时,
//必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
等待唤醒机制
Object类中的方法:
wait(): 唤醒在此对象监视器上等待的单个线程
notify(): |在其他线程调用此对象的 notify()
方法或 notifyAll()
方法前,导致当前线程等待。 |
为啥是在object类中,因为锁可以是任意对象
线程状态转换图

线程组 ThreadGroup
线程组表示一个线程的集合
线程默认情况下属于main线程组,和main线程都属于同一个组
public ThreadGroup(String name)
//获取线程所在的线程组
thread1.getThreadGroup();
第三种方式实现线程 Callable
public interface Callable<V>返回结果并且可能抛出异常的任务。
实现者定义了一个不带任何参数的叫做 call 的方法。
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。
配合线程池,Callable接口,创建带返回值的线程
线程常见的面试题
1:多线程有几种实现方案,分别是哪几种?
继承Thread类
实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。
2:同步有几种方式,分别是什么?
同步代码块
同步方法
3:启动一个线程是run()还是start()?它们的区别?
start();
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法
4:sleep()和wait()方法的区别
sleep():必须指时间;不释放锁。
wait():可以不指定时间,也可以指定时间;释放锁。
5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。
同步异步,并行串行
- 同步:同步很好理解,就是一段代码执行是按照顺序执行的
上一行代码有返回结果才会执行下一行
int i = 0;
int k = functionA(i); // 同步执行 是在functionA返回结果后才执行下面的functionB方法
functionB();
-
异步:在发起异步调用后不会马上得到结果,就执行下面一行代码
-
串行:简单理解为单个线程执行同一时间执行单个任务,好比单行道
-
并行:多CPU,同一时间可以执行多个任务,多行道
举例:下载一张图片耗时2秒, 如果要下载十张图片,串行的话就是一张图片一张图片的下载,
总共耗时20秒,而并行的话,如果2个线程并行,那么相当于一次同时下载两张图片,时间减半只需要10秒
并发并行
举例:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』
链接:https://www.zhihu.com/question/33515481/answer/58849148
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在现实机器中:
单核的机器,都是并发 concurrence 执行的。
多核的机器,都是并行 parallel 中嵌套着并发 concurrence 运行的。
如果存在无限核心的机器,则所有任务都可以是并行运行的。
一个4核心的机器,同时运行4个长时间Thread,每个核心跑一个,【对于进程来说】,这就是并行运行。
一个4核心的机器,运行5个Thread,有一个核心跑了2个线程,他们在轮替要CPU执行上下文切换。 其余核心都是跑一个线程,【对于这个进程来说】,就是并行 parallel 中夹杂并发concurrence。
一个单核心的机器,无论运行多少个线程,都是concurrence,即使用了.NET中的TPL,也是concurrence。

并发:两个队列交替使用一台咖啡机
并行:两个队列同时使用两台咖啡机互不干扰
串行:一个队列使用一台咖啡机
并行和并发都可以是多个线程,就看这些线程能不能同时被多个CPU执行,
如果能,就是并行,如果不能就是并发,多个线程被一个CPU轮流执行
网友评论