一、多线程
使用多线程可以让程序充分利用CPU的资源,提高CPU的使用效率,从而解决高并发所带来的负载均衡问题。
优点:
- 资源得到更合理的利用
- 程序设计更加简洁
- 程序响应速度更快,运行效率更高
缺点:
- 需要更多的内存空间来支持多线程
- 多线程并发访问的情况可能会影响数据的准确性
- 数据被多线程共享,可能会出现死锁的情况
应该将程序设计更加合理有效,避免多线程的缺点,充分发挥多线程的优点,从而提高程序的性能。
1. 进程和线程
什么是进程?
进程就是计算机正在运行的一个独立的应用程序,进程是一个动态的概念。
什么是线程?
线程是组成进程的基本单位,一个进程种包含一个或多个线程,线程可以完成特定的功能。
进程和线程都是应用程序在执行过程种的概念,如果应用程序没有执行,就不存在进程和线程。
应用程序是静态的概念,进程和线程是动态的,有创建就有销毁,存在是暂时的。
2. 进程和线程的区别?
进程在运行时有独立的内存空间,每个进程占用的内存都是独立的,互不干扰。
多个线程是共享内存空间的,但每个线程的执行是相对独立的,只不过共用内存空间。
线程必须依赖于进程才能执行,单独的线程是无法执行的,由进程来控制多个线程的执行。
多线程:一个进程中,多个线程同时执行。
单核CPU的情况下,多线程并不是真正的同时执行,而是多个线程交替占用CPU,执行自己的业务。
因为程序执行速度太快,看起来是在同时执行。
java
public class ThreadTest {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("++++++" + i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i + "========");
}
}).start();
}
}
多核CPU的情况下,线程才是真正地同时执行。
3.Java中线程的使用
3.1 继承Thread类
- Thread类是Java提供的线程的父类
- 实现程序的扩展,基于JDK基础类开发者可以扩展出其他的相关类。
- 对修改封闭(不能改源码),对扩展开放(自定义类,通过继承的形式融入到JDK中)
- 线程一定跟任务绑定,一个线程必须要执行一个任务,一个空的线程没有意义,一个空的任务也没有意义,一定是线程+任务绑定在一起。
Thread类是JDK线程父类,Runnable接口时JDK定义任务的接口,任务的具体实现写在run方法中
- 创建Thread对象的时候,从外部传入一个Runnable对象(创建线程的时候,需要绑定一个任务)
- 当线程执行的时候,会调用线程的run方法
- 如果创建线程对象的时候,传入了任务,则会执行任务,否则线程什么都不做。
使用线程
1.创建线程对象
2.调用线程对象的start方法来启动线程
线程首先需要去争夺CPU资源,当拿到资源之后才能执行任务
run方法和start方法的区别?
main作为程序入口,本质上也是一个线程。
使用run方法时,定义的线程对象会被当成普通对象进行处理,main线程使用CPU进行执行。此时只有一个线程:mian
使用start方法时,定义的线程对象就是一个线程,并且使用CPU进行执行,此时有两个线程:main和thread
java
package com.thread;
public class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println(i+"=========");
}
}
}
java
package com.thread;
public class Test {
public static void main(String[] args) {
//创建线程对象
MyThread thread = new MyThread();
//启动线程
thread.start();
}
}
3.2 实现Runnable接口
第一种形式线程和任务的耦合度过高,不利于程序的扩展,所以开发中不推荐使用
实现解耦合
java
package com.thread;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i+"+++++++++++++");
}
}
}
java
package com.thread;
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
把任务和线程组装起来。
二、线程的状态
1.5种状态
5种状态,在特定的情况下,线程可以在不同的状态之间切换
- 创建状态:实例化了一个新的线程对象,还未启动;
- 就绪状态:创建好的线程对象调用了start()方法完成启动,进入线程池等待抢占CPU资源;
- 运行状态:线程对象获取了CPU资源,在规定的时间内执行任务
- 阻塞状态:正在运行的线程暂停执行任务,释放所占用的CPU资源
- 终止状态:线程运行完毕或因为异常导致线程终止运行
2.lambda表达式
函数式编程,将方法的实现作为参数进行传递,可以简化代码的开发
1、通过实现Runnable接口的形式来完成线程的使用
java
package com.thread;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i+"+++++++++++++");
}
}
}
java
package com.thread;
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
2.使用内部类对代码进行简化
java
package com.thread;
public class Test {
public static void main(String[] args) {
Runnable1 myRunnable = new Runnable1();
Thread thread = new Thread(myRunnable);
thread.start();
}
static class Runnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i+"+++++++++++++");
}
}
}
}
3.使用匿名内部类进行简化
java
package com.thread;
public class Test {
public static void main(String[] args) {
Runnable myRunnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i + "+++++++++++++");
}
}
};
Thread thread = new Thread(myRunnable);
thread.start();
}
}
new后面的就是类名。此时Runnable表示实现的接口
4.使用lambda表达式
Thread thread = new Thread(()->{ });
java
package com.thread;
public class Test {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println(i+"+++++++++++++");
}
}).start();
}
}
将接口作为参数传递的场景下,可以使用lambda进行简化
java
package com.thread;
public interface MyRunnable1 {
public void test();
}
java
package com.thread;
public class MyTest {
public void test(MyRunnable1 myRunnable1){
myRunnable1.test();
}
}
java
package com.thread;
public class Test1 {
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.test(()->{
System.out.println(111);
});
}
}
接口中只能有一个方法,多个方法会报错。因为无法判断具体指向的方法。
三、线程休眠
休眠是指让当前线程暂停执行,从运行状态进入阻塞状态,从而将CPU资源让给其他线程的一种调度方式,通过sleep方法来实现。
java
public static native void sleep(long millis) throws InterruptedException;
java
package com.thread;
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 100; i++) {
if (i == 5){
try {
Thread.sleep(1000);//1秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(i+"+++++++++++++");
}
});
thread.start();
}
}
lambda表达式只能调用静态方法。
1.多线程的场景中,调用sleep是让哪个休眠?
不是谁调用sleep就让谁休眠,而是看在哪个线程中调用sleep方法就让哪个线程休眠,和调用者无关。
java
package com.thread;
public class Test2 {
public static void main(String[] args) {
//子线程
Thread thread = new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println(i + "===================");
}
});
thread.start();
try {
thread.sleep(2000); //子线程调用,但是代码块在main方法中,休眠的是主线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//主线程
for (int i = 0; i < 100; i++) {
System.out.println("++++++++++++++++++++++++++++++mian" + i);
}
}
}
判断方法在哪里面,谁休眠。
四、线程合并
合并是指将指定的某个线程加入到当前线程种,合并为一个线程。
由两个线程交替执行变成一个线程中的两个子线程顺序执行,一个线程执行完毕后再来执行第二个线程,通过join方法来实现合并。
谁调用join方法,谁被合并,谁先执行
java
package com.thread;
public class JoinRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(i+ "-----------------JoinRunnable");
}
}
}
java
package com.thread;
public class Test2 {
public static void main(String[] args) {
JoinRunnable joinRunnable = new JoinRunnable();
Thread thread = new Thread(joinRunnable);
thread.start();
for (int i = 0; i < 50; i++) {
if(i == 10){
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(i + "这是mian线程");
}
}
}
join()方法存在重载
join(long mills)
1.join()和join(long mills)的区别?
通过join进行合并,则合并的线程会一直占用CPU资源,知道自己的任务结束才会释放CPU,另外一个线程才能执行。
通过join(long mills)进行合并,则合并进来的线程会根据mills时间来占用CPU资源,时间到了之后无论线程是否执行完毕,都会释放CPU资源,回到两个线程争夺CPU资源的情况。