1.什么是进程
正在运行的程序,是系统进行资源分配的基本单位
2.什么是线程
线程,又称轻量级进程(Light Weight Process)。进程中的一条执行路径,也是CPU的基本调
度单位。 一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。
3.进程和线程的区别
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
- 一个程序运行后至少有一个进程。
- 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的。
- 进程间不能共享数据段地址,但是同进程的线程之间可以。
4.线程的组成
- 任何一个线程都具有基本的组成部分
- CPU时间片:操作系统(OS)会为每个线程分配执行时间
- 运行数据:(堆空间、栈空间)
- 堆空间:存储线程需要的对象,多个线程可以共享堆中的数据
- 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈.
- 线程的逻辑代码
5.线程的特点
5.1线程抢占式执行
- 效率高
- 可防止单一线程长时间独占CPU.
5.2在单核CPU中,宏观上同时执行,微观上属性执行
6.线程的创建方式
6.1【继承Thread类,重写run方法】
6.1.1创建线程
6.1.2获取和设置线程的名称
获取线程ID和线程名称
- 在Thread的子类中调用this.getId()或this.getName()
- 使用Thread.currentThread().getId和Thread.currentThread().getName()
修改线程名称
- 调用线程对象的setName()方法
- 使用线程子类的构造方法赋值
案例:
public class Ticket extends Thread{
@Override
public void run(){
for (int i=100;i>0;i--){
System.out.println(Thread.currentThread().getName()+ "卖了一张票"+"剩余:"+i+"张");
}
}
}
public class Test {
public static void main(String[] args) {
Ticket t1 = new Ticket();
t1.setName("张三");
Ticket t2 = new Ticket();
t2.setName("李四");
Ticket t3 = new Ticket();
t3.setName("王五");
Ticket t4 = new Ticket();
t4.setName("赵六");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
6.2【实现Runnable接口】
6.2.1创建线程
案例:
public class Ticket implements Runnable{
private int ticket =100;
@Override
public void run() {
while (true){
if (ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余:"+ticket+"张票");
}else {
break;
}
}
}
}
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"曹操");
Thread t2 = new Thread(ticket,"刘备");
Thread t3 = new Thread(ticket,"孙权");
Thread t4 = new Thread(ticket,"司马懿");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
package com.ykq.demo05;
/**
* @program: qy151-java高级-多线程
* @description:
* @author: YSH
* @create: 2022-07-17 12:22
**/
public class TestBank {
public static void main(String[] args) {
BankCard bankCard=new BankCard(0);
SaveMoney save=new SaveMoney(bankCard);
TakeMoney take=new TakeMoney(bankCard);
Thread t1=new Thread(save,"喜羊羊");
Thread t2=new Thread(take,"美羊羊");
t1.start();
t2.start();
}
}
//取钱得任务
class TakeMoney implements Runnable{
private BankCard card;
public TakeMoney(BankCard card){
this.card=card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(card.getBalance()>=1000) {
card.setBalance(card.getBalance() - 1000);
System.out.println(Thread.currentThread().getName() + "从卡中取出1000,卡中余额为:" + card.getBalance());
}else{
System.out.println("赶紧存钱,卡中没钱了.");
i--;
}
}
}
}
//存钱得任务
class SaveMoney implements Runnable{
private BankCard card;
public SaveMoney(BankCard card){
this.card=card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
card.setBalance(card.getBalance()+1000);
System.out.println(Thread.currentThread().getName()+"往卡中存入1000,卡中余额为:"+card.getBalance());
}
}
}
//抽取类:---银行卡类
class BankCard {
private double balance;
public BankCard(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
6.3【实现Callable接口】
package com.qy151.demo04;
import java.util.concurrent.*;
/**
* @unthor : YSH
* @date : 21:16 2022/7/18
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
My task = new My();
/* FutureTask futureTask = new FutureTask(task);
Thread t1 = new Thread(futureTask);
t1.start();
System.out.println(futureTask.get());
//自建创建线程对象并提交Callable类型的任务是比较麻烦的,需要封装到一个FutureTask类种, 建议使用线程池来提交任务*/
My2 task2 = new My2();
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<Integer> future = executorService.submit(task);
Future<Integer> future2 = executorService.submit(task2);
Integer sum = future.get();//需要等线程执行完毕后,才会把结果返回给该变量
Integer sum2 = future2.get();//需要等线程执行完毕后,才会把结果返回给该变量
System.out.println(sum+sum2);
//应用场景适合大文件上传。
}
}
class My implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <=50; i++) {
sum+=i;
}
return sum;
}
}
class My2 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 51; i <= 100; i++) {
sum += i;
}
return sum;
}
}
7.线程的状态
8.线程的常见方法
8.1休眠
public static void sleep(long millis)
当前线程主动休眠millis毫秒
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 13:03 2022/7/17
*/
public class TestSleep {
public static void main(String[] args) {
T t = new T();
t.start();
for (int i =0;i<20;i++){
System.out.println("main没有休眠======="+i);
}
}
}
class T extends Thread{
@Override
public void run(){
for (int i=0;i<20;i++){
System.out.println("main没得休眠======="+i);
try {
Thread.sleep(100);//使当前线程休眠一段时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠了================"+i);
}
}
}
8.2放弃
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 13:14 2022/7/17
*/
public class TestYield {
public static void main(String[] args) {
T2 t01 = new T2();
T2 t02 = new T2();
t01.setName("王五");
t02.setName("赵六");
t01.start();
t02.start();
}
}
class T2 extends Thread{
@Override
public void run(){
for (int i = 0; i < 10; i++) {
Thread.yield();//可能导致t1,t2交替的频率高了
System.out.println(Thread.currentThread().getName()+"============"+i);
}
}
}
8.3加入
public final void join()
允许其它线程加入到当前线程中
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 13:23 2022/7/17
*/
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
/* T3 t01 = new T3();
t01.setName("张三");
t01.start();
t01.join();//把线程t01加入到main线程中,直到t01执行完毕后, main线程再执行
for (int i = 0; i < 15; i++) {
System.out.println("main============"+i);
}*/
//t1,t2,t3 如何保证这三个线程有序的执行--t1执行完毕后t2再执行--t2执行完毕后t3再执行
T3 t01 = new T3();
T3 t02 = new T3();
T3 t03 = new T3();
t01.setName("一一");
t02.setName("二二");
t03.setName("三三");
t01.start();
t01.join();
t02.start();
t02.join();
t03.start();
}
}
class T3 extends Thread{
@Override
public void run(){
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"============"+i);
}
}
}
8.4优先级
线程对象.setPriority()
线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高
8.5守护线程
线程对象.set.Daemon(true);设置为守护线程
线程有两类:用户线程(前台线程)和守护线程(后台线程)
如果程序中所有前台线程都执行完毕了,后台线程也会自动结束
垃圾回收线程属于守护线程。
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 13:39 2022/7/17
*/
public class TsetDaemon {
public static void main(String[] args) {
T4 t4 = new T4();
t4.setDaemon(true);//设置t4为守护线程,当前台线程都执行完毕后,守护线程也会自动结束
t4.start();
for (int i = 0; i < 20; i++) {
System.out.println("main==============="+i);
}
}
}
class T4 extends Thread{
@Override
public void run(){
for (int i = 0; i < 50 ; i++) {
System.out.println(Thread.currentThread().getName()+"========="+i);
}
}
}
9.线程的状态(等待)
10.线程安全问题
package com.qy151.demo03;
import java.util.Arrays;
/**
* @unthor : YSH
* @date : 13:55 2022/7/17
*/
public class TestSaft {
private static String[] arr =new String[5];
private static int index=0;
public static void main(String[] args) throws InterruptedException {
//匿名对象
Runnable hello = new Runnable() {
@Override
public void run() {
if (arr[index]==null){
arr[index]="hello";
index++;
}
}
};
Runnable world = new Runnable() {
@Override
public void run() {
if (arr[index]==null){
arr[index]="world";
index++;
}
}
};
Thread t1 =new Thread(hello);
Thread t2 =new Thread(world);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.toString(arr));
}
}
多线程安全问题
如何保证线程的安全性?
package com.qy151.demo03;
import java.util.Arrays;
/**
* @unthor : YSH
* @date : 14:09 2022/7/17
*
* 加锁
* 如何解决线程安全问题--加锁
*/
public class TestSaft02 {
private static String[] arr =new String[5];
private static int index=0;
public static void main(String[] args) throws InterruptedException {
//匿名对象
Runnable hello = new Runnable() {
@Override
public void run() {
synchronized (arr){
if (arr[index]==null){
arr[index]="hello";
index++;
}
}
}
};
Runnable world = new Runnable() {
@Override
public void run() {
synchronized (arr) {//临界资源或共享资源
if (arr[index] == null) {
arr[index] = "world";
index++;
}
}
}
};
Thread t1 =new Thread(hello);
Thread t2 =new Thread(world);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.toString(arr));
}
}
10.1同步方式
同步代码块
synchronized(临界资源对象){//对临界资源对象加锁
//代码()原子操作
}
注意:
- 每个对象都有一个互斥锁标记,用来分配给线程的
- 只有拥有对象互斥锁标记的线程,才可以进入该对象加锁的同步代码块
- 线程退出同步代码块时,会释放相应的互斥锁标记
10.2同步方法
synchronized 返回值类型 方法名称(形参列表0){ //当前对象(this)加锁
//代码(原子操作)
}
注意
- 只有拥有对象互斥锁标记的线程,才可以进入该对象加锁的同步代码块
- 线程退出同步代码块时,会释放相应的互斥锁标记
10.3同步规则
注意:
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
如果用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用已知JDK中线程安全的类:
StringBuffer
Vector
Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法
11.线程死锁
当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。
例子: 情人节---两个情人去餐厅吃饭---必须具有两个筷子。
男方拥有一根筷子,女方拥有另一个筷子。
package com.qy151.demo05;
/**
* @unthor : YSH
* @date : 14:38 2022/7/18
*/
public class LockObject {
public static Object a = new Object();
public static Object b = new Object();
}
package com.qy151.demo05;
/**
* @unthor : YSH
* @date : 14:38 2022/7/18
*/
public class Boy extends Thread{
@Override
public void run(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockObject.a){
System.out.println(Thread.currentThread().getName()+"拥有一个筷子a");
synchronized (LockObject.b){
System.out.println(Thread.currentThread().getName()+"拥有一个筷子b");
System.out.println("可以吃饭了");
}
}
}
}
package com.qy151.demo05;
/**
* @unthor : YSH
* @date : 14:40 2022/7/18
*/
public class Girl extends Thread{
@Override
public void run(){
synchronized (LockObject.b){
System.out.println(Thread.currentThread().getName()+"拥有一个筷子b");
synchronized (LockObject.a){
System.out.println(Thread.currentThread().getName()+"拥有一个筷子a");
System.out.println("可以吃饭了");
}
}
}
}
package com.qy151.demo05;
/**
* @unthor : YSH
* @date : 14:41 2022/7/18
*/
public class Test {
public static void main(String[] args) {
Boy boy = new Boy();
Girl girl = new Girl();
boy.setName("邓超");
girl.setName("孙俪");
boy.start();
girl.start();
}
}
11.1造成死锁得原因:
锁与锁之间有嵌套导致。
11.2如何解决死锁:
1. 尽量减少锁得嵌套。 2. 可以使用一些安全类。 3. 可以使用Lock中得枷锁,设置枷锁时间。
11.3经典问题
死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
12.线程通信
12.1线程通信中使用得方法
例子: 存钱和取钱。
先存钱---再取钱-
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 19:47 2022/7/18
*/
public class BankCard {
private double balance;
//true 表示有钱 false 表示没钱
public boolean flag=false;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public synchronized void saveMoney(double money){
if (flag==true){
try {
this.wait(1000);
}catch (Exception e){
e.printStackTrace();
}
}
//存钱的功能
this.balance=this.balance+money;
System.out.println(Thread.currentThread().getName()+"往卡中存钱"+money+"余额为:"+this.balance);
flag=true;
//唤醒等待的队列
this.notify();
}
public synchronized void takeMoney(double money){
if (flag==false){
try {
this.wait(1000);
}catch (Exception e){
e.printStackTrace();
}
}
//存钱的功能
this.balance=this.balance-money;
System.out.println(Thread.currentThread().getName()+"从卡中取钱"+money+"余额为:"+this.balance);
flag=false;
//唤醒等待的队列
this.notify();
}
}
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 19:57 2022/7/18
*/
public class BoyTask implements Runnable{
private BankCard bankCard;
public BoyTask(BankCard bankCard){
this.bankCard=bankCard;
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
bankCard.saveMoney(1000);
}
}
}
package com.qy151.demo02;
/**
* @unthor : YSH
* @date : 19:58 2022/7/18
*/
public class GirlTask implements Runnable{
private BankCard bankCard;
public GirlTask(BankCard bankCard){
this.bankCard=bankCard;
}
@Override
public void run() {
for (int i = 0; i <10; i++) {
bankCard.takeMoney(1000);
}
}
}
package com.qy151.demo02;
import com.qy151.demo01.Boy;
import com.qy151.demo01.Girl;
/**
* @unthor : YSH
* @date : 19:47 2022/7/18
*/
public class Test02 {
public static void main(String[] args) {
BankCard bankCard = new BankCard();
BoyTask boyTask = new BoyTask(bankCard);
GirlTask girlTask = new GirlTask(bankCard);
Thread t1 = new Thread(boyTask,"喜羊羊");
Thread t2 = new Thread(girlTask,"美羊羊");
t1.start();
t2.start();
}
}
12.2sleep和wait的区别
1.所在得类不同。sleep属于Thread类,wait属于Object类。
2.使用的地方: sleep可以使用再任何代码块。wait只能再同步代码块中。
3.是否释放锁资源: sleep不释放锁资源,wait会释放锁资源。
4.sleep时间到了自动唤醒,wait必须需要使用notify和notifyAll唤醒
12.3 notify和notyfyAll区别
12.4经典问题
13.线程池
13.1什么是线程池
该池子中预先存储若干个线程对象。整个池子就是线程池
13.2线程池的作用
13.3线程池的原理
13.4线程池的创建方式
所有的线程池---封装了一个父接口---java.util.concurrent.Executor.
它的实现接口: ExecutorService.
有一个工具类。Executors可以帮你创建相应的线程池。
[1] 创建单一线程池 newSingleThreadExecutor()
[2] 创建定长线程池。newFixedThreadPool(n);
[3] 创建可变线程池. newCachedThreadPool()
[4] 创建延迟线程池 .newScheduledThreadPool(n);
执行方式:
- submit
- execute
- schedule 只有延迟线程池可用
package com.qy151.demo03;
import java.util.concurrent.*;
/**
* @unthor : YSH
* @date : 20:27 2022/7/18
*
*
* //创建线程池的方式
*
Executor:线程池的根类。里面有一个方法。execute执行线程任务的方法Runnable类型的任务
* ExecutorService: 线程池的子接口
* shutdown(); 关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
* shutdownNow(): 立即关闭线程池。
* isTerminated():判断线程池是否终止了。
* submit(): 提交任务给线程池中线程对象、Runnable和Callable类型的任务。
*/
public class Test01 {
public static void main(String[] args) {
//单一线程池: 适应场景:队列要求线程有序执行。
//ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建固定长度的线程池对象
//ExecutorService executorService = Executors.newFixedThreadPool(6);//默认长度为6,超出的线程池对象需要等待
//创建可变长度的线程池对象
//ExecutorService executorService = Executors.newCachedThreadPool();
//创建可延迟的线程池对象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "====================");
}
},10, TimeUnit.SECONDS);
}
executorService.shutdown();
}
}
14.使用最原始的方式创建线程池
上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
package com.qy151.demo03;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @unthor : YSH
* @date : 20:55 2022/7/18
*
*
* int corePoolSize, 核心线程数 -- 线程池中始终存活的线程数
* int maximumPoolSize, 最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
* long keepAliveTime, 最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程
* TimeUnit unit, 时间单位
* BlockingQueue<Runnable> workQueue: 堵塞队列,
*
* 根据你自己的业务以及服务器配置来设置。
*/
//LinkedBlockingDeque:可以设置等待的个数,如果不设置默认为Integer的最大值。
public class Test02 {
public static void main(String[] args) {
BlockingDeque blockingDeque = new LinkedBlockingDeque(3);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,5,10, TimeUnit.SECONDS,blockingDeque);
for (int i = 0; i < 6; i++) {
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"=====================");
}
});
}
threadPoolExecutor.shutdown();
}
}
15.手动锁
Lock它是手动锁的父接口,它下面有很多实现类。
lock()方法。
unlock()释放锁资源,放在finally中
15.1手动锁的使用
15.1.1重入锁
package com.qy151.demo05;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @unthor : YSH
* @date : 21:40 2022/7/18
*/
public class Ticket implements Runnable{
private int ticket =100;
Lock suo = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
suo.lock();
System.out.println(suo.tryLock());
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余:" + ticket + "张票");
} else {
break;
}
} finally {
suo.unlock();
}
}
}
}
package com.qy151.demo05;
/**
* @unthor : YSH
* @date : 21:40 2022/7/18
*/
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"曹操");
Thread t2 = new Thread(ticket,"刘备");
Thread t3 = new Thread(ticket,"孙权");
Thread t4 = new Thread(ticket,"司马懿");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
15.1.2读写锁
感谢观看!!!!!!