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接口@Overridepublic 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;@Overridepublic 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;}@Overridepublic 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为实现类,继承接口Lockprivate Lock lock = new ReentrantLock();@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic void run() {while (true) {this.r.getCount();}}}
public class Produce implements Runnable {private Resource r;public Produce(Resource r) {this.r = r;}@Overridepublic 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;}@Overridepublic void run() {while (true) {r.getCount();}}}
public class Produce implements Runnable {private Resource r;public Produce(Resource r) {this.r = r;}@Overridepublic 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 {@Overridepublic void run() { // 线程的run方法不能有返回值System.out.println(Thread.currentThread().getName() + "我是线程的run方法");}}
public class MyCall implements Callable<String> {@Overridepublic 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(); // 销毁线程池}}
