JAVA多线程
基本概念
程序
程序就是你一行一行敲出的代码 , 它们在没有运行之前都是死的.
进程
当程序进行运行 , 原来死的代码就开始跑起来 , 从开始运行到全部结束 , 这就是一个进程
线程
代码执行每一条并列任务时 , 都是一个线程的运行 , 一个进程中存在一个或多个线程 ,
并行与并发
操作系统分为多核cpu和单核cpu , 当只有一个cpu运行代码中多个线程时 , 此时就是并发 ; 多核cpu一对一处理线程 , 此时就是并行.
多线程中cpu并发运行 , 形成时间切片 , 在不同线程之间调度 , 调度的顺序是随机的 , 这和线程自身是否满足运行条件也有关系
临界区与锁
程序中需要访问的公共资源(文件 , 全局变量)就是临界区 , 锁是防止多个线程同时访问临界区的一个限制 , 当一个线程访问临界区时,就会获得锁 , 其他线程只有等到当前线程释放锁才能进行访问
wait()与notify()方法
wait(): 程序主动进行调用 , 当一个线程的运行条件无法满足时会调用此方法进行等待 , 其他线程则继续竞争cpu
notify(): 程序主动进行调用 , 当一个线程运行时条件满足时 , 等待的线程就会开始执行
注意 : cpu调度线程是随机被动进行的 , 而wait()与notify()方法是程序主动进行调度 , 结合使用能加快运行效率
多线程方式
一个线程从建立到结束的过程:

