黑马程序员-多线程

------- android培训java培训、期待与您交流! ----------

线程:进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间标示,线程负责的是应用程序的执行顺序

进程:正在进行中的程序。进程就是一个应用程序运行时的内存分配空间


一个进程至少有一个线程在运行。如果出现多个线程就称为多线程。每个线程在栈中都有自己的执行空间,自己的方法区,自己的变量

但是:多线程并不代表多个线程同时运行,而是因为CPU执行效率很高,看起来像是同时处理几个线程一样。实质还是一个一个的线程在执行


jvm在启动时,首先有一个主线程,负责程序的执行,调用的是main方法。所以主线程执行的代码都在main方法中。

当产生垃圾时,收垃圾的动作,由专门的一个线程去负责。这样可以提高效率


既然线程作为一个事物,那就自然会有关于线程的类,这个类为Thread

这个类中有一个run方法,专门设置线程内容,所以从java的角度考虑的话,线程就是一个类继承了Thread这个类复写了run方法。

所以任何对象都可以作线程,那么就解释了为什么有的线程方法在object类中,一个是wait,一个是notify,另一个是notifyAll

wait : 等待,将线程处于等待状态。

notify:唤醒,将指定的等待状态的线程唤醒

notifyAll:将所有等待的线程唤醒


Thread类中的主要方法

构造: Thread() :空参数构造

     Thread(Runnable r) : 这个是将Runnable类型的对象传入进来,后面会介绍

     Thread(String name) : 这个是创建了一个有名字的线程。

成员方法:

static Thread currentThread  返回这个thread类的对象引用

String getName :获取线程名称

int getProperty  获取优先级

void interrupt  中断线程

public boolean  interrupted 判断线程是否被中断

boolean  isAlive   判断线程是否是活动的

void  join  等待该线程终止 当线程终止后才能执行主线程

void run 线程所执行的内容

static void sleep(long miles) 使线程进入睡眠状态,过多少秒后继续执行

void  start   运行该线程

String toString  返回线程信息



其中有几个方法需要说明

线程的名称是通过构造或者setName来设置。如果没有设置的话,系统会自动以Thread-编号的形式来定义。编号从0开始

线程要运行的代码都写在run方法中

但是执行这个线程则需要start方法

start方法的作用: 启动线程,调用run方法

wait和sleep的区别

wait:可以指定时间也可以不指定,只能由对应的notify或者notifyAll来唤醒

线程一旦wait后将放弃执行权。释放锁

sleep:必须指定时间,时间到后自动变为运行状态(临时阻塞)

会释放执行权,但是不会释放锁

锁就是线程进行同步时的一个标示,同步时会讲到


创建线程的第一种方式:继承Thread 由子类复写run方法

一共有以下步骤:

1.定义类继承Thread类

2.复写run方法

3.创建子类对象

4.调用start方法


线程的流程解析

1.首先,线程由start方法来运行,调用run方法。

2.这时,因为cpu的它并非直接运行,而是先进入临时阻塞状态(线程具备cpu执行资格,但是没有执行权)。

3.当获取执行权后,运行该线程

4.如果使用了sleep或者wait,线程将进入冷冻状态

5.到了一定时间后,sleep执行完毕或者执行了notify语句,则将线程继续停留在临时阻塞状态。唯一不同是同步时,wait不会再持有锁,而sleep持有锁。

6.抢夺到了CPU执行权后,继续运行,运行完了之后,则run方法结束,线程结束


所以线程结束的 方法就是:run方法结束。

如果因为程序处于冷冻状态而无法结束的话,就使用interrupt方法,强制将其结束,因为是强制的,所以会抛出异常。


创建线程的第二种方式:实现runnable接口

1.定义类实现Runnable接口

2.覆盖接口中的run方法

3.通过Thread创建线程对象

4.将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数

5.调用Thread对象的start方法,开启线程,运行run方法


如果要创建线程对象,则必须new Thread或者new Thread的子类,所以这里将实现了runnable的接口传递。

为什么会有第二种形式:因为继承的局限性


如果当一个类复写了thread并且实现了runnable,那么执行的是哪个run方法呢?


new Thread(new Runnable()

{

public void run()

{

System.out.println("runnable run");

}

})

{

public void run()

{

System.out.println("subthread run");

}

}


如果出现这种情况,会优先于thread类中的run方法


当线程数量多时,一般会出现安全隐患

1.数据错误

造成原因:同时处理共享数据

解决方法:添加同步,添加一个锁,当此线程处理共享数据时,其他线程无法处理。可避免数据错误

2.死锁

因为这个线程需要A锁,进去后进入冻结状态,而另一个需要B锁,进去后也处于冻结状态,当冻结状态恢复时,进入了A的线程现在需要B锁,而进入B的线程现在需要A锁。这样互相等待对面释放锁而造成的阻塞叫做死锁。

