😍多线程与并发😍
1 进程与线程
1、什么是进程
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
进程是一个具有一定独立功能的程序,一个实体,每一个进程都有它自己的地址空间。(独立运行的程序)
2、进程的状态
进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程具有以下三种基本状态。
1)就绪状态(Ready)
2)运行状态(Running)
3)阻塞状态(Blocked)
3、什么是线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干程序又可以划分成若干个线程。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程(单线程程序)
一个程序可以同时执行多个任务,来提高效率。
例如:
(1)同时下载多个电影
(2)同时与多人聊天
- 并行:就是两个任务同时运行(多个CPU)
- 并发:是指两个人任务同时请求运行,而处理器一次只能接受一个任务,就会把两个任务安排轮流执行,由于CPU时间片运行时间较短,就会感觉两个任务在同时执行
2 线程的基本使用
实现线程的两种方式,继承Thread类和实现Runnable接口;
通常建议是实现接口的方式,因为接口可以实现多个,而继承只能是一个类。
- 一种时继承Thread类
class MyThread extends Thread{
public void run(){
//逻辑处理
}
}
MyThread mt = new MyThread();
mt.start();
- 另一种时实现Runnable接口
class MyRunnable implements Runnable{
public void run(){
//逻辑处理
}
}
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
代码实现:
package com.huang.TestThread.ThreadDemo01;
/**
* 多线程的实现
* 有两种实现方法
*/
public class Demo01 {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();//启动线程
//推荐使用第二种方法,更加灵活
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
}
}
/**
* (1)实现多线程的第一种方法,继承Thread类
*/
class MyThread extends Thread{
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
/**
* (2)实现多线程的第二种方法,实现Runnable接口类
*/
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
3 线程休眠
线程的休眠,目的是让出CPU执行的时间片,让其他工作的线程可以执行,但不会释放对象锁。
方法:
public static void sleep(long millis) throws InterruptedException使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),释放CPU的时间片,具体取决于系统定时器和调度程序的精度和准确性。线程不会丢失任何显示器的所有权。
public static void sleep(long millis,int nanos) throws InterruptedException毫秒,纳秒
static Thread currentThread()返回对当前正在执行的线程对象的引用。
参数:
millis-以毫秒为单位的睡眠时间长度
异常:
IllegalArgumentException - 如果 millis值为负数。
InterruptedException - 如果任何线程中断当前线程。当抛出此异常时,当前线程的中断状态将被清除。
package com.huang.TestThread.ThreadDemo01;
/**
* 多线程的实现
* 有两种实现方法
*/
public class Demo01 {
public static void main(String[] args) {
MyThread mt = new MyThread();
//推荐使用第二种方法,更加灵活
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
mt.start();//启动线程
t.start();
}
}
/**
* (1)实现多线程的第一种方法,继承Thread类
*/
class MyThread extends Thread{
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* (2)实现多线程的第二种方法,实现Runnable接口类
*/
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4 join与中断线程
join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行
- public final void join() throws InterruptedException等待这个线程死亡。
t.join();
- 异常
InterruptedException
- 如果任何线程中断当前线程。当抛出此异常时,当前线程的中断状态将被清除。
想要中断线程,使用自定义标记的方式实现或许比interrupt方法更加实用和优雅
-
public void interrupt() 中断这个线程。
除非当前线程中断自身,这是始终允许的 -
public static boolean interrupted()
-
测试当前线程是否中断。该方法可以清除线程的中断状态。换句话说,如果这个方法被连续调用两次,那么第二个调用蒋返回false(除非当前线程再次中断,在第一个调用已经清除其中断状态之后,在第二个调用之前已经检查过)。
-
忽略线程中断,因为线程在中断时不存在将被该方法返回false所反映。
-
-
自定义标记中断线程
package com.huang.TestThread.ThreadDemo01;
/**
* join 与 中断线程
* jion方法:加入线程,让调用的线程先执行指定时间或执行完毕,再执行其他线程
* 中断线程:
* (1)使用interrupt方法来中断线程,设置一个中断状态(标记)
* (2)自定义标记的方法(推荐使用)
*/
public class Demo02 {
public static void main(String[] args) {
MyRunnable1 mr = new MyRunnable1();
Thread t = new Thread(mr);
// t.start();
MyRunnable2 mr2 = new MyRunnable2();
Thread t2 = new Thread(mr2);
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==5){
// try {
// t.join();//让t线程执行完毕
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// t.interrupt();//中断线程,只是做了一个中断标记
mr2.flag = false;//自定义中断线程
}
}
}
}
//(1)使用interrupt方法来中断线程,设置一个中断状态(标记)
class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(Thread.interrupted()){//测试中断状态,此方法会把中断状态清楚清除
//。。。做处理
break;//主动中断进程(跳出)
}
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);//休眠,抛出异常时会清除当前线程的中断状态
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();//再打上中断标记
}
}
}
}
//(2)自定义中断标记
class MyRunnable2 implements Runnable{
public boolean flag = true;
public MyRunnable2() {
flag = true;
}
@Override
public void run() {
int i = 0;
while(flag){
System.out.println(Thread.currentThread().getName()+"=="+(i++));
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5 守护线程与yield
public final void setDaemon(boolean on)
将此线程标记为daemon线程(守护线程)或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
public final boolean isDaemon()
测试这个线程是否是守护线程。
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。(了解)
yield作用是暂停当前正在执行的线程对象(放弃当前cpu资源),并执行其他线程。yield是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会
package com.huang.TestThread.ThreadDemo01;
/**
* 守护线程与yield
*
*/
public class Demo03 {
public static void main(String[] args) {
MyRunnable3 mr3 = new MyRunnable3();
Thread t = new Thread(mr3);
t.setName("Thread-t");
//优先级高可以提高该线程抢占CPU时间片的概率大
t.setPriority(Thread.MAX_PRIORITY);
//线程可以分成守护线程和 用户线程,当进程中没有用户进程时,JVM会退出
t.setDaemon(true);//把线程设置为守护进程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("main--"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==3){
Thread.yield();//让出本次CPU执行时间片
}
}
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("--"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6 其他方法与优先级
方法 | 功能 |
---|---|
long getld() | 返回该线程的标识符. |
String getName() | 返回该线程的名称. |
void setName(String name) | 改变线程名称,使之与参数name相同. |
boolean isAlive() | 测试线程是否处于活动状态. |
void setPriority(int newPriority) | 更改线程的优先级. |
static int MAX PRIORITY | 线程可以具有的最高优先级. |
static int MIN PRIORITY | 线程可以具有的最低优先级. |
static int NORM PRIORITY | 分配给线程的默认优先级. |
7 线程同步
在多线程编程时,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
线程同步是确保多线程共享数据的安全性,同时也会牺牲性能,同步过多还可能产生死锁,因此务必按需求使用同步
1、多线程共享数据
在多线程的操作中,多个线程有可能同时处理同一个资源,这就是多线程中的共享数据。
2、线程同步
解决数据共享问题,必须使用同步,所谓同步就是指多个线程在同一个时间段内只能有一个线程执行指定代码,其他线程要等待此线程完成之后才可以继续执行。
线程进行同步,有以下三种方法:
(1)同步代码块
synchronized(要同步的对象){
要同步的操作;
}
(2)同步方法
public synchronized void method(){
要同步的操作;
}
(3)Lock(ReentrantLock)
3、同步准则
当编写synchronized块时,有几个简单的准则可以遵循,这些准则在避免死锁和性能危险的风险方面大有帮助:
(1)使代码块保持简短。把不随线程变化的预处理和后处理移出synchronized块。
(2)不要阻塞。如InputStream.read()
(3)在持有锁的时候,不要对其它对象调用方法。
代码实现:
package com.huang.TestThread.ThreadDemo01;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程同步
* 1、多线程共享数据时,会发生线程不安全的情况
* 2、多线程共享数据必须使用线程同步
* 3、实现同步的三种方法
* (1)使用同步代码块
* (2)使用同步方法
* (3)使用Lock(更灵活的代码控制)
* 多线程共享数据会有安全问题,使用同步可以解决安全问题,但同时会牺牲性能,所以同步的代码块
* 要尽量保持简短,把不随数据变化的相关代码移除同步块,不要阻塞
*/
public class Demo04 {
public static void main(String[] args) {
MyRunnable4 mr4 = new MyRunnable4();
Thread t1 = new Thread(mr4);
Thread t2 = new Thread(mr4);
t1.start();
t2.start();
}
}
class MyRunnable4 implements Runnable{
private int ticket = 10;//售票
private Object obj = new Object();//同步锁
@Override
public void run() {
for (int i = 0; i < 300; i++) {
//(1)同步代码块
// synchronized (obj) {//this 代替 obj 也可以
// if(ticket > 0) {
// ticket--;
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("您购买的票数剩余:" + ticket + "张");
// }
// }
//(2)同步方法
// method1();
//(3)Lock实现同步
method2();
}
}
//(2)同步方法:同步的对象时当前对象(this)
public synchronized void method1(){
if(ticket > 0) {
ticket--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票数剩余:" + ticket + "张");
}
}
//(3)Lock实现同步
ReentrantLock lock = new ReentrantLock();//互斥锁
public void method2(){
lock.lock();//上锁
try {
if(ticket > 0) {
ticket--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("您购买的票数剩余:" + ticket + "张");
}
}finally {
lock.unlock();//解锁
}
}
}
8 死锁
了解死锁是为了更好的避免程序中出现死锁,这在多线程共享资源(数据)时尤为关键
过多的同步有可能出现死锁,死锁的操作一般是在程序运行的时候才有可能出现。
多线程中要进行资源的共享,就需要同步,但同步过多,就可能造成死锁。
线程死锁:在一个同步方法中调用了另一个对象的同步方法,可能产生死锁。
9 生产与消费者应用案例
多线程的开发中有一个最经典的操作案例,就是生产者-消费者,生产者不断生产产品,消费者不断取走产品。
例如:饭店里的有一个厨师和一个服务员,这个服务员必须等待厨师准备好膳食。当厨师准备好时,他会通知服务员,之后服务员上菜,然后返回继续等待。这是一个任务协作的示例,厨师代表生产者,而服务员代表消费者。
代码实现:
package com.huang.TestThread.ThreadDemo02;
/**
* description:生产者与消费者案例
* 两个线程协同工作,先生产,再消费
* 面试题:
* sleep 与 wait 的区别?
* sleep:让线程进入休眠状态,让出CPU的时间片,不释放对象监视器的所有权(对象锁)
* wait:让线程进入等待状态,让出CPU的时间片,并释放对象监视器的所有权,等待其他线程通过notify方法来唤醒
*/
public class ProducterCustomerDemo {
public static void main(String[] args) {
Food food = new Food();
Producter p = new Producter(food);
Customers c = new Customers(food);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
class Customers implements Runnable{
private Food food;
public Customers(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
food.get();
}
}
}
/**
* 生产者
*/
class Producter implements Runnable{
private Food food;
public Producter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i %2==0){
food.set("辣子鸡","麻辣口味");
}else{
food.set("红烧肉","肥而不腻");
}
}
}
}
//食物
class Food{
private String name;
private String desc;
private boolean flag = true;//true:可以生产;false:可以消费(先生产再消费)
/**
* 生产产品
*/
public synchronized void set(String name, String desc){
//不能生产
if(!flag){
try {
this.wait();//线程进入等待状态,释放监视器的所有权(对象锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setName(name);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setDesc(desc);
flag = false;
this.notify();//唤醒等待的线程(随机的其中一个)
}
/**
* 消费产品
*/
public synchronized void get(){
//不能消费
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"->"+this.getDesc());
flag = true;
this.notify();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Food{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
public Food() {
}
public Food(String name, String desc) {
this.name = name;
this.desc = desc;
}
}
10 线程生命周期
11 线程池
- 使用线程池最直接的好处就是:线程可以重复利用、减少创建和销毁线程所带来的系统资源的开销,提升性能(节省线程创建的时间开销,使程序响应更快)
- 线程在未来的学习中通常会出现在各种组件、框架的底层实现中,例如,Servlet多线程、Tomcat多线程等
线程池是预先创建线程的一种技术。线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用。减少频繁的创建和销毁对象。
jdk1.5版本以上提供了现成的线程池。
Java里面线程池的顶级接口是Executor,是一个执行线程的工具。
线程池接口是ExecutorService
java.util.concurrent包:并发编程中很常用的实用工具类
**Executor接口:**执行已提交的Runnable任务的对象。
**ExecutorService
接口:**Executor提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成Future的方法。
**Executors类:**此包中所定义的Executor、ExecutorService等的工厂和实用方法。
在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
- newSingleThreadExecutor:
- 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。
- 如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。
- 此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool:
- 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
- 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- newCachedThreadPool:
- 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。
- 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- newScheduledThreadPool:
- 创建一个大小无限的线程池。
- 此线程池支持定时以及周期性执行任务的需求。
代码实现:
package com.huang.TestThread.ThreadDemo01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* description:线程池
*/
public class Demo05 {
public static void main(String[] args) {
//创建线程池(4种)
//(1)创建一个单线程线程池
// ExecutorService es = Executors.newSingleThreadExecutor();
//(2)创建一个固定大小的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
es.execute(new MyRunnable5());
es.execute(new MyRunnable5());
es.shutdown();
}
}
class MyRunnable5 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
资料来源:点击进入