JAVA多线程
什么是进程(Process)
- 正在运行的程序,是系统进行资源分配的基本单位
- 目前操作系统都是支持多进程的,可以执行多个进程,通过进程ID区分
- 单核CPU在同一个时刻,只能运行一个进程;宏观并行,微观串行
什么是线程
- 线程,又称轻量级进程。线程中的一条执行路径,也是CPU的基本调度单位。
- 一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程
进程与线程的区别
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位
- 一个程序运行后至少有一个进程
- 一个进程可以包含多个线程,但是至少需要一个线程,否则这个进程是没有意义的
- 进程间不能共享数据段地址,但同进程的线程之间可以
线程的组成
- 任何一个线程都具有基本的组成部分
- CPU时间片:操作系统(OS)会为每个线程分配执行时间
- 运行数据:
- 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
- 线程的逻辑代码
线程的特点
-
线程抢占式执行
-
效率高
-
防止单一线程长时间独占CPU
-
-
在单核CPU中,宏观上同时执行,微观上顺序执行
创建线程
继承Thread类
- 继承Thread方法
- 重写run()方法
- 创建子类对象
- 调用start()启动子线程
获取和修改线程名称
-
获取线程ID和线程名称
- 在Thread的子类中调用this.getId()或this.getName()
- 使用Thread.currentThread().getId()和Thread.currentThread().getName()
-
修改线程名称
- 调用线程对象的setName()方法
- 使用线程子类的构造方法赋值
-
package com.wly.Dome; //创建线程的方式一:继承Thread类,重新run()方法,调用start开启线程 //线程开启不一定立即执行,由cpu调度 public class TestDome extends Thread{ public TestDome() { } public TestDome( String name) { super(name); } public void run(){ for (int i = 0; i < 20; i++) { //继承Thread类使用该类的方法 // System.out.println("线程ID:"+this.getId()+"线程名:"+this.getName()+"子线程----------"+i); //获取当前线程.get方法获取ID System.out.println("线程ID:"+Thread.currentThread().getId()+"线程名:"+Thread.currentThread().getName()); } } public static void main(String[] args){ //main主线程 //创建一个线程对象 TestDome testDome1=new TestDome("子线程1"); //调用start()方法开启线程,cpu调度执行 //修改线程名称 // testDome1.setName("子线程1"); testDome1.start(); TestDome testDome2=new TestDome(); testDome2.setName("子线程2"); testDome2.start(); for (int i = 0; i < 500; i++) { System.out.println("天天向上+++++++++++++"+i); } } }
卖票案例
package com.wly.Dome;
public class TicketWin extends Thread{
private int ticket=100;
private String name;
public TicketWin(String name) {
super(name);
}
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
package com.wly.Dome;
public class TestWin {
public static void main(String[] args) {
//创建4个窗口
TicketWin w1=new TicketWin("窗口1");
TicketWin w2=new TicketWin("窗口2");
TicketWin w3=new TicketWin("窗口3");
TicketWin w4=new TicketWin("窗口4");
w1.start();
w2.start();
w3.start();
w4.start();
}
}
实现Runnable
-
实现Runnable接口
-
覆盖run()方法
-
创建实现类对象
-
创建线程对象
-
调用start()方法
-
package com.wly.Dome; public class TestDome implements Runnable{ public TestDome() { } public void run(){ for (int i = 0; i < 20; i++) { System.out.println("线程ID:"+Thread.currentThread().getId()+"线程名:"+Thread.currentThread().getName()); } } public static void main(String[] args){ //创建对象,表示线程要执行的功能 TestDome testDome1=new TestDome(); //创建线程对象 Thread thread=new Thread(testDome1,"线程1"); thread.start(); for (int i = 0; i < 500; i++) { System.out.println("main+++++++++++++"+i); } } }
匿名内部类实现
package com.wly.Dome;
public class TestDome{
public static void main(String[] args){
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("线程ID:" + Thread.currentThread().getId()
+ "线程名:" + Thread.currentThread().getName()+i);
}
}
};
//创建线程对象
Thread thread=new Thread(runnable,"线程1");
//启动线程
thread.start();
for (int i = 0; i < 500; i++) {
System.out.println("main"+"=========="+i);
}
}
}
资源共享
-
卖票案例
-
package com.wly.Dome; public class Ticket implements Runnable{ private int ticket=100; private String name; @Override public void run() { while (true){ if (ticket<=0){ break; } System.out.println(Thread.currentThread().getName()+"卖了"+ticket+"张票"); ticket--; } } }
-
package com.wly.Dome; public class TestTicket { public static void main(String[] args) { //创建票对象 Ticket ticket=new Ticket(); //创建线程对象 Thread w1=new Thread(ticket,"窗口1"); Thread w2=new Thread(ticket,"窗口2"); Thread w3=new Thread(ticket,"窗口3"); Thread w4=new Thread(ticket,"窗口4"); //启动线程 w1.start(); w2.start(); w3.start(); w4.start(); } }
-
-
存取钱问题
-
package com.wly.Dome; public class BankCard { private double money; public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
-
package com.wly.Dome; public class AddMoney implements Runnable{ private BankCard card; public AddMoney(BankCard card){ this.card=card; } @Override public void run() { for (int i = 0; i <10 ; i++) { card.setMoney(card.getMoney()+1000); System.out.println(Thread.currentThread().getName()+"存了1000,余额是"+card.getMoney()); } } }
-
package com.wly.Dome; public class SunMoney implements Runnable{ private BankCard card; public SunMoney(BankCard card){ this.card=card; } @Override public void run() { for (int i = 0; i <10 ; i++) { if(card.getMoney()>=1000){ card.setMoney(card.getMoney()-1000); System.out.println(Thread.currentThread().getName() +"取了1000,余额是"+card.getMoney()); }else{ System.out.println("余额不足"); i--; } } } }
-
package com.wly.Dome; public class TsetBankCard { public static void main(String[] args) { //创建一张卡 BankCard card=new BankCard(); //创建存钱,取钱 AddMoney add=new AddMoney(card); SunMoney sub=new SunMoney(card); //创建两个线程 Thread cq=new Thread(add,"cq"); Thread qq=new Thread(sub,"qq"); //启动线程 cq.start(); qq.start(); } }
-
package com.wly.Dome; public class TestBankCard2 { private BankCard card; public static void main(String[] args) { //创建卡对象 BankCard card=new BankCard(); //存钱线程 Runnable add=new Runnable() { @Override public void run() { for (int i = 0; i <10 ; i++) { card.setMoney(card.getMoney()+1000); System.out.println(Thread.currentThread().getName()+"存了1000,余额是"+card.getMoney()); } } }; //取钱线程 Runnable sub=new Runnable() { @Override public void run() { for (int i = 0; i <10 ; i++) { if(card.getMoney()>=1000){ card.setMoney(card.getMoney()-1000); System.out.println(Thread.currentThread().getName() +"取了1000,余额是"+card.getMoney()); }else{ System.out.println("余额不足"); i--; } } } }; //启动线程 new Thread(add).start(); new Thread(sub).start(); } }
-
线程的状态
状态 | 说明 |
---|---|
New初始状态 | 线程对象被创建,即为初始状态,只在堆中开辟内存,与常规对象无异。 |
Ready就绪状态 | 调用start()之后,进入就绪状态,等待OS选中并分配时间片 |
Running | 获得时间片之后,进入运行状态,如果时间片到期,则回到就绪状态 |
Terminated终止状态 | 主线程main()或独立线程run()结束,进入终止状态,并释放持有的时间片 |
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rWqJSReA-1672213397412)(D:\笔记\JAVA多线程\IMG_1288(20220330-210836)].PNG)
常见方法
休眠
-
public ststic void sleep(long millis)
-
当前线程主动休眠millis毫秒
-
package com.wly.Dome; public class SleepThread extends Thread{ public void run(){ for (int i = 0; i <10 ; i++) { System.out.println(Thread.currentThread().getName()+"........"+i); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { SleepThread sleep1=new SleepThread(); sleep1.start(); SleepThread sleep2=new SleepThread(); sleep2.start(); } }
放弃
-
public static void yield()
-
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
-
package com.wly.Dome; public class YieldThread extends Thread { public void run(){ for (int i = 0; i <10 ; i++) { System.out.println(Thread.currentThread().getName()+i); //主动放弃时间片 Thread.yield(); } } public static void main(String[] args) { YieldThread y1=new YieldThread(); y1.start(); YieldThread y2=new YieldThread(); y2.start(); } }
加入
-
public final void join()
-
package com.wly.Dome; public class JionThread extends Thread{ public void run(){ for (int i = 0; i <50 ; i++) { System.out.println(Thread.currentThread().getName()+"-----"+i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { JionThread jion1=new JionThread(); jion1.start(); //加入当前(main)线程,并阻塞当前线程,直到jion线程执行完毕 jion1.join(); //主线程 for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"======="+i); Thread.sleep(100); } } }
优先级
-
线程对象.setPriority()
-
优先级为1-10,默认为5,优先级越高,表示获取CPU的机会越多
-
package com.wly.Thread; public class PriorityThread extends Thread{ @Override public void run() { for (int i = 0; i <50 ; i++) { System.out.println(Thread.currentThread().getName()+"-----"+i); } } public static void main(String[] args) { PriorityThread pt1=new PriorityThread(); pt1.setName("p1"); PriorityThread pt2=new PriorityThread(); pt2.setName("p2"); PriorityThread pt3=new PriorityThread(); pt3.setName("p3"); //设置优先级 pt1.setPriority(10); pt3.setPriority(2); //启动线程 pt1.start(); pt2.start(); pt3.start(); } }
守护线程
- 线程对象.setDaemon(true);设置为守护线程
- 线程有两类:用户线程(前台线程),守护线程(后台线程)
- 如果程序中所有前台线程都执行完毕了,后台线程会自动结束
- 垃圾回收线程属于守护线程
线程的状态(等待)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FkexbSZm-1672213397413)(D:\笔记\JAVA多线程\IMG_1289(20220331-104523)].PNG)
安全问题
-
当多个线程并发的访问临界资源(共享资源)时,如果破坏原子操作,可能造成数据不一致
-
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
-
原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省
-
package com.wly.Thread; import java.util.Arrays; public class ThreadSafe { private static int index=0; public static void main(String[] args) throws InterruptedException { //创建数组 String[] s=new String[5]; //创建两个线程 Runnable runnable=new Runnable() { @Override public void run() { s[index]="Hello"; index++; } }; Runnable runnable1=new Runnable() { @Override public void run() { s[index]="word"; index++; } }; //创建线程对象 Thread a=new Thread(runnable,"A"); Thread b=new Thread(runnable1,"B"); a.start(); b.start(); a.join(); b.join(); System.out.println(Arrays.toString(s)); } }
线程安全
同步方式(一)
-
同步代码块
-
synchronized(临界资源对象){//对临界资源对象加锁 //代码(原子操作) }
-
package com.wly.Thread; public class Ticket implements Runnable{ private int ticket=1000; //创建锁 private Object obj=new Object(); @Override public void run() { while (true){ synchronized (obj){ if (ticket<=0){ break; } System.out.println(Thread.currentThread().getName()+"卖了"+ticket+"张票"); ticket--; } } } }
-
package com.wly.Thread; public class TestTicket { public static void main(String[] args) { //创建票对象 Ticket ticket=new Ticket(); //创建线程对象 Thread w1=new Thread(ticket,"窗口1"); Thread w2=new Thread(ticket,"窗口2"); Thread w3=new Thread(ticket,"窗口3"); Thread w4=new Thread(ticket,"窗口4"); //启动线程 w1.start(); w2.start(); w3.start(); w4.start(); } }
-
每个对象都有一个互斥锁标记,用来分配给线程,只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块,线程退出代码块时,会释放相应的互斥锁标记
-
package com.wly.Thread; public class BankCard { private double money; public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
-
package com.wly.Thread; public class TestBankCard3 { public static void main(String[] args) { //创建银行卡 BankCard card=new BankCard(); //创建操作 Runnable add=new Runnable() { @Override public void run() { for (int i = 0; i <10 ; i++) { synchronized (card){ card.setMoney(card.getMoney()+1000); System.out.println(Thread.currentThread().getName() +"存了1000元,余额为:"+card.getMoney()); } } // try { // Thread.sleep(1); // } catch (InterruptedException e) { // e.printStackTrace(); // } } }; Runnable sub=new Runnable() { @Override public void run() { for (int i = 0; i <10 ; i++) { //加锁 synchronized (card){ if (card.getMoney()>=1000){ card.setMoney(card.getMoney()-1000); System.out.println(Thread.currentThread().getName()+"取了1000元,余额为:"+card.getMoney()); }else { System.out.println("余额不足"); i--; } } } } }; //创建两个线程对象 Thread xiaoli=new Thread(add); Thread xiaoming=new Thread(sub); // xiaoli.setPriority(1); // xiaoming.setPriority(10); xiaoli.start(); xiaoming.start(); } }
线程的状态(阻塞)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VdTKYMIc-1672213397414)(D:\笔记\JAVA多线程\IMG_1290.PNG)]
同步方式(二)
-
同步方法
-
synchronized 返回值类型 方法名称(形参列表){//对当前对象this加锁
//代码,原子操作
}
-
package com.wly.Thread; public class Ticket implements Runnable{ private int ticket=1000; //创建锁 private Object obj=new Object(); @Override public void run() { while (true){ if (!sale()){ break; } } } //同步方法 public synchronized boolean sale(){//静态方法锁Ticket.class,非静态,this if (ticket<=0){ return false; } System.out.println(Thread.currentThread().getName()+"卖了"+ticket+"张票"); ticket--; return true; } }
-
package com.wly.Thread; public class TestTicket { public static void main(String[] args) { //创建票对象 Ticket ticket=new Ticket(); //创建线程对象 Thread w1=new Thread(ticket,"窗口1"); Thread w2=new Thread(ticket,"窗口2"); Thread w3=new Thread(ticket,"窗口3"); Thread w4=new Thread(ticket,"窗口4"); //启动线程 w1.start(); w2.start(); w3.start(); w4.start(); } }
同步规则
-
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的标记锁
-
如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可以直接调用
-
已知JDK中线程安全的类
-
StringBuffer
-
Vector
-
Hashtable
-
以上类中的公开方法,均为synchonized修饰的同步方法
死锁问题
-
package com.wly.Thread; //创建俩个对象 public class Mylock { //创建两个锁 public static Object a=new Object(); public static Object b=new Object(); }
-
package com.wly.Thread; public class Boylock extends Thread{ @Override public void run() { synchronized (Mylock.a){ System.out.println("男孩拿到了a"); synchronized (Mylock.b){ System.out.println("男孩拿到了b-------"); System.out.println("男孩可以吃东西了"); } } } }
-
package com.wly.Thread; public class GirlLock extends Thread{ @Override public void run() { synchronized (Mylock.b){ System.out.println("女孩拿到了b"); synchronized (Mylock.a){ System.out.println("女孩拿到了a"); System.out.println("女孩可以吃东西了-------"); } } } }
-
package com.wly.Thread; public class TestDeadLock { public static void main(String[] args) throws InterruptedException { Boylock boylock=new Boylock(); GirlLock girlLock=new GirlLock(); boylock.start(); Thread.sleep(100); girlLock.start(); } }
线程通信
等待
- public final void wait()
- public final void Wait(long timeout)
- 必须在对obj加锁的同步代码中。在一个线程,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁进入等待队列
通知
- pubilc final void notify()
- public final void notifyAll()
- 唤醒等待的线程
-
生产者,消费者问题
synchronized (Mylock.a){
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃东西了-------");
}
}
}
}
```
-
package com.wly.Thread; public class TestDeadLock { public static void main(String[] args) throws InterruptedException { Boylock boylock=new Boylock(); GirlLock girlLock=new GirlLock(); boylock.start(); Thread.sleep(100); girlLock.start(); } }
线程通信
等待
- public final void wait()
- public final void Wait(long timeout)
- 必须在对obj加锁的同步代码中。在一个线程,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁进入等待队列
通知
- pubilc final void notify()
- public final void notifyAll()
- 唤醒等待的线程