01 多线程
1. 线程的基本概念
1.1 进程
任何的软件存储在磁盘中,运行软件的时候,OS使用IO技术,将磁盘中的软件的文件加载到内存,程序在能运行。
进程的概念 : 应用程序(typerpa,word,IDEA)运行的时候进入到内存,程序在内存中占用的内存空间(进程).
1.2线程
线程(Thread) : 在内存和CPU之间,建立一条连接通路,CPU可以到内存中取出数据进行计算,这个连接的通路,就是线程.
一个内存资源 : 一个独立的进程,进程中可以开启多个线程 (多条通路)
并发: 同一个时刻多个线程同时操作了同一个数据
并行: 同一个时刻多个线程同时执行不同的程序
2. Java实现线程程序
2.1 java.lang.Thread
一切都是对象,线程也是对象,Thread类是线程对象的描述类
实现线程程序的步骤 :
- 定义类继承Thread
- 子类重写方法run
- 创建子类对象
- 调用子类对象的方法start()启动线程
public class SubThread extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("run--------" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
SubThread subThread = new SubThread();
subThread.start();
// 打印的顺序开始无序了
for (int i = 0; i < 50; i++) {
System.out.println("main------" + i);
}
}
}
2.2 获取线程的名字
public class subThread extends Thread {
public void run() {
// 获取线程名字
// 每个线程都有自己的名字:thread-xx数字
System.out.println("线程名字:" + super.getName());
}
}
public class threadTest extends Thread {
public static void main(String[] args) {
// 创建2个线程实例
subThread subThread = new subThread();
subThread.start();
subThread subThread1 = new subThread();
subThread1.start();
subThread subThread2 = new subThread();
subThread2.start();
// 获取当前线程对象,拿到运行main方法的线程对象
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "-------");
}
}
2.3线程的优先级
public class PriorityThread extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
PriorityThread p1 = new PriorityThread();
PriorityThread p2 = new PriorityThread();
PriorityThread p3 = new PriorityThread();
// 线程优先级最大是10,最小为1,默认为5,基本测不出来
p1.setPriority(Thread.MAX_PRIORITY);
p2.setPriority(Thread.MIN_PRIORITY);
p3.setPriority(3);
p1.start();
p2.start();
p3.start();
}
}
2.4线程让步
Thread类的方法 join()
- 解释,执行join()方法的线程,他不结束,其它线程运行不了
public class ThreadTest {
public static void main(String[] args) {
ThreadJoin j0 = new ThreadJoin();
ThreadJoin1 j1 = new ThreadJoin1();
j0.setPriority(10);
j1.setPriority(1);
j0.start();
try {
j0.join(); // 线程等待,只有j0完成,j1才开始做,只有在start()方法下面,执行join方法的线程,它不结束,其他线程运行不了,更换位置就不行了
} catch (InterruptedException e) {
e.printStackTrace();
}
j1.start();
}
}
Thread类的方法 static yield()
- 线程让步,线程把执行权让出
public class ThreadJoin extends Thread {
public void run() {
Thread.yield(); // 线程让步
for (int i = 0; i < 50; i++) {
System.out.println(super.getName() + "------" + i);
}
}
}
public class ThreadJoin1 extends Thread {
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(super.getName() + "------" + i);
}
}
}
3. 线程安全
出现线程安全的问题需要一个前提 : 多个线程同时操作同一个资源
线程执行调用方法run,同一个资源是堆内存的
3.1 售票例子
火车票的票源是固定的,购买渠道在火车站买,n个窗口
public class Ticket implements Runnable {
// 定义票源
private int tickets = 100;
private Object object = new Object();
// 实现Runnable接口
@Override
public void run() {
// 当一个线程没有完全完成操作时,其他线程不能操作
while (true) {
/*
* 任意对象:在同步中这个对象称为对象锁,官方的稳定称呼叫做:对象监视器
* 同步代码块的执行原理:关键点就是这个对象锁
* 1.线程执行到同步,判读锁是否存在
* 如果锁存在,获取到锁,进入到同步中执行
* 执行完毕,线程出去同步代码块,将锁对象归还
* 2.线程执行到同步,判断锁是否存在
* 如果锁不存在,线程只能在同步代码块这里等待,锁的到来
* 综上:使用同步,线程先判断锁,然后获取锁,出去同步要释放锁,增加了许多步骤,因此线程是安全,运行速度慢,牺牲性能,不能牺牲数据安全
* 注意:任意的对象 一定要是运行时唯一的,否则进来的锁和出去的锁不一样,线程自然也不行
* */
synchronized (object) { // 使用同步代码块需要要的格式: synchronized(任意的对象){代码块}
if (tickets > 0) {
try {
Thread.sleep(300); // 线程休眠,暂停执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().toString() + "当前出售第:" + tickets + "张");
tickets--;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 创建实现了runnable实例,
Ticket t = new Ticket();
// 创建3个窗口,3个线程
Thread r = new Thread(t);
Thread r1 = new Thread(t);
Thread r2 = new Thread(t);
// 使用的全是t实例的run方法
r.start();
r1.start();
r2.start();
}
}
另一种实现方法,使用synchronized 同步方法
public class Ticket implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
num();
}
}
// synchronized方法,当一个方法中,所有的代码都是线性操作的共享内容,可以在方法的定义添加同步的关键字,同步的方法,可以被称为同步的函数
public synchronized void num() {
// synchronized (Ticket.class){ 静态方法使用synchronized,那个任意对象就可以使用当前类的对象:xxx(当前的类).class
//
// }
if (tickets > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "当前的出售第:" + tickets + "张");
tickets--;
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread r1 = new Thread(t);
Thread r2 = new Thread(t);
Thread r3 = new Thread(t);
r1.start();
r2.start();
r3.start();
}
}
4. 死锁
public class LockA {
public static LockA lockA = new LockA();
}
public class LockB {
public static LockB lockB = new LockB();
}
public class ThreadDeadLock implements Runnable {
private boolean flag;
public ThreadDeadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (true) {
if (flag) {
/* 在t1线程中,flag为true,因此执行同步方法synchronized (LockA.lockA),将线程锁住,直到代码块中内容全部运行完毕,
因为t2线程中flag为false,也执行了,将线程也锁住了,所以在t1中
synchronized (LockA.lockA)还在等待synchronized (LockB.lockB)的完成,t2中synchronized(LockB.lockB)还在等待
synchronized (LockA.lockA)的完成*/
synchronized (LockA.lockA) {
System.out.println("线程获取了A锁1");
synchronized (LockB.lockB) {
System.out.println("线程获取了B锁1");
}
}
} else {
synchronized (LockB.lockB) {
System.out.println("线程获取了B锁2");
synchronized (LockA.lockA) {
System.out.println("线程获取了A锁2");
}
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
/*
* 死锁程序:多个线程同时争夺同一个锁资源,出现程序假死现象
* 面试点:考察开发人员是否充分理解同步代码的执行原理
* 同步代码块:线程判断锁,获取锁,释放锁,不出代码,锁不释放
* 1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
* 2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
* 3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
* 4.循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。*/
ThreadDeadLock t1 = new ThreadDeadLock(true);
ThreadDeadLock t2 = new ThreadDeadLock(false);
new Thread(t1).start();
/*try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
new Thread(t2).start();
}
}
5. JDK5新特性Lock锁
JDK5新的特性 : java.util.concurrent.locks包. 定义了接口Lock.
Lock接口替代了synchronized,可以更加灵活
Lock接口的方法
- void lock() 获取锁
- void unlock()释放锁
- Lock接口的实现类ReentrantLock
public class ThreadLock implements Runnable {
private int num = 100;
// ReentrantLock为实现类,继承接口Lock
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
count();
}
}
public void count() {
lock.lock(); // 获取锁
if (num > 0) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "当前门票还剩:" + num);
num--;
}
lock.unlock();
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadLock t = new ThreadLock();
Thread r1 = new Thread(t);
Thread r2 = new Thread(t);
Thread r3 = new Thread(t);
r1.start();
r2.start();
r3.start();
}
}
6. 生产者和消费者案列
6.1 synchronized实现
/*
* 消费线程
* 资源对象中变量输出打印*/
public class Customer implements Runnable {
private Resource r;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
synchronized (r) {
if (!r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费第:" + r.count);
r.flag = false;
r.notify();
}
}
}
}
/*
* 生产线程
* 资源对象中变量增加*/
public class Produce implements Runnable {
private Resource r;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
// 生产者
/*
* 1.创建了2个线程,进行创建和消费
* 2.将Resource作为对象锁
* 3.使用synchronized同步代码块
* 4.在初始化Resource实例时,flag必定为false,因此,is中代码不生效,生产+1,打印语句,修改标志位为true,并唤醒对象锁的另一个线程(线程可以空唤醒)
* 5.因为while为true,所以继续循环,这次flag为true,走if语句,wait当前线程等待
* 6.因为唤醒了消费者线程,在消费者线程中对flag取反,也不走if,消费生产的数据,修改flag = false,并唤醒对象锁的另一个线程,
* 7.因为while为true,所以继续循环,这次flag为false取反,所以走if语句,wait当前线程等待,创建线程开始运行
* 8.4-7步依次循环,直到手动停止*/
synchronized (r) {
// 判断标志位,是否允许生产
// flag为true,生产完成,等待消费
if (r.flag) {
try {
r.wait(); // // 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
r.count++;
System.out.println(Thread.currentThread().getName() + "生产第" + r.count + "个");
// 修改标志位,已经生产了,需要消费
r.flag = true;
// 唤醒消费者线程
r.notify(); // 唤醒正在等待对象监视器的单个线程。如果有多个线程在等待,随机挑选一个线程唤醒
}
}
}
}
//资源
public class Resource {
public int count;
public boolean flag;
}
public class ThreadTest {
public static void main(String[] args) {
Resource r = new Resource();
Customer c = new Customer(r);
Produce p = new Produce(r);
Thread t1 = new Thread(c); // 消费者线程
Thread t2 = new Thread(p); // 生产者线程
t1.start();
t2.start();
}
}
优化synchronized同步代码的生产者和消费者例子
public class Customer implements Runnable {
private Resource r;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
this.r.getCount();
}
}
}
public class Produce implements Runnable {
private Resource r;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
this.r.setCount();
}
}
}
public class Resource {
private int count = 0;
private boolean flag = false;
// 消费
public void getCount() {
synchronized (this) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费为:" + count);
flag = false;
this.notify();
}
}
// 生产
public void setCount() {
synchronized (this) {
if (flag) {
try {
// sleep:在等待时间里,同步锁不会释放,不丢失
// wait:在等待的时间里,会释放同步锁,被唤醒才能去取得同步锁,才能执行
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "生产为:" + count);
flag = true;
this.notify();
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Resource r = new Resource();
Customer c = new Customer(r);
Produce p = new Produce(r);
Thread t1 = new Thread(c);
Thread t2 = new Thread(p);
t2.start();
t1.start();
}
}
6.2 Lock实现
public class Customer implements Runnable {
private Resource r;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.getCount();
}
}
}
public class Produce implements Runnable {
private Resource r;
public Produce(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setCount();
}
}
}
public class Resource {
private int count = 0;
private boolean flag = false;
// ReentrantLock为实现类
Lock lock = new ReentrantLock();
Condition prod = lock.newCondition(); // 生产者线程阻塞队列
Condition cust = lock.newCondition(); // 消费者线程阻塞队列
public void setCount() {
lock.lock(); // 获取锁
if (flag) {
try {
prod.await(); // 生产者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "生产:" + count);
flag = true;
cust.signal(); // 唤醒消费者线程中的一个
lock.unlock(); // 释放锁
}
public void getCount() {
lock.lock(); // 获取锁
if (!flag) {
try {
cust.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费:" + count);
flag = false;
prod.signal(); // 唤醒线程
lock.unlock(); // 释放锁
}
}
public class ThreadTest {
/*
* 线程方法wait和sleep的区别
* wait:在等待中会释放锁,只有被唤醒才能去重新获取锁,才能执行
* sleep:在等待中,没有释放锁,不丢失不释放*/
public static void main(String[] args) {
/*
* wait()方法和notify()/notifyAll()方法,本地调用os的功能,和操作系统交互,JVM找os,把线程停止,频繁等待与唤醒,
* 导致JVM和os交互的次数过多,notifyAll()唤醒全部的线程,也浪费线程资源,为了一个线程,不得不唤醒全部的线程
* 为了更高的性能Lock接口替换了同步synchronized,提供了更加灵活,性能更好的锁定操作
* Lock接口中的方法:newCondition()方法返回的生死接口 Condition
* 例如: Lock lock = new Lock()
* Condition xx1 = lock.NewCondition() =>返回Condition接口实现的类的对象,线程阻塞队列*/
Resource r = new Resource();
Produce p = new Produce(r);
Customer c = new Customer(r);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
7. 线程池
public class MyRunnable implements Runnable {
@Override
public void run() { // 线程的run方法不能有返回值
System.out.println(Thread.currentThread().getName() + "我是线程的run方法");
}
}
public class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
return "我是线程返回值";
}
}
public class ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 线程的缓冲池,目的就是为了提高效率,JDK5开始内置线程池
* 1.Executors类
* 静态方法 static newFixedThreadPool(int 线程的个数)
* 该方法的返回值ExecutorService接口的实现类,管理池子里面的线程
* 2.ExecutorService接口的方法
* submit(Runnable r)提交线程执行的任务*/
/*
* 线程的生命周期有6个,在某一时刻,线程只能处于其中的一种状态,这种线程的状态反应的是JVM的线程状态和OS无关
* 1.创建=>new,开始线程(执行中,受阻塞)2种
* 2.执行中=>runnable运行过程中则会出现2种状态(受阻塞和结束)
* 3.结束=>terminated
* 4.受阻塞=>blocked,受阻塞也分2种(有时间的,和无限等待的)
* 5.时间=>timed_waiting 如:sleep
* 6.无限等待=>waiting 如 wait*/
// 创建线程池,线程个数是3个
ExecutorService es = Executors.newFixedThreadPool(3);
// 线程池管理对象,线程池调用submit()方法才可以使用线程中的run方法,
MyRunnable r = new MyRunnable();
es.submit(r);
es.submit(r);
es.submit(r);
es.submit(r);
// 如果运行了但不销毁线程,该线程不会自己销毁,需要手动销毁
// 如果需要线程任务的返回值,那么需要实现Callable接口实现类
Future<String> future = es.submit(new MyCall());
// 获取线程返回值
String str = future.get();
System.out.println("线程返回值为:" + str);
es.shutdown(); // 销毁线程池
}
}