进程与线程
进程是系统资源分配的最小单位,进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵,通常一个任务就是一个程序,而一个程序就是一个进程。
线程是CPU执行调度的最小单位(资源调度的最小单位),线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
为什么使用多线程
-
从计算机底层来说: 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
-
从当代互联网发展趋势来说: 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
多线程特点
- 线程共享相同的地址空间
- 线程是轻量级的
- 线程间通信的成本低
线程的生命周期(线程的状态)
1)New,新建状态
你创建了Thread类的对象,在调用startK()方法之前则线程处于新建状态
2)Runnable,可执行状态
线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待线程调度程序的调度
3)Running,执行状态
如果线程调度程序选择了该线程,cpu执行代码,则该线程处于运行状态。
4)Non-Runnable(Blocked),阻塞状态
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5)Dead,死亡状态
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程的创建
Thread类的几个常用构造函数
Thread()
Thread(String name)
Thread(Runnable r)
Thread(Runnable r,String name)
1)继承Thread类(任务和线程合并在一起了)
class T extends Thread {
@Override
public void run() {
log.info("我是继承Thread的任务");
}
}
2)实现Runnable接口(任务和线程分开了)
class R implements Runnable {
@Override
public void run() {
log.info("我是实现Runnable的任务");
}
}
3)实现Callable接口(利用FutureTask执行任务,有返回值)
class C implements Callable<String> {
@Override
public String call() throws Exception {
log.info("我是实现Callable的任务");
return "success";
}
线程的启动
// 启动继承Thread类的任务
new T().start();
// 启动实现Runnable接口的任务
new Thread(new R()).start();
// 启动实现了Callable接口的任务 结合FutureTask 可以获取线程执行的结果
FutureTask<String> target = new FutureTask<>(new C());
new Thread(target).start();
log.info(target.get());
多线程常用方法
1)线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台
//设置线程的优先级 public final void setPriority(int newPriority) //获取线程的优先级 public final int getPriority()
2)线程休眠,sleep()
Thread类的sleep()方法用于在指定的时间内休眠一个线程。
public static void sleep(long miliseconds)throws InterruptedException public static void sleep(long miliseconds, int nanos)throws InterruptedException
public class ToSleepThread implements Runnable {
private int ticketNums = 5;
@Override
public void run() {
while (true){
if(ticketNums <= 0){
break;
}
//模拟网络延迟
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " 拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args){
ToSleepThread sleepThread = new ToSleepThread();
new Thread(sleepThread,"jack").start();
new Thread(sleepThread,"lucy").start();
}
}
output:
jack 拿到了第5张票
lucy 拿到了第4张票
jack 拿到了第3张票
lucy 拿到了第3张票
lucy 拿到了第2张票
jack 拿到了第2张票
jack 拿到了第1张票
lucy 拿到了第1张票
public class ToSleepThread2 {
public static void main(String[] args){
turnDown();
// printCurrentTime();
}
//模拟倒计时
public static void turnDown(){
int num = 10;
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if(num <= 0){
break;
}
}
}
//输出当前时间
public static void printCurrentTime(){
while (true){
System.out.println(new Date().toString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3)线程并入,join()
join()是Thread类的一个方法, t.join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。如控制A, B, C线程的执行顺序,主线程等待子线程执行结束,都可以用join来实现。
public void join()throws InterruptedException public void join(long milliseconds)throws InterruptedException
//测试join方法,想象为插队
public class ToJoinThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程vip来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动我们的线程
ToJoinThread joinThread = new ToJoinThread();
Thread thread = new Thread(joinThread);
thread.start();
//主线程
for (int i = 0; i < 5; i++) {
if(i == 3){
try {
// 线程插队后,会先把插队的线程执行完成的
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("主线程运行 ----" + i);
}
}
}
output:
主线程运行 ----0
主线程运行 ----1
主线程运行 ----2
线程vip来了0
线程vip来了1
线程vip来了2
主线程运行 ----3
主线程运行 ----4
4)线程礼让,yield()
yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
public static void yield()
public class ToYieldThread {
public static void main(String[] args){
MyYield myYield = new MyYield();
new Thread(myYield,"线程一").start();
new Thread(myYield,"线程二").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName() + "线程停止");
}
}
守护线程
java中的守护线程是为用户线程提供服务的服务提供线程。它的生命取决于用户线程,即当所有用户线程死亡时,JVM自动终止这个线程.
java中有许多守护线程自动运行,例如gc,finalizer等
特点:
- 它为后台支持任务的用户线程提供服务。它在生活中除了服务于用户线程之外没有其他角色。
- 它的生命取决于用户线程。
- 它是一个低优先级线程
//设置当前线程为守护线程或用户线程 public void setDaemon(boolean status) //判断线程是否为守护线程 public boolean isDaemon()
//测试守护进程
//上帝守护你
public class DaemonTest {
public static void main(String[] args){
God god = new God();
Thread threadGod = new Thread(god);
//线程默认为用户线程,setDaemon 可以将它改为守护进程
threadGod.setDaemon(true);
//开启守护进程
threadGod.start();
Thread threadYou = new Thread(new You());
//开启用户进程
threadYou.start();
}
}
//上帝 守护进程
class God implements Runnable{
@Override
public void run() {
//此线程是死循环线程,但设置为守护线程后,当所有的用户线程都死亡时,它会被jvm自动终止。
while(true){
System.out.println("god is blessing you");
}
}
}
//你 , 用户进程
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("一直幸福地活着");
}
System.out.println("---------goodbye world");
}
}
tips: 如果想要设置一个用户线程为守护线程,一定要在该线程开启(start方法调用)前设置,否则会抛出IllegalThreadStateException异常。
线程池
Java线程池表示一组等待任务并多次重用的工作线程。对于线程池,可以创建一组固定大小的线程。从线程池中取出一个线程,并由服务提供者分配一个任务。任务完成后,线程再次包含在线程池中。简单来说,它就是一个管理线程的池子。
优点:
- 它帮我们管理线程,避免增加创建线程和销毁线程的资源损耗
- 提高响应速度。 如果任务到达了,从线程池拿线程,相对于重新去创建一条线程执行,速度更快。
- 重复利用。 线程用完,再放回池子,可以达到重复利用的效果,节省资源。
- 解耦作用。线程的创建于执行完全分开,方便维护。
class WorkerThread implements Runnable {
private String message;
public WorkerThread(String s){
this.message=s;
}
public void run() {
System.out.println(Thread.currentThread().getName()+" (Start) message = "+message);
processmessage();//call processmessage method that sleeps the thread for 2 seconds
}
private void processmessage() {
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);//creating a pool of 3 threads
for (int i = 0; i < 7; i++) {
Runnable worker = new WorkerThread("" + i);
executor.execute(worker);//calling execute method of ExecutorService
}
executor.shutdown();
while (!executor.isTerminated()) { }
System.out.println("Finished all threads");
}
}
output:
pool-1-thread-2 (Start) message = 1
pool-1-thread-1 (Start) message = 0
pool-1-thread-3 (Start) message = 2
<!--两秒后-->
pool-1-thread-2 (Start) message = 3
pool-1-thread-3 (Start) message = 4
pool-1-thread-1 (Start) message = 5
<!--两秒后-->
pool-1-thread-2 (Start) message = 6
<!--两秒后-->
Finished all threads