1.1 多线程介绍
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫”互斥锁”(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做”信号量”(Semaphore),用来保证多个线程不会互相冲突。不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
多线程:具有多个执行执行代码的路径.在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间.进行方法的压栈和弹栈,栈内存是线程私有的的
线程的状态
- new 新建状态
- runnable:正在在Java虚拟机中执行的线程处于这种状态
- blocked:受阻塞状态并等待某个监听器锁的线程处于这种状态
- waiting:等待,在Object类中,无限期第等待另一个线程来执行某一特定操作的线程处于这种状态
- timed waiting: 休眠(执行sleep方法之后)等待另一个线程来执行,取决于指定 等待时间的操作的线程处于这种状态
- terminated:死亡状态 run()结束 stop()过时
线程池
线程池的概念:
线程池,可以叫缓冲池,其实就是一个容器容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大无需反复创建线程而消耗过多资源
线程安全
多个线程共用一个共享数据
经典的卖票问题
解决安全问题:
2. java程序提供技术,同步技术
公式:synchronized(任意的对象,随便){
线程要操作的共享数据
}
package com.lanou.security.demo2;
/**
* 同步方法形式解决线程的安全问题
* 同步方法有锁吗???是本类的对象引用
* 静态方法中同步有锁吗?同步有锁吗?有
* 是this吗?不是!
* 静态方法,同步锁是本类类名.class
*
* @author wangyucui
* @date 2018/7/21 下午10:33
*/
public class Tickets implements Runnable {
//定义出售的票源
private int ticket = 100;
@Override
public void run() {
while (true) {
payTicket();
}
}
public synchronized void payTicket() {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
//出现异常,锁是不释放的
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket--);
}
}
}
- Lock接口
灵活性好
package com.lanou.security.demo3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用JDK1.5接口Lock
* <p>
* Lock接口方法:
* lock()获取锁
* unlock释放锁,放在finally里
* 实现类:ReentrantLock
*
* @author wangyucui
* @date 2018/7/21 下午10:33
*/
public class Tickets implements Runnable {
//定义出售的票源
private int ticket = 100;
//在类的成员位置,创建Lock接口的实现类对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//调用Lock接口方法
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "出售第" + ticket--);
} catch (InterruptedException e) {
} finally {
//释放锁,调用Lock接口方法unlock
lock.unlock();
}
}
}
}
}
死锁
当线程中使用了多个锁时,如果同步中嵌套了其他的同步,这时容易引发一种现象:程序出现无线等待,这种现象称为死锁。
package com.lanou.security.demo4;
/**
* @author wangyucui
* @date 2018/7/24 下午3:29
*/
public class DeadLock implements Runnable {
private int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
//先进入A同步,再进入B同步
synchronized (LockA.locka) {
System.out.println("if....Locka");
synchronized (LockB.lockb) {
System.out.println("if...Locka");
}
}
} else {
//先进入B同步,再进入A同步
synchronized (LockB.lockb) {
System.out.println("else...lockb");
synchronized (LockA.locka) {
System.out.println("else...Locka");
}
}
}
i++;
}
}
}
等待唤醒机制(线程通信 )
线程之间的通信:多个线程在处理同一个资源,但是处理的动作却不相同。通过一定的手段是各个线程能有效利用资源。而这种手段即–等待唤醒机制。
等待唤醒机制所涉及到的方法:
- wait():等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中;
- notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的;
- notifyAll():唤醒全部:可以将线程池的所有wait线程都唤醒