多线程,也叫做并发编程 JUC
首先理解进程:通俗来讲运行中的程序就叫做进程。例如我们电脑中的APP、编写好的程序在未启动的时候是静态的,并未在运行。而一旦启动,就会开始运行,就可以称为是进程
进程的特征:
动态性:运行中的进程会动态的占有cpu以及内存资源等
独立性:进程与进程之间相互独立,不会产生相互影响
并发性:假设CPU是单核,那么在某一时刻CPU当中其实只有一个程序在运行,CPU会分时的切换为每个进程服务,只是切换的速度很快,给人的感觉就是很多进程在同时被执行,这就是并发性
并行是与并发相关的一个概念:四核CPU中同时有四个进程正在运行,这就被称为并行
线程:一个进程中会至少有一个顺序执行流,每个顺序执行流被称为线程,当有两个或以上的线程时就称为多线程。
理解如:WechatAPP:app运行时便会在内存中开辟空间占用CPU资源,而WeChat运行时不仅要支持发消息还要收消息、更新朋友圈等等,那么就牵扯到多线程,每个线程负责一个功能单元。所以多线程也是具有并发性的。
进程与线程:
- 一个进程可以有多个线程,但至少有一个线程。而一个线程只能在所属进程的内存中活动
- 资源分配给进程:进程中的所有线程共享资源
- CPU分享给线程:即真正在处理器中运行的是线程
- 线程在执行过程中需要协作同步,不同进程的线程间要利用消息通信的办法实现同步
线程创建三种方式概述:
注意事项:
start方法不能连续调用,启动线程会失败
不能直接调用run方法,此方法调用不会创建新线程,只是普通方法调用
创建步骤:
编写一个类,此类继承Thread类
重写run方法,此方法是子线程的主体
调用star方法启动子线程
class MyThread extends Thread{
//此时创建的叫做线程类 并不是一个线程!
//重写run方法
@Override
public void run() {
System.out.println("子线程名称="+Thread.currentThread().getName()+"子线程ID="+Thread.currentThread().getId());
}
}
public class Demo02 {
//此时的Demo02可以看作是一个进程,而main方法是主线程
public static void main(String[] args) {
System.out.println("主线程名称="+Thread.currentThread().getName()+"主线程ID="+Thread.currentThread().getId());
//创建线程对象:
Thread t = new MyThread();//多态写法
//此时t线程创建完毕 但是并未启动 只有主线程(main线程)在运行,但是子线程并未启动运行
t.start();
//此时子线程启动, 子线程与主线程将会由于线程并发性相互争夺CPU资源,所以如果数据较多时将会发现主线程与子线程的运行是无序的
//或者直接利用对象调用: new MyThread().start();
}
}
/*
start方法的底层:
调用start方法后会在底层执行group.add(this),也就是将当前线程放入争夺CPU资源的组当中去
可以理解为start底层是在CPU中注册此线程并登录运行(触发run方法执行)
由此(方式一)可以得出:
线程类继承Thread,进而无法继承其他类。此处也体现了Java的单继承特性
启动线程必须调用start方法 直接调用run方法则当作普通类处理,则还是只有主线程在运行
多线程是并发执行,在执行过程中各个线程会争夺cpu,进而导致程序执行的随机性
*/
创建步骤:
//创建一个类实现Runnable接口
class TestThread implements Runnable{
//重写run方法
@Override
public void run() {
Thread.currentThread().setName("子线程");
System.out.println(Thread.currentThread().getName());
}
}
public class Demo04 {
public static void main(String[] args) {
//创建实现类对象
TestThread testThread = new TestThread();
//创建线程类对象并将实现类对象注入线程类构造器
Thread thread = new Thread(testThread,"子线程1");
//启用子线程
thread.start();
//继续使用同一个实现类对象包装成其他线程
Thread thread1 = new Thread(testThread,"子线程2");
thread1.start();
//方式二的匿名内部类写法:
//匿名内部类写法
new Thread(new TestThread(){
@Override
public void run() {
System.out.println("创建线程方式二写法-->匿名内部类");
}
}).start();//创建并直接启动该线程
}
}
/*
由此(方式二)可以得出:
-- 实现类可以继续实现其他接口,也可以继承其他类,避免了单继承的局限性
-- 线程代码和线程相互独立 线程代码可以被多个线程共享 同一个实现类对象可以被包装成多个线程
-- 适合多个线程共享同一个资源
缺点:
-- 不能直接获取线程返回的结果{
1. 不能抛出异常 因为其所实现的接口并未抛出异常 只能自己处理
2. 没有返回值 当需要获取线程执行后的返回值时需要在外部定义变量 最后在线程中存储并获取
}
*/
创建步骤:
//创建Callable实现类
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
//返回值
return Thread.currentThread().getName()+"线程执行结果为="+sum;
}
}
public class Demo05 {
public static void main(String[] args) {
//创建Callable实现类对象
MyCallable myCallable = new MyCallable();
/**
* 为什么要创建FutureTask对象并注入Callable实现类对象?
* 打开FutureTask原码发现:
* public class FutureTask implements RunnableFuture
* Future实现了RunnableFuture接口
* 而RunnableFuture接口实现了Runnable接口
* public interface RunnableFuture extends Runnable, Future
* 综上:由于Callable实现类对象无法直接注入线程类构造器,所以使用了FutureTask类
* 最终将FutureTask对象包装成线程类对象
* FutureTask提供了get方法来获取Callable实现类重写call方法的结果
* 并且在线程执行完毕后可以获取线程的执行结果
*/
//创建FutureTask对象并注入Callable实现类对象
FutureTask<String> futureTask = new FutureTask<>(myCallable);
//将FutureTask对象包装成线程类对象
Thread thread = new Thread(futureTask);
//利用线程类对象启动线程
thread.start();
//此处利用futureTask获取call方法返回的结果
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
-------public Thread(){} //无参构造
-------public Thread(String name){} //注入线程名称
-------public Thread(Runnable target){} //注入Runnable实现类对象
-------public Thread(Runnable target,String name){} //注入Runnable实现类对象及线程名称
通俗点说就是通过此方法可以获取当前的线程对象,进而可以直接调用Thread的其他方法用法:
class MyThread1 extends Thread{
@Override
public void run() {
Thread.currentThread().getId();//获取子线程Id
Thread.currentThread().getName();//获取子线程Name
}
}
public class Demo03{
public static void main(String[] args) {
Thread.currentThread().setName("超级线程");
Thread.currentThread().getId();//获取主线程Id
Thread.currentThread().getName();//获取主线程Name 输出---> 超级线程
}
}
/*
Thread.currentThread().getName();放在哪个线程下就返回哪个线程的名称
*/
public class Demo03{
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);//每次输出一个数据后休眠一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread1 extends Thread{
//创建子类构造器
public MyThread1(String name) {
super(name);
}
//通过super将名字送上去到父类去接收
//父类的实现:private volatile String name;
@Override
public void run() {
}
}
public class Demo03{
public static void main(String[] args) {
Thread t1 = new MyThread1("子线程是也");
System.out.println(t1.getName());
}
}
当线程对象调用了 start()方法之后,该线程处于就绪状态, Java 虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM线程调度器的调度。
注意问题
public class InvokeRun extends Thread
{
private int i ;
// 重写run方法,run方法的方法体就是线程执行体
public void run()
{
for ( ; i < 100 ; i++ )
{
// 直接调用run方法时,Thread的this.getName返回的是该对象名字,
// 而不是当前线程的名字。
// 使用Thread.currentThread().getName()总是获取当前线程名字
System.out.println(Thread.currentThread().getName()
+ " " + i); // ①
}
}
public static void main(String[] args)
{
for (int i = 0; i < 100; i++)
{
// 调用Thread的currentThread方法获取当前线程
System.out.println(Thread.currentThread().getName()
+ " " + i);
if (i == 20)
{
// 直接调用线程对象的run方法,
// 系统会把线程对象当成普通对象,run方法当成普通方法,
// 所以下面两行代码并不会启动两条线程,而是依次执行两个run方法
new InvokeRun().run();
new InvokeRun().run();
}
}
}
}
在某一时刻某一个线程在运行一段代码的时候,这时候另一个线程也需要运行,但是在运行过程中的那个线程执行完成之前,另一个线程是无法获取到CPU执行权的(调用sleep方法是进入到睡眠暂停状态,但是CPU执行权并没有交出去,而调用wait方法则是将CPU执行权交给另一个线程),这个时候就会造成线程阻塞。
如何进入阻塞状态
如何解除阻塞状态
阻塞状态转换图

线程会以如下三种方式结束,结束后就处于死亡状态 。
不使用stop方法的原因
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。
举例
package com.gec.创建线程.stop用法;
class MyThread extends Thread
{
@Override
public void run() {
try {
System.out.println("my thread name="+Thread.currentThread().getName());
//抛出 ThreadDeatherror 的错误
this.stop();
} catch (ThreadDeath e) {
System.out.println("进入catch块了");
e.printStackTrace();
}
}
}
public class StopThreadMainTest {
public static void main(String[] args) {
MyThread t=new MyThread();
t.start();
}
}
使用退出标志退出线程
一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false 来控制 while循环是否退出
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}
}
}
使用退出标志结束线程
public class Demo01 {
//利用退出标志终止子线程
public static void main(String[] args) throws Exception {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
//主线程休眠退出抢夺cpu资源队列,资源提供给子线程执行
Thread.sleep(200);
myThread.setFlag(true);//子线程执行完毕后关闭线程
System.out.println("子线程已关闭");
}
}
class MyThread extends Thread{
private boolean flag = false;
public void run(){
while (!flag){
System.out.println("子线程执行体");
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}