多线程
观看狂神课程,所作笔记
线程概念
- 线程就是独立的执行路径。
- 在程序运行时,即使自己没有创建线程,后台也会存在线程,比如:主线程、gc线程。
- main()称之为主线程,为系统的入口,用于执行程序。
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。(一个CPU其实是顺序执行)
- 线程会带来额外的开销,如CPU的调度时间
- 每一个线程在自己的工作内存交互,内存控制不会造成数据不一致。
线程实现(借助JAVA帮助文档)
继承Thread类
//java 帮助文档的例子 将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。 然后可以分配并启动子类的实例。
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// 实现的方法
. . .
}
}
//开启线程
PrimeThread p = new PrimeThread(143);
p.start();
public class Demo01 extends Thread {
public void run () {
for (int i =0;i<5;i++) {
System.out.println("我在-----------------");
}
}
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
demo01.start();
for (int i =0;i<200;i++) {
System.out.println("我不在-----------------");
}
}
}
线程1:主线程
线程2:开启的线程
总结:线程开启不一定立即执行。
实现Runnable 接口
//java帮助文档例子 创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
public class Demo01 implements Runnable {
public void run () {
for (int i =0;i<5;i++) {
System.out.println("我在-----------------");
}
}
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
new Thread(demo01).start();
for (int i =0;i<200;i++) {
System.out.println("我不在-----------------");
}
}
}
(推荐使用Runnable,因为java单继承多实现)
//抢票问题
public class TrainTicket implements Runnable{
String trainNum;
Date driverTime;
int ticketsLeftNum = 10;
public TrainTicket(Date driverTime, String ticketsLeftNum) {
this.driverTime = driverTime;
this.trainNum = trainNum;
}
@Override
public void run() {
while (ticketsLeftNum >= 0) {
System.out.println(Thread.currentThread().getName()+"抢到了"+ticketsLeftNum--+"票");
}
}
public static void main(String[] args) {
TrainTicket ta = new TrainTicket(new Date(),"天津-山西");
new Thread(ta,"a").start();
new Thread(ta,"b").start();
new Thread(ta,"c").start();
}
/*输出结果
c抢到了10票
b抢到了9票
b抢到了7票
b抢到了6票
b抢到了5票
b抢到了4票
b抢到了3票
a抢到了10票
a抢到了1票
a抢到了0票
b抢到了2票
c抢到了8票*/
}
实现Callable接口
package javaBasic.test;
import java.util.Date;
import java.util.concurrent.*;
public class TrainTicket implements Callable<String> {
String trainNum;
Date driverTime;
int ticketsLeftNum = 10;
public TrainTicket(Date driverTime, String trainNum) {
this.driverTime = driverTime;
this.trainNum = trainNum;
}
@Override
public String call() throws Exception {
return trainNum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TrainTicket ta = new TrainTicket(new Date(),"天津-山西");
TrainTicket ta1 = new TrainTicket(new Date(),"天津-山东");
TrainTicket ta2 = new TrainTicket(new Date(),"天津-河北");
//创建执行服务(获取线程)
ExecutorService ser = Executors.newFixedThreadPool(10);
//提交执行(提交执行run)
Future<String> r1= ser.submit(ta);
Future<String> r2= ser.submit(ta1);
Future<String> r3= ser.submit(ta2);
//获取结果 (获取run 的返回结果)
String trainNum01 = r1.get();
String trainNum02 = r2.get();
String trainNum03 = r3.get();
System.out.println(trainNum01);
System.out.println(trainNum03);
System.out.println(trainNum02);
//关闭服务
ser.shutdown();
}
}
静态代理
要求 :
-
真实对象和代理对象都要实现一个接口
-
代理对象要代替真实角色
好处:
-
代理对象可以做很多真实对象做不了的事情
-
真实对象专注做自己的事情
package javaBasic.test;
public class StaticProxy {
public static void main(String[] args) {
Student student = new Student();
Parent parent = new Parent(student);
parent.goExam();
// new Parent(new Student()).goExam();
//new Tread(对象).start(); 对比可知Tread其实就是代理了Runnable
}
}
interface Exam {
public void goExam();
}
class Student implements Exam {
@Override
public void goExam() {
System.out.println("学生参加考试");
}
}
class Parent implements Exam {
private Student student;
public Parent(Student student) {
this.student = student;
}
@Override
public void goExam() {
before();
student.goExam();
after();
}
public void after() {
System.out.println("父母等成绩++++++");
}
public void before() {
System.out.println("给孩子报名++++++");
}
}
//控制台输出结果:
//给孩子报名++++++
//学生参加考试
//父母等成绩++++++
Lamda 表达式
函数式接口
定义:
- 任何接口,如果只包含唯一一个抽象方法,那么他就是函数式接口
public interface Runnable {
public abstract void run();
}
- 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
lamdba推导过程
-
正常调用Tv的play方法需要
-
为了方便变成静态内部类
-
局部内部类
-
匿名内部类
-
lamdba
package javaBasic.test; public class TestLamdba { /* //2.为了方便变成静态内部类 static class TV implements Player { @Override public void play() { System.out.println("电视可以播放电影"); } }*/ //1.正常调用Tv的play方法需要 public static void main(String[] args) { /* //3.局部内部类 class TV implements Player { @Override public void play() { System.out.println("电视可以播放电影"); } }*/ // Player player = new TV(); /* //4匿名内部类 Player player = new Player(){ @Override public void play() { System.out.println("电视可以播放电影"); } }; player.play();*/ //5.lamdba /* Player player = (int a) -> { System.out.println(a+"电视可以播放电影"); }; //简化 player = (a) -> { System.out.println(a+"电视可以播放电影"); }; player = a -> { System.out.println(a+"电视可以播放电影"); };*/ Player player = a -> System.out.println(a+"电视可以播放电影"); player.play(1); } } interface Player { public void play (int a); } /* 对应1 class TV implements Player{ @Override public void play(int a) { System.out.println(a+"电视可以播放电影"); } }*/
线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BYevLCIn-1645697502988)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220222144011372.png)]
线程停止
-
不推荐JDK里面的stop() 和 destroy()(已经废气)
-
推荐要线程自己停下来
-
建议使用一个标志位进行中止变量。当flag=false,则中止线程运行。
package javaBasic.test; import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput; import org.w3c.dom.ls.LSOutput; /** * 1.建议线程正常停止 ---> 利用次数,不建议死循环 * 2.建议使用标示位--》设置一个标识位 * 3.不要使用stop或者destroy */ public class TestStop implements Runnable { int i = 0; private boolean flag = true; @Override public void run() { while (flag) { System.out.println(i); if (i == 20) { stop(); } i++; } } public void stop() { flag = false; System.out.println("线程停止了"); } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); } }
线程休眠
-
sleep 指定当前线程阻塞的毫秒数
-
sleep存在异常InterruptedException
-
sleep时间达到后线程进入就绪状态
-
sleep可以模拟网络延时,倒计时等
-
每一个对象都有一个锁,sleep不会释放锁
package javaBasic.test; public class TestSleep2 { //模拟倒计时 public void tenDown () throws InterruptedException { int num = 10; while (true) { Thread.sleep(1000); System.out.println(num); num --; if (num == 0) { break; } } } public static void main(String[] args) throws InterruptedException { TestSleep2 a = new TestSleep2(); a.tenDown(); } }
线程礼让
-
礼让线程,让当前正在执行的线程暂停,但不阻塞
-
将线程从运行状态转为就绪状态
-
让cpu重新调度,礼让不一定成功,看CPU心情
package javaBasic.test; public class TestYield { public static void main(String[] args) { MyYield myYield = new MyYield(); new Thread(myYield,"a").start(); new Thread(myYield,"b").start(); } } class MyYield implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程执行"); Thread.yield(); //线程礼让 System.out.println(Thread.currentThread().getName()+"线程停止执行"); } }
JOIN
-
join 合并线程,待此线程执行完成之后,在执行其他线程,其他线程阻塞。
-
可以想象成插队
package javaBasic.test;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("插队vip"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动的线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 1000; i++) {
if (i == 200) {
thread.join();//启动线程插队,走完之后,才会执行主线程
}
System.out.println("游客"+ i);
}
}
}
线程的优先级
- 先设置优先级,在启动
- 优先级用数字表示,范围0-10
- getPriority().setPriority(int xxx)
- 优先级低只是意味着获取调度的概率低,并不意味着优先级低就不会被调用了,这得看cpu的调度
守护线程(daemon)
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 后台记录操作日志,监控内存,垃圾回收等待。。。
package javaBasic.test;
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
Dog dog = new Dog();
Thread thread = new Thread(god);
thread.setDaemon(true); // 开启守护线程 ,默认是false 用户线程
thread.start(); //启动守护线程
new Thread(dog).start();
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝一直存活");
}
}
}
class Dog implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3600; i++) {
System.out.println("开心的叫着");
}
System.out.println("不能再吃最喜欢的骨头了!!!");
}
}
线程同步
并发
- 定义:同一个对象被多个线程同时操作
- 处理:线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列。
- 队列:排队等待
- 锁:安全(每个对象都有一个锁)synchronized
- 弊端:一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。(小便和蹲坑所用时间)
package javaBasic.test;
/**
*不安全的实例
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"张三").start();
new Thread(station,"李四").start();
new Thread(station,"王五").start();
}
}
class BuyTicket implements Runnable {
private int ticketNum = 10;
private boolean flag = true;
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException {
if (ticketNum <= 0) {
flag = false;
return;
}
Thread.sleep(100); //可以方法代码问题的发生性
System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNum+"张车票");
ticketNum--;
}
}
//理解:线程不安全的原因可能是,在同一个时间,操作了相同的内容 比如:两人取钱,同时看到的都是100,然后对100进行了操作 每一个线程在自己的工作内存交互,彼此之间互不影响
并发安全(synchronized)
- 通过private 关键字来保证数据对象只能被方法访问
- 通过synchronized方法和synchronized块来保证方法的安全性
- synchronized方法控制对象访问,每个对象都有一把锁,synchronized方法必须获得调用该方法的对象的锁才能执行。直到方法释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
public synchronized void buy() throws InterruptedException {
if (ticketNum <= 0) {
flag = false;
return;
}
Thread.sleep(200); //可以方法代码问题的发生性
System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNum+"张车票");
ticketNum--;
}
//这样就安全了 这个叫做锁方法
-
同步块:synchronized (obj){}
-
Object 称之为同步监视器
Object 可以是任何对象,但推荐使用共享资源做为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this。就是这个对象本身,或者class
-
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中的代码
2.第二个线程访问,发现同步监视器被锁定,无法访问
3.第一个线程访问完毕,解锁同步监视器
4.第二个线程访问没发现同步监视器没有锁,然后锁定并访问
package javaBasic.test;
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(10000,"结婚基金");
Drawing you = new Drawing(account,30,"你");
Drawing yourWalf = new Drawing(account,30,"你老婆");
new Thread(you).start();
new Thread(yourWalf).start();
}
}
class Account{
int money; //余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing implements Runnable{
private Account account;
private int drawingMoney;
private String name;
private int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
this.account = account;
this.drawingMoney = drawingMoney;
this.name = name;
}
@Override
public void run() {
//这里就不能用this了,因为锁银行没有用,我们应该锁变量
synchronized (account) {
if (account.money-drawingMoney<0) {
System.out.println(Thread.currentThread().getName()+"钱不够了,取不出来了!!!!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡里余额 你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(name + "取出"+drawingMoney+"。卡里余额"+account.money+"手里余额"+nowMoney);
}
}
}
// synchronized (account) 加了代码块之后也变安全了
死锁
- 定义:多线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行。
- 情景:某一个同步代码块,同时拥有两个以上对象的锁,就可能发生死锁问题。
package javaBasic.test;
public class DeadLockTest {
public static void main(String[] args) {
MakeUp makeUp = new MakeUp(0,"灰姑娘");
MakeUp makeUp1 = new MakeUp(1,"白雪公主");
makeUp.start();
makeUp1.start();
}
}
class Lipstick {
}
class Mirror {
}
class MakeUp extends Thread {
//需要的资源只有一根,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;
public MakeUp(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
}
//输出结果:
//灰姑娘获得口红的锁
//白雪公主获得镜子的锁 程序锁住了
//解决方法 只要不是同一个代码块里面有俩个锁就行了
@Override
public void run() {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
}
} else {
synchronized (mirror) {
System.out.println(this.girlName + "获得镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick) {
System.out.println(this.girlName + "获得口红的锁");
}
}
}
死锁的避免方法
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程获得的资源,在未使用完之前,不能前行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
Lock(锁:可重入锁)
- 一般使用它的实现类:ReentrantLock.他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用,可以显示的加锁、释放锁。
package javaBasic.test;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
TestLock2 t = new TestLock2();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestLock2 implements Runnable {
int tickNum = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock(); //加锁
if (tickNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tickNum--);
} else {
break;
}
} finally {
lock.unlock(); //解锁
}
}
}
}
线程通信问题
线程协作(生产者消费者问题)
- 生产者消费者模式
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
线程通信
- java 提供了几个方法解决线程之间的通信问题 (一下均为OBJECT 类方法)
- wait() :表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
- notify() : 唤醒一个处于等待状态的线程
- notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
管程法(通过缓冲区解决问题)
package javaBasic.test;
public class TestPc {
public static void main(String[] args) {
Container container = new Container();
new Product(container).start();
new Consumer(container).start();
}
}
//生产者
class Product extends Thread {
Container container;
public Product(Container container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "只鸡");
try {
container.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer extends Thread {
Container container;
public Consumer(Container container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println("消费了第" + container.pop().num + "只鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//容器
class Container {
Chicken[] chickens = new Chicken[10];
int count = 0;
public synchronized void push(Chicken chicken) throws InterruptedException {
//鸡满了等待消费者消费
if (count == chickens.length) {
this.wait();
}
chickens[count] = chicken;
count++;
//有鸡了,通知消费者消费,开始吃鸡
this.notifyAll();
}
public synchronized Chicken pop() throws InterruptedException {
if (count == 0) {
//等待生产者生产
this.wait();
}
//可以消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
class Chicken {
int num;
public Chicken(int num) {
this.num = num;
}
}
信号灯法(通过标示位解决问题)
- 同上面就多了个标示位,count== length 换成 flag
线程池
- ExecutorService : 整整的线程池接口,常见子类ThreadPoolExecutor
- void execute (Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task) :执行任务,有返回值,一般又来执行Callable
- void shutdown(): 关闭连接池
- Executors :工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package javaBasic.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}