创建线程的方式有三种:
- 继承Tread类
- 实现Runnable接口
- 通过Callable和Future创建线程
继承Thread类
Thread类封装了操作系统调用的复杂性 , 具有标准化的线程操作 .
当自定义类没有继承任何父类的时候 , 我们通过自定义类继承Thread父类 , 并调用父类的start()方法进入就绪状态
下面是创建子类的代码:
package 线程;
public class MythreadDemo extends Thread{
private String threadName;
public MythreadDemo(String name) //创建构造器
{
this.threadName = name;
System.out.println("Creating " + threadName);
}
@Override
public void run() //重写run方法
{
for(int i=1;i<=100;i++)
{
if(i%2==0)
{
System.out.println(threadName+":"+i);
}
}
}
}
在子类中创建私有属性和构造器声明线程的成功创建;
private String threadName;
public MythreadDemo(String name)//创建构造器
{
this.threadName = name;
System.out.println("Creating " + threadName);
}
进入执行状态 : 通过调用父类的Run()方法使线程进入执行状态 , 由于Tread父类的Run()方法是空的 , 所以我们需要重写方法 , 写入我们要执行的代码.
@Override//声明重写父类的方法
public void run()//重写run方法
{
for(int i=1;i<=100;i++)
{
if(i%2==0)
{
System.out.println(threadName+":"+i);
}
}
}
重写的注意点 :
(1)方法名必须相同
(2)参数列表必须相同
(3)返回值类型必须相同
下面是主线程中新建线程的代码:
package 线程;
public class ThreadExample {
public static void main(String[] args) {
MythreadDemo thread1= new MythreadDemo("线程1");
thread1.start();
MythreadDemo thread2=new MythreadDemo("线程2");
thread2.start();
for(int i=0;i<3;i++)
{
System.out.println("好的好的");
}
}
}
新建状态: 新建两个子类的实例对象(thread1和thread2)
MythreadDemo thread1 = new MythreadDemo("线程1");
MythreadDemo thread2 = new MythreadDemo("线程2");
就绪状态 :分别调用父类的start()方法 , 线程加入cpu的竞争
thread1.start();
thread2.start();
主线程执行的代码:
for(int i=0;i<3;i++)
{
System.out.println("好的好的");
}
实现 Runnable 接口
Runnable是Thread类实现的一个储存执行代码的接口 , 和继承Thread类不同的是 , 实现接口将执行操作和执行内容分隔开
下面是Thread类的源码:
public class Thread implements Runnable {
private Runnable target; // 传入的Runnable对象
// 构造器:接收Runnable任务
public Thread(Runnable target) {
this.target = target;
}
// Thread自己的run方法
@Override
public void run() {
if (target != null) {
target.run(); // 调用传入的Runnable对象的run方法
}
// 如果target为null,就什么也不做(需要子类重写)
}
}
Thread类通过构造器接收传入的对象.
private Runnable target; // 传入的Runnable对象
public Thread(Runnable target) {
this.target = target;
}
Thread类实现了Runnable接口 , 进而实现Run()方法
@Override
public void run() {
if (target != null) {
target.run();
}//初始执行内容为空
}
由于Thread类构造器只允许传入Runnable接口的实例对象 , 所以我们必须通过实现接口创建对象
下面是具体实现的代码:
package 线程;
public class MyRunnable implements Runnable{
private String RunnableName;
public MyRunnable(String name)
{
this.RunnableName=name;
System.out.println("Creating:"+RunnableName);
}
@Override
public void run()
{
for (int i = 1; i <= 100; i++) {
if (i % 2 == 1) {
System.out.println(RunnableName+":" + i);
}
}
}
}
创建具体类实现Runnable接口:
public class MyRunnable implements Runnable{
private String RunnableName;
public MyRunnable(String name)
{
this.RunnableName=name;
System.out.println("Creating:"+RunnableName);
}
在新建类中实现接口的Run()方法:
@Override
public void run()
{
for (int i = 1; i <= 100; i++) {
if (i % 2 == 1) {
System.out.println(RunnableName+":" + i);
}
}
创建具体类的实例对象:
MyRunnable Runnable1 = new MyRunnable("线程1");
MyRunnable Runnable2 = new MyRunnable("线程2");
将Runnable类型的实例对象传入Tread类:
Thread thread1 = new Thread(Runnable1);
Thread thread2 = new Thread(Runnable2);
再通过Tread类下的对象调用start()方法,创建线程:
thread1.start();
thread2.start();
注意:
- 不能使用具体类的对象直接调用run()方法 , 那样只是在主线程里同步执行两个任务 , 必须通过Thread类调用start方法才能成功创建线程 , 并异步执行两个任务
- run( )方法是由 Java 虚拟机和操作系统底层调用的,不是由你的应用程序代码直接调用的
通过 Callable 和 Future 创建线程
下面是实现Callable接口的子类代码:
package 线程;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String>{
public String Callname;
public MyCallable(String name){
this.Callname=name;
}
//实现call方法
public String call() throws Exception{//带有返回值供Futrue.get()方法调用
//线程具体的执行任务
for (int i = 1; i <= 100; i++) {
if (i % 2 == 1) {
System.out.println(Callname + ":" + i);
}
}
// 返回输出结果
String result="输出结果:"+Callname;
return result;
}
}
创建子类实现接口:
public class MyCallable implements Callable<String>
实现Callable接口中的call方法(带有返回值):
public String call() throws Exception{
return result;
}
注意:实现的call()方法可以抛出异常也可以得到返回值 (这也是运用callable接口创建线程的独特之处)
下面进行main方法创建线程:
package 线程;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) {
// 创建线程池
ExecutorService executor=Executors.newFixedThreadPool(2);
// 创建线程
MyCallable tast1=new MyCallable("线程1");
MyCallable tast2=new MyCallable("线程2");
// 将线程导入submit方法
Future <String> Future1=executor.submit(tast1);
Future <String> Future2=executor.submit(tast2);
// 获取返回值
try {
String result1=Future1.get();
System.out.println("线程1:"+result1);
String result2=Future2.get();
System.out.println("线程2:" + result2);
} catch (Exception e) {
System.out.println("执行发生错误");
}
// 主线程输出
System.out.println("okok");
// 关闭线程池资源
executor.shutdown();
}
}
创建固定线程容量的线程池(executors类中的方法全为静态方法):
ExecutorService executor=Executors.newFixedThreadPool(2);
创建MyCallable类对象实例(此处同理于继承Thread类 , 实现Thread接口创建对象):
MyCallable tast1=new MyCallable("线程1");
MyCallable tast2=new MyCallable("线程2");
进入就绪状态(类似于Thread.start()方法):
Future <String> Future1=executor.submit(tast1);
Future <String> Future2=executor.submit(tast2);
注意:
- start()方法在executor内部自动进行调用 , 我们只需手动调用submit传入我们的实例对象即可
- submit()方法的返回值为call()方法中的返回的值
- 运用泛型Futrue<String>接收返回值
用try-catch语句包装Futrue调用方法等操作:
try {
String result1=Future1.get();
System.out.println("线程1:"+result1);
String result2=Future2.get();
System.out.println("线程2:" + result2);
} catch (Exception e) {
System.out.println("执行发生错误");
}
注意:
- 可通过Futrue.get()方法获取call()方法中的返回值来判断执行任务的状态
- Futrue.get()会抛出受检异常 , 必须运用try-catch进行异常处理
main方法中进入就绪状态内部原理:
main线程提交任务:
executor.submit(task1)
↓
线程池把Callable包装成FutureTask
↓
线程池创建/复用Thread来执行FutureTask
↓
Thread执行FutureTask.run()方法
↓
FutureTask.run()内部调用: task1.call()
↓
call()方法执行,返回结果"XXX"
↓
FutureTask把结果保存起来: result = "XXX"
↓
main线程调用: future1.get()
↓
如果call()还没完成,main线程等待
↓
call()完成后,main线程拿到结果"XXX"
先把call()方法包装在run()方法中 , 再执行run()方法
底层原理还是运用Tread类进行创建线程和执行任务 ! ! !
线程异常处理(公共资源)
临界区(即公共资源)只能被一个线程访问,当多个线程同时访问公共资源时,就会导致资源无法及时更新,导致执行结果异常.
所有我们需要给临界区定义锁(Lock) , 当线程需要访问时 , 要先拿到锁 , 此时其他线程无法同时访问 , 访问完毕之后自动释放锁 (获取锁是各线程竞争的结果)
给临界区设置锁有三种方式:
- 使用synchronized修饰符定义方法(隐式锁)
- 等价的手动同步(使用this)
- 使用专门的锁对象
方式一:
通过创建实例类形成隐式锁:
// 方法1:使用synchronized修饰符
public synchronized void increment() {
count++;
}
下面是运用synchronized修饰符定义方法的代码:
package 线程;
//隐式锁实例类
class Counter{
//定义公共资源
public int x = 0;
//创建隐式锁方法(使用公共资源的核心代码)
public synchronized void count(){
x++;
}
//实时获取变量数值
public int getValue(){
return x;
}
}
//执行类
public class ShareResouce1 extends Thread{
//定义Counter类类型的变量
public Counter counter;
String Lockname;
//构造器:传入创建Counter实例对象和线程名
public ShareResouce1( Counter counter ,String name)
{
//对成员名字赋值
this.counter=counter;
this.Lockname=name;
}
//run方法书写执行内容
public void run(){
while(counter.getValue()<100)
{
//竞争隐式锁的关键代码(决定了执行和阻塞状态的变换)
counter.count();//调用Counter类的隐式锁方法
System.out.println(Lockname + ":" + counter.getValue());
}
}
}
运用synchronized修饰符创建隐式锁:
public synchronized void count(){
x++;
}
等同于:
public void method() {
// 自动获取this对象的钥匙
synchronized(this) {
x++;
}
// 自动归还钥匙
}
count包裹的内容为需要使用公共资源的代码块
实时返回变量x的数值:
public int getValue(){
return x;
}
定义Counter类的变量 , 用于存储主线程传入的实例对象:
public Counter counter;
String Lockname;
//构造器:传入创建Counter实例对象和线程名
public ShareResouce1( Counter counter ,String name)
{
//对成员名字赋值
this.counter=counter;
this.Lockname=name;
}
调用实例类中的隐式锁方法:
counter.count();//调用Counter类的隐式锁方法
System.out.println(Lockname + ":" + counter.getValue());
重点:是否竞争到锁是进入运行状态和阻塞状态的关键
新建(New) → 启动 → 就绪(Runnable)
↓
竞争synchronized锁
↓
成功 → 运行(Running) → 执行完毕 → 终止(Terminated)
↓
失败 → 阻塞(Blocked)
↓
锁释放 → 就绪(Runnable)
注意:只将需要访问临界区(即公共资源)的执行代码放入隐式锁的方法中 , 其余代码无需放入 , 从而避免传入传出的不便
下面是相应的main主线程代码:
package 线程;
public class ShareResouce1Example {
public static void main(String[] args) {
//创建Counter类的共享对象(只可创建一个实例对象,保证锁的唯一性)
Counter count1=new Counter();
//传入同一个共享对象
ShareResouce1 task1=new ShareResouce1(count1,"线程1");
ShareResouce1 task2=new ShareResouce1(count1,"线程2");
System.out.println("线程1开始执行");
task1.start();
System.out.println("线程2开始执行");
task2.start();
}
}
方法二:
使用This对象锁
this对象锁: 每一个创建的对象都有一个锁机制 , 指向该对象.
为了确保锁的唯一性 , 我们只能创建一个实例对象 , 但由于线程有多个 , 所以我们只能使用实现Runnable接口的方式创建线程.
下面是运用this锁实现公共资源锁的代码:
package 线程;
public class ShareResouce3 implements Runnable{
//公共资源共享
public static int x=0;
public String Sharename;
public ShareResouce3(String name){
this.Sharename=name;
}
public void run(){
while(x<100){
//运用this对象锁
synchronized (this) {
x++;
}
//输出创建Thread对象的线程名
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
重点:运用this对象锁时必须要注意只能创建一个实例对象(确保锁的唯一性)
main主线程:
package 线程;
public class ShareResouce3Example {
public static void main(String[] args) {
ShareResouce3 tast=new ShareResouce3("公共资源");
Thread tast1=new Thread(tast,"线程1");
System.out.println("线程1开始执行");
tast1.start();
Thread tast2 = new Thread(tast, "线程2");
System.out.println("线程2开始执行");
tast2.start();
}
}
只创建一个实例对象,而创建多个线程对象,从而保证this的唯一性和多线程的创建.
方式三:
创建专用Lock对象
下面是包装Lock锁和线程类的代码:
package 线程;
//封装类(放置公共资源变量和创建静态对象)
class makeLock
{
//定义公共资源静态变量
public static int x = 0;
public static final Object Lock=new Object();//公共锁对象(final保证锁唯一性)
}
public class ShareResouce2 extends Thread{
public String Myname;
public ShareResouce2(String name){
this.Myname=name;
}
public void run(){
while(makeLock.x<100)
{
//通过封装类调用静态锁
synchronized(makeLock.Lock){
makeLock.x++;
}
System.out.println(Myname+":"+makeLock.x);
}
}
}
创建类封装资源和唯一锁对象:
class makeLock
{
//定义公共资源静态变量
public static int x = 0;
public static final Object Lock=new Object();//公共锁对象(final保证锁唯一性)
}
在run方法中调用封装类的资源和锁对象:
public void run(){
while(makeLock.x<100)
{
//通过封装类调用静态锁
synchronized(makeLock.Lock){
makeLock.x++;
}
System.out.println(Myname+":"+makeLock.x);
}
注意:
- 无法在线程类中创建实例锁对象 , 会导致每个线程都会得到一个锁对象 , 无法处理访问异常
- 也不建议使用静态锁对象 , 创建资源管理类来放置公共资源和锁对象最稳妥.
线程通信(此板块敬请期待)
wait()方法
notify()方法
重要内容!!!
文章到这里就暂告一段落啦,真心希望能为你带来一些启发和帮助。❤️
你的每一次阅读,都是我坚持分享的最大动力 ✨。如果文中有任何不清楚、或者你觉得有误的地方,非常欢迎在评论区指出来 🤔,我们一起探讨,共同进步~
如果觉得内容不错,别忘了点个赞 👍 让我知道哦!
关注我 👉,不要错过系列后续的更多干货分享!期待在下一篇文章里,再次与你相遇。😊
更多推荐

所有评论(0)