class getLock

{

public static final Object LOCKA;

public static final Object LOCKB;

}

class DeadLock 

{

private boolean  flag ;

DeadLock(boolean flag)

{

this.flag = flag ;

}

if(flag)

{

synchronized(LOCKA)

{

System.out.println(true - LOCKA);

synchronized(LOCKB)

{

System.out.println(true - LOCKB);

}

}

}

else 

{

synchronized(LOCKB)

{

System.out.println(false - LOCKB);

synchronized(LOCKA)

{

System.out.println(false - LOCKA);

}

}

}

}

上述的假设创建两个线程A传入参数true   B传入参数false

A持有A锁打印了true-A后进入冻结状态,这时B抢到了CPU执行权,进入了B锁中这时因为A持有了A锁,B无法获取锁,所以处于阻塞状态,这时A又抢回了执行权

但是也没有B锁,所以两个线程就互相等待对方释放锁,就形成了死锁。


由此可看出,死锁其实就是同步嵌套了同步出现了问题


综上所述,当遇到了多线程安全问题时,先考虑两点

1.多个线程是否在操作共享数据

2.是否有多条语句对共享数据进行运算

如果有则添加同步。

但是同步也有安全问题:

锁是不是同一把锁。

如果不是,同步失败。

是否有两个线程以上,如果没有,不需同步。


同步代码块:synchronized{语句}  

同步函数: 函数上添加synchronized修饰符


各种同步使用的锁的类型

同步代码块:使用的锁是任意对象,最好取当前类的对象

同步函数:因为是对象调用函数,所以取得是this

静态函数:因为没有产生实例对象,所以使用的是类的字节码对象,*.class


线程间通信

思路:多线程使用同一个资源

这时,就将此资源定义成一个类,然后多线程依次定义成类。

Lock接口: 多线程在JDK1.5以后推出一个Lock接口,专门解决同步问题

替代了同步来解决多线程安全问题


Lock接口没有直接操作等待唤醒的方法,而是将这些方式又单独封装到一个对象中,这个对象就是condition。

将Object中三个方法进行单独封装,并提供了功能一致的方法:await  signal  signalAll


用法:创建一个锁对象,再创建两个线程操作锁的对象。

这样就可以实现同步了


### 黑马程序员多线程学习笔记与资料 在Java中,多线程是实现并发编程的重要工具。以下是一些关于黑马程序员多线程学习笔记和相关资料的内容总结[^1]。 #### 线程池的工作机制 线程池是一种用于管理和复用线程的机制,能够有效减少线程创建和销毁的开销,提高系统性能。当通过`submit`方法向线程池提交任务时,其工作流程如下: - 客户端每次提交一个任务,线程池会在核心线程池中创建一个工作线程执行这个任务。 - 如果核心线程池中的线程已满,则尝试将任务存储到工作队列中。 - 如果工作队列也满了,线程池会再次在非核心线程池区域创建新线程执行任务,直到当前线程池总线程数量达到最大值。 -线程池中的线程数量超过最大值时,多余的任务将按照指定的拒绝策略进行处理。 #### 创建线程的方式 在Java中,可以通过以下几种方式创建线程: 1. **继承Thread类**:通过重写`Thread`类的`run`方法实现线程逻辑,并调用`start`方法启动线程。 2. **实现Runnable接口**:定义一个实现了`Runnable`接口的类,并在`run`方法中编写线程逻辑,然后将其传递给`Thread`类的构造函数。 3. **使用Callable和FutureTask**:`Callable`接口类似于`Runnable`,但可以返回结果并抛出异常。结合`FutureTask`可以实现更复杂的线程功能。 4. **使用线程池**:通过`ExecutorService`接口提供的线程池管理功能,简化线程的创建和管理过程。 以下是使用线程池的一个简单示例: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池 for (int i = 0; i < 10; i++) { Runnable task = () -> { System.out.println("Task executed by: " + Thread.currentThread().getName()); }; executorService.submit(task); // 提交任务到线程池 } executorService.shutdown(); // 关闭线程池 } } ``` #### 多线程同步与锁 在多线程环境中,多个线程可能同时访问共享资源,导致数据不一致的问题。为了解决这个问题,可以使用同步机制: - **synchronized关键字**:用于修饰方法或代码块,确保同一时间只有一个线程可以访问该方法或代码块。 - **Lock接口**:提供了比`synchronized`更灵活的锁机制,例如可重入锁、读写锁等。 #### 常见问题与解决方案 在多线程编程中,可能会遇到死锁、线程安全等问题。解决这些问题的关键在于合理设计线程间的协作机制,避免竞争条件的发生。 --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值