1.多线程基础概念
多线程:让程序同时做多件事情
多线程作用:提高效率
并发:在同一时间,有多个指令在单个cpu上交替执行
并行:在同一时刻,有多个指令在多个cpu上同时执行
2.多线程的实现
(1)继承Thread类来实现多线程
//第一步:写一个类mythread继承Thread类 //第二步:重写run方法,run方法里面就是线程要执行的代码 //第三步:在测试类中创建mythread类的对象th,并用th.start();来启动线程
注意是调用start方法而不是run方法
Thread中的方法:
setName方法是设置线程名字
getName方法是获取线程名字
注意看
System.out.println(getName()+"helloworld");
这个语句直接使用了getName,是因为mythread1类是Thread的子类,子类可以直接调用父类的方法
package multithreading;
//第一步:写一个类mythread继承Thread类
//第二步:重写run方法,run方法里面就是线程要执行的代码
//第三步:在测试类中创建mythread类的对象th,并用th.start();来启动线程
public class mythread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+"helloworld");}}
}
package multithreading;public class threaddemo {public static void main(String[] args) {var a = new mythread1();//为该线程起名a.setName("线程1");var b = new mythread1();b.setName("线程2");a.start();b.start();}
}
运行结果可以看到线程1和线程2交替执行
(2)定义一个类去实现Runnable接口来实现多线程
//第一步:定义一个类去实现Runnable接口 //第二步:重写里面的Run方法 //第三步:在测试类里创建实现类的对象(也被称作任务对象) //第四步:创建thread类的对象,形参就是你希望执行的线程的对象,即任务对象,再调用start()方法
package multithreading;
//第一步:定义一个类去实现Runnable接口
//第二步:重写里面的Run方法
//第三步:创建实现类的对象(也被称作任务对象)
//第四步:创建thread类的对象,形参就是你希望执行的线程的对象,即任务对象,再调用start()方法
public class MyRun implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+"HelloWorld");}}
}
package multithreading;public class threaddemo1 {public static void main(String[] args) {MyRun m = new MyRun();Thread t1 = new Thread(m);t1.setName("线程1");Thread t2 = new Thread(m);t2.setName("线程2");t1.start();t2.start();}
}
System.out.println(Thread.currentThread().getName()+"HelloWorld");
这个语句中的Thread.currentThread()是获取当前线程的对象
(3)利用Callable接口和FutureTask接口来实现多线程
//第三种方式可以获取多线程运行的结果//第一步:创建一个类MyCallable去实现Callable接口 //第二步:重写call(这个方法是有返回值的,返回多线程运行得到的结果) //第三步:在测试类中,创建MyCallable类的对象(任务对象,表示多线程要执行的任务) //第四步:创建FutureTask的对象(用来管理多线程运行的结果) //第五步:创建Thread类的对象,并启动(表示线程)
package multithreading;
//第三种方式可以获取多线程运行的结果//第一步:创建一个类MyCallable去实现Callable接口
//第二步:重写call(这个方法是有返回值的,返回多线程运行得到的结果)
//第三步:在测试类中,创建MyCallable类的对象(任务对象,表示多线程要执行的任务)
//第四步:创建FutureTask的对象(用来管理多线程运行的结果)
//第五步:创建Thread类的对象,并启动(表示线程)import java.util.concurrent.Callable;public class MyCallable implements Callable<Integer> {//泛型和线程运行结果要一致@Overridepublic Integer call() throws Exception {int sum=0;for (int i = 1; i <= 100; i++) {sum+=i;}return sum;}
}
package multithreading;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class threaddemo2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//mc是线程对象,ft是用来管理传入的线程的结果的,t是用来启动线程的var mc = new MyCallable();var ft = new FutureTask<Integer>(mc);var t =new Thread(ft);t.start();System.out.println(ft.get());}
}
三种实现方式的优缺点
多线程中常用成员方法
设置优先级和获取优先级
优先级1~10,不设置就是默认的5,优先级越高,该线程抢占成功的概率就越大
守护线程:当非守护线程运行结束时,守护线程也就没有执行的必要了,会慢慢结束
礼让线程:让线程尽可能抢占均匀
插入线程:t.join(); 把t线程插入到当前线程之前,当插入的线程结束后再执行之前那个线程
线程的生命周期
线程安全问题--同步代码块
当多个线程同时运行时,出现的不合理情况
if (ticket <= 0) {// 卖完了break;
} else {ticket--;System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张");
}
窗口三在卖票,还剩下98张
窗口一在卖票,还剩下98张
窗口一在卖票,还剩下96张
窗口三在卖票,还剩下95张
窗口二在卖票,还剩下95张
这是出现了多个窗口同时卖同一张票的情况,因为多个线程同时运行到了某条语句
窗口一在卖票,还剩下0张
窗口三在卖票,还剩下-1张
这是出现负数票,因为运行过程中出现了脏数据 ,线程三运行到 判断票数时,票数还大于0,可当运行到 ticket--;时,其他线程在这之前已经 ticket--了,导致了出现负数
同步代码块
语法:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
当有多个线程时,这个(任意对象)必须是静态唯一的,就是说是唯一的锁,不然每个线程用各自的锁不就相当于没锁了吗
只要有一个线程进入该代码块,该块就被锁住,其他线程无法进入,当线程执行完毕出来,就自动解锁,
同步方法
同步方法不需要指定锁对象,而且同步方法可以锁住方法中所有代码,故同时只能有一个线程运行同步方法中的代码。
修饰符 synchronized 返回值类型 方法名(方法参数) {}
Lock锁
唤醒机制,生产者和消费者
Cook类
package lock;public class Cook extends Thread{//表示Cook能创建线程对象@Overridepublic void run() {//1.写循环//2.写同步代码块//3.判断共享数据是否到达末尾(到达末尾)//4.判断共享数据是否到达末尾(没有到达末尾,就执行while(true)synchronized (Desk.lock){if(Desk.count==0){//总次数为0表示吃货已经吃饱break;}else{//还有次数if(Desk.foodFlag==1) {//如果桌子上有面条,厨师等吃货吃完这碗就好了try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else{//桌子上没有面条,厨师就做面条System.out.println("厨师开始做面条");//做完就要修改桌子数据,0改成1Desk.foodFlag =1;//唤醒吃货来吃Desk.lock.notifyAll();}}}}
}
Foodie类
package lock;public class Foodie extends Thread{@Overridepublic void run() {//1.写循环//2.写同步代码块//3.判断共享数据是否到达末尾(到达末尾)//4.判断共享数据是否到达末尾(没有到达末尾,就执行核心逻辑)while(true)synchronized (Desk.lock){if(Desk.count==0)break;else{//先判断桌子上是否有面条//没有就等//有就吃//吃完之后唤醒厨师线程//总次数减1//修改桌子状态,就是本来桌子上有面条,是1,吃完就没了,就要修改桌子状态为0if(Desk.foodFlag==0){//桌子上没有面条//没有就等待try {Desk.lock.wait();//等待的方法要用锁对象去调用} catch (InterruptedException e) {throw new RuntimeException(e);}}else{//表示桌子上有面条//有就开吃//能吃的面条湾数减一Desk.count--;System.out.println("吃货在吃面条,还能再吃"+Desk.count +"碗");//吃完就唤醒厨师Desk.lock.notifyAll() ;//表示唤醒绑定该锁对象的所以线程//修改桌子状态Desk.foodFlag =0;}}}}
}
Desk类
package lock;public class Desk {//该类控制生产者和消费者的执行//0:没食物,1:有食物public static int foodFlag =0;//总个数public static int count =10;//锁对象//只是名字是锁,用来做 synchronized ()的锁对象public static Object lock = new Object();}
测试类
package lock;public class ThreadDemo {public static void main(String[] args) {//完成生产者和消费者的代码//实现线程轮流交替执行的效果//创建线程的对象Cook c = new Cook();Foodie f = new Foodie();//给线程设置名字c.setName("厨师");f.setName("吃货");//开启线程c.start();f.start();}}
运行结果
唤醒机制:阻塞队列
线程状态
线程池
自定义线程池