在操作系统中,每运行一个程序就是一个进程,一个进程可以有多个线程
进程与线程
进程与多进程
当我们编写完一个项目时,它们只是一堆静态的代码,但当我们他项目运行起来工作时,它会在操作系统中创建出一个进程,也就是每个正在运行的程序就是一个进程。它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。
操作系统并不是同一时间只能运行一个软件,而当你同时打开多个软件时就是多进程。多进程操作系统能同时达运行多个程序,由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行速度非常快,使得所有程序好像是在同时运行一样。
线程与多线程
线程比进程小一个执行单位,进程是相对于操作系统,而线程只相对于一个独立的进程。
由于CPU是以时间片的方式为进程分配CUP处理时间的,当一个进程以同步的方式去完成几件事情时,此进程必须完成了第一件事情以后再做第二件事,如此按顺序地向CPU请求完成要做的事情。在此单线程的工作模式下,如果把CUP看作是一共有100个时间片的话,CPU可能一直都只是花了其中的10个时间片来处理当前进程所要做的事情,只是用到了CPU的10%的时间片,而其他时间都白白浪费了,当然,实际上CPU的工作模式还是做完一件事以后再去做另一件事,只是CUP的处理速度非常快,很快就处理完成所请求的情事。
为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而互不干扰,如当前进程要完成三件事情,那么CPU会分别用10%的时间来同时处理这3件事情,从而让CPU的使用率达到了30%,大大地提高了CPU的利用率。多线程的好处在处理一些特殊的场合其优势尤其明显。比如下载文件,你要一边下载一边显示进度一边保存,在这种情况下,如果没有用多线程的话,没有意外的话一般都会把主线程阻塞,比如进度条的进度根本没有随着已下载的量而变化,堪至是整个窗体都动不了,用多线程就可以很好地解决这个问题。
多线程的好处
在程序中使用多线程有三大好处
- 更好的资源利用
- 在某些场景下程序的设计会更简单
- 提升程序的响应性
java中的多线程
其实java项目运行的时候并不是单线程运行,而是至少有两个以上线程,最基本的两线程就是主线程main和垃圾回收线程gc
但java程序不仅仅需要这两线程,以jdk1.8为例,下面是展示线程信息的代码
package top.coor.threaddemo.demo;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-25 18:28
**/
public class ThreadShow {
public static void main(String[] args) {
//构建 ThreadMXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
//获取所有存活的线程信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
//打印线程的id和名字
System.out.println(threadInfo.getThreadId()+" "+threadInfo.getThreadName());
}
}
}
执行结果
6 Monitor Ctrl-Break
5 Attach Listener
4 Signal Dispatcher
3 Finalizer
2 Reference Handler
1 main
根据结果显示,我启动这个项目启动了6个线程,不同jdk版本会有少许差异
当我们不使用多线程编写项目时,所有我们写的代码只在mian线程中执行,但我们可以使用多线程技术改变这一点
实现方式
在java中,提供了3中实现方式
1.继承Thread类
- 子类继承Thread类可具备多线程能力
- 启动线程方式:子类对象.start()
- 不建议使用,避免java单继承的局限性,导致使用线程后不能继承其他类
2.实现Runnable接口
- 实现Runnable接口可具备多线程能力
- 启动线程方式:传入目标对象+Thread对象.start() 或 创建线程池
- 推荐使用,避免java单继承的局限性,灵活方便,方便同一个对象被多个线程使用
3.实现Callable<T>接口
- 实现Callable接口可具备多线程能力
- 启动线程方式:传入目标对象+Thread对象.start()
- 具有返回值,在需要获取返回值的情况下使用
实现代码
Thread类
package top.coor.threaddemo.demo.t1;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-25 19:05
**/
public class ThreadTest {
public static void main(String[] args) {
//实例化拥有多线程能力的类
Demo demo1 = new Demo("a");
Demo demo2 = new Demo("b");
//启动多条线程
demo1.start();
demo2.start();
}
}
class Demo extends Thread {
//用于打印识别哪条线程在执行
private String name;
private int count = 0;
public Demo() {
}
public Demo(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程名:" + name + "正在执行-> " + count++);
}
}
}
Runnable接口
package top.coor.threaddemo.demo.t2;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-25 19:29
**/
public class RunnableTest {
public static void main(String[] args) {
Demo demo = new Demo();
//启动多条线程 可指定线程名称
new Thread(demo, "a").start();
new Thread(demo, "b").start();
new Thread(demo, "c").start();
}
}
class Demo implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//Thread.currentThread().getName() 可获取线程名称
System.out.println("线程名:" + Thread.currentThread().getName() + "正在执行-> " + count++);
}
}
}
Callable接口
传入目标对象+Thread对象.start() 方式
package top.coor.threaddemo.demo.t3;
import java.security.SecureRandom;
import java.util.concurrent.*;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-25 19:41
**/
public class CallableTest {
public static void main(String[] args) {
Demo demo = new Demo();
//模拟启动10个线程
for (int i = 0; i < 10; i++) {
FutureTask futureTask = new FutureTask(demo);
new Thread(futureTask, "ID" + i).start();
try {
//获取子线程的返回值
System.out.println("子线程获取的返回值:" + futureTask.get() + "\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class Demo implements Callable<String> {
private Integer i = 0;
private String str = "我是返回的字符串";
@Override
public String call() throws Exception {
System.out.println("线程类:" + Thread.currentThread().getName() + "正在执行-> " + i++ + "(来自线程类打印)");
return Thread.currentThread().getName() + "正在执行-> " + str + "(来自返回值)";
}
}
创建线程池方式
package top.coor.threaddemo.demo.t5;
import java.util.concurrent.*;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-26 15:16
**/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo demo1 = new Demo("a");
Demo demo2 = new Demo("b");
Demo demo3 = new Demo("c");
//创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交执行
Future<String> submit1 = executorService.submit(demo1);
Future<String> submit2 = executorService.submit(demo2);
Future<String> submit3 = executorService.submit(demo3);
//获取结果
String r1 = submit1.get();
String r2 = submit2.get();
String r3 = submit3.get();
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
//关闭服务
executorService.shutdownNow();
}
}
class Demo implements Callable<String> {
//将被返回的字符串
private String str;
public Demo(String str) {
this.str = str;
}
@Override
public String call() throws Exception {
System.out.println("线程类:" + Thread.currentThread().getName() + "正在执行-> " + str + "(来自线程类打印)");
return Thread.currentThread().getName() + "返回结果-> " + str + "(来自返回值)";
}
}
synchronized
以上几种代码实现都未使用安全锁,这将导致这些启动的线程将会出现同时操作同一资源的现象,java中称之为线程不安全
synchronized关键字是java中定义的一种互斥锁,当一个线程运行到使用了synchronized关键字的代码段时,首先检查当前资源是否已经被其他线程所占用,如果已经被占用,那么该线程则阻塞在这里,直到拥有资源的线程释放锁,其他线程才可以继续申请资源。
代码实现
package top.coor.threaddemo.demo.t4;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-25 20:36
**/
public class RunnableTest {
public static void main(String[] args) {
Demo demo = new Demo();
//启动多条线程 可指定线程名称
new Thread(demo, "a").start();
new Thread(demo, "b").start();
new Thread(demo, "c").start();
}
}
class Demo implements Runnable {
//用于代表被操作的资源
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//只需要在操作资源的地方加上锁,就可以解决资源被同时操作的安全问题
synchronized (this) {
System.out.println("线程名:" + Thread.currentThread().getName() + "正在执行-> " + count++);
}
}
}
}
synchronized关键字不仅能修饰代码块,还能修饰方法
public synchronized void fun() {}
修饰代码块时,传入被修饰的对象
synchronized (Object object) {}
Lock
从jdk1.5开始,Java提供了更加强大的线程同步机制,通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
代码实现
package top.coor.threaddemo.demo.t6;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: thread-demo
* @description:
* @author: coortop
* @create: 2020-06-27 14:06
**/
public class LockTest {
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo, "a").start();
new Thread(demo, "b").start();
new Thread(demo, "c").start();
}
}
class Demo implements Runnable {
private int num = 10;
//定义Lock锁
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加锁
reentrantLock.lock();
try {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + " " + num--);
} else {
break;
}
} finally {
//解锁
reentrantLock.unlock();
}
}
}
}
一般形式为
//创建锁
Lock lock = ...;
//加锁
lock.lock();
try{
//处理任务
}catch(Exception e){
//处理异常
}finally{
//释放锁
lock.unlock();
}
synchronized与Lock的对比
- Lock是显式锁(手动开启和关闭),synchronized是隐式锁,出了作用域就自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并具有更好的扩展性(提供更多的子类)
- 优先使用顺序:Lock -> 同步代码块(已经进入方法体,分配了相应资源) -> 同步方法(在方法体之外)
Over,enjoy it~