目录
一:简单了解多线程
多线程是指从软件或者硬件上实现多个线程并发执行的技术,
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提高性能。
二:线程相关的概念
1,并发和并行
并行:在同一时刻,有多个指令在多个CPU上同时执行。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
2,进程和线程
(1) 进程:是正在运行的软件
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性:进程的实质是程序的一次执行过程,进程是动态的,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
(2)线程:是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程如果只有一条执行路径,则称为单线程程序。
多线程:一个进程如果有多条执行路径,则称为多线程程序。
3,小结
进程:就是操作系统中正在运行的一个应用程序
线程:就是应用程序中做的事情,比如:360软件中的杀毒,扫描木马,清理垃圾。
三:多线程的实现方式
1,继承Thread类的方式进行实现
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法
创建MyThread类的对象
启动线程
public class Demo01 {
public static void main(String[] args) {
MyThread01 mt1 = new MyThread01();
MyThread01 mt2 = new MyThread01();
mt1.start();
mt2.start();
}
}
为什么要重写run方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。
start():启动线程,让后由JVM调用此线程的run()方法
2,实现Runnable接口的方式进行实现
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
启动线程
MyRunnable mr1 = new MyRunnable();
//创建了一个线程对象,并把参数传递给这个线程
//在线程启动之后,执行的就是参数里面的run()方法
Thread th1 = new Thread(mr1);
th1.start();
3,利用Callable和Future接口方式实现
定义一个MyCallable实现Callable接口
再MyCallable类中重写call()方法
创建MyCallable类的对象
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
创建Thread类的对象,把FutureTask对象作为构造方法的参数
启动线程
再调用get方法,就可以获取线程结束之后的结果
MyCollable mc = new MyCollable();
//可以获取线程执行完毕之后的结果,也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t = new Thread(ft);
//开启线程
t.start();
//线程结束之后,可以使用get方法,获取MyCollable中call()方法中的的返回值。
String s = null;
try {
s = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(s);
4,三种方式的比较
优点 | 缺点 | |
实现Runnable、Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程先对复杂,不能直接使用Thread类中的方法 |
继承Thread类 | 编程简单,可以直接使用Thread类中的方法 | 扩展性差,不能继承其他的类 |
四:线程类的常见方式
1,Thread方法设置获取名字
获取线程的名字getName()方法:返回此线程的名称
Thread类中设置线程的名字
setName(String name):将此线程的名称跟改为等于参数name
通过构造方法也可以设置线程名称
//使用构造方法设置线程名称需要再资源类中重写有参的构造方法。
MyThread01 mt = new MyThread01("线程一");
//或者
mt.setName("线程一");
mt.start();
2,Thread方法获取线程对象
public static Thread currentThread():返回当前正在执行的线程对象的引用
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
//通过Thread.currentThread先获取线程对象,再通过getName获取线程的名称
}
}
3,Thread方法sleep
public static void sleep(long time):让线程休眠指定的时间,单位为毫秒。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {//这里需要手动抛出异常
Thread.sleep(1000);//在这里睡眠一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
4,Thread方法线程的优先级
(1),线程调度
多线程的并发运行:
计算机中的CPU,在任意时刻只能执行一条机器指令,每个线程只有获得到CPU的使用权才能执行代码,各个线程轮流获得CPU的使用权,分别执行各自的任务。
线程有两种调度模型:
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占有CPU的时间片
抢占式电镀模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取CPU时间片相对多一些
java使用的是抢占式调度模型
(2),线程优先级
public final void setPriority(int newPriority) 设置线程的优先级
public fianal int getPriority() 获取线程的优先级
(优先级范围[1-10],默认是5)
MyCollable mc1 = new MyCollable();
FutureTask<String> ft = new FutureTask<>(mc1);
Thread t1 = new Thread(ft);
t1.setPriority(10);//设置线程的优先级
int priority = t1.getPriority();//获取线程的优先级
System.out.println(priority);//打印优先级
t1.start();
MyCollable mc2 = new MyCollable();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2);
t2.setPriority(8);
int priority1 = t2.getPriority();
System.out.println(priority1);
t2.start();
5,Thread方法守护线程
后台线程/守护线程
public final void setDaemon(boolean on):设置为守护线程
MyThread01 mt1 = new MyThread01();
MyThread02 mt2 = new MyThread02();
mt1.setName("线程一");
mt2.setName("守护线程");
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了。
mt2.setDaemon(true);
mt1.start();
mt2.start();
五:线程的安全问题
在买票案例中会出现线程安全的问题
1,相同的票出现了多次
2,出现了负数的票
1,卖票案例数据安全问题的解决
(1)为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)
多线程操作共享数据
(2)如何解决多线程安全问题?
基本思想:让程序没有安全问题的环境
(3)怎么实现
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
java提供了同步代码块的方式来解决
2,同步代码块
锁语句操作共享数据,可以使用同步代码块实现
格式:
sychronized(任意对象){
多条语句操作共享数据的代码
}
(1)默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
(2)当线程执行完出来了,锁才会自动打开
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
3,第一种多线程的实现方式实现卖票案例
(1)资源类
//第一种多线程方式
public class MyThread01 extends Thread {
private static int ticketcont = 100;
private static Object obj = new Object();
//第一种多线程的书写方式,要想数据是共享的需要在数据前加上静态的修饰符(static)
@Override
public void run() {
while (true) {
synchronized (obj) {//多个线程必须使用同一把锁
//锁对象必须是唯一的,如果用this当作锁对象,第一种多线程的实现方式
//创建了两个线程对象,这两个线程对象是不相同的,所以会出现线程安全问题。
if (ticketcont == 0) {
break;
} else {
ticketcont--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + ticketcont);
}
}
}
}
}
注意:
a:如果有多条线程锁的对象一定要相同
b:想要数据是共享的需要在数据前加上静态的修饰符(static)
c:第一种创建线程的方式中的锁不能用(this)本类的对象
因为在测试类中会创建两个线程对象。
b:如果需要睡眠,Thread.sleep应该放在锁的外面
(2)测试类
//第一中多线程方式
public class Demo01 {
public static void main(String[] args) {
//因为需要创建多个线程对象,如果想让操作的数据是共享的需要在数据前加上静态修饰符(static)
MyThread01 mt1 = new MyThread01();
MyThread01 mt2 = new MyThread01();
mt1.setName("窗口一");
mt2.setName("窗口二");
mt1.start();
mt2.start();
}
}
4,第二种多线程的实现方式实现卖票案例
(1)资源类
public class MyRunnable02 implements Runnable {
private int tickedcond = 100;
@Override
public void run() {
while (true) {
//同步代码块
synchronized (this) {//多个线程必须使用同一把锁
//这里使用的是创建线程的第二种方法,这个this表示的是Runnable对象,
//第二种创建线程的方法,Runnable只会创建一次,所以,这里不会出现线程安全问题
if (tickedcond == 0) {
break;
} else {
tickedcond--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + tickedcond);
}
}
}
}
}
注意:
a:这里可以使用this作为锁,这里是创建了 MyRunnable的对象,把这个对象
作为参数传给了线程创建的对象,操作的是同一个资源类。
b:这里的数据也不用加static来实现共享,因为本来就是共享的。
c:如果需要睡眠,Thread.sleep应该放在锁的外面
(2)测试类
public class Demo {
public static void main(String[] args) {
MyRunnable02 mr = new MyRunnable02();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
5,第三种多线程的实现方式实现卖票案例
(1)资源类
//第三种多线程方式
public class MyCallable implements Callable<String> {
private int ticketcont = 100;
@Override
public String call(){
while (true) {
synchronized (this) {//多个线程必须使用同一把锁
if (ticketcont == 0) {
break;
} else {
ticketcont--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + ticketcont);
}
}
}
return "执行完成";
}
}
(2)测试类
//第三种多线程方式
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*1,创建MyCallable类的对象
2,创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
3,创建Thread类的对象,把FutureTask对象作为构造方法的参数
4,启动线程
5,再调用get方法,就可以获取线程结束之后的结果*/
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.setName("窗口一");
t1.start();
//获取线程最后的返回值
String s = ft.get();
System.out.println(s);
}
}
注意:
a:需要创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
也可以通过其中的get()方法获取返回值
b:这里的锁对象也可以使用this,因为只创建了一个MyCallable对象,所以锁对象相同
c: 如果需要睡眠,Thread.sleep应该放在锁的外面
6,同步方法
同步方法:就是把synchronized关键字加到方法上
格式:
修饰符synchronized返回值类型 方法名(方法参数){}
同步代码块和同步方法的区别:
同步代码块可以锁住指定的代码,同步方法是锁住方法种的所有代码
同步代码块可以指定锁对象,同步方法不能指定锁对象
同步方法的锁对象是什么?
this
7,同步方法和第二种实现线程的方式实现卖票案例
(1)资源类
public class MyRunnable01 implements Runnable {
private int tickedcond = 100;
@Override
public void run() {
while (true) {
if ("窗口一".equals(Thread.currentThread().getName())) {
//同步方法
boolean result = synchronizedmethod();
if(result){
break;
}
}
if ("窗口二".equals(Thread.currentThread().getName())) {
//同步代码块
synchronized (this) {//多个线程必须使用同一把锁
//这里使用的是创建线程的第二种方法,这个this表示的是Runnable对象,
//第二种创建线程的方法,Runnable只会创建一次,所以,这里不会出现线程安全问题
if (tickedcond == 0) {
break;
} else {
tickedcond--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + tickedcond);
}
}
}
}
}
private synchronized boolean synchronizedmethod() {
if (tickedcond == 0) {
return true;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickedcond--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + tickedcond);
return false;
}
}
}
注意:
a:如果资源类种有同步方法和同步代码块,那么同步代码块的锁必须是this,因为
同步方法的锁是this,要保证两种方法的相同。
(2)测试类
public class Demo {
public static void main(String[] args) {
MyRunnable02 mr = new MyRunnable02();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
8,lock锁
Lock实现提供比使用synchronized方法和语句可以获得跟广泛的锁操作
Lock中提供了获得锁和释放锁的方法
1,void lock():获得锁
2,void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
(1)资源类
public class MyRunnable implements Runnable {
private int tickedcond = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//lock锁,unlock()方法一般放在finally里面,
//这样即使线程中间报错,也会执行关锁这条语句
if (tickedcond == 0) {
break;
} else {
Thread.sleep(100);
tickedcond--;
System.out.println(Thread.currentThread().getName() + "在买票,还剩下" + tickedcond);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
注意:
a: 使用Lock锁需要先创建ReentrantLock的对象
b:一般要把unlock()方法放在异常抛出的finally里面,这样即使程序终止也会执行
(2)测试类
public class Demo {
public static void main(String[] args) {
MyRunnable01 mr = new MyRunnable01();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
六:死锁
死锁:
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前进执行。
死锁现象:
public class Demo01 {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while(true){
synchronized (objA){
//线程一
synchronized (objB){
System.out.println("线程一");
}
}
}
}).start();
new Thread(()->{
while(true){
synchronized (objB){
//线程二
synchronized (objA){
System.out.println("线程二");
}
}
}
}).start();
}
}
七:生产者消费者
1,消费者步骤
a:判断桌子上是否有汉堡包
b:如果没有就等待
c:如果有就开吃
d:吃完之后,桌子上的汉堡包就没有了,叫醒等待的生产者继续生产
2,生产者步骤
a:判断桌子上是否有汉堡包,如果有就等待,如果没有才生产
b:把汉堡包放在桌子上
c:叫醒等待的消费者开吃
3,等待和唤醒的方法
为了体现生产和消费过程中的等待和唤醒,java就提供了几种方法供我们使用,者几个方法在Object类种:
方法名 | 说明 |
void wait() | 导致当前线程等待,知道另一个线程调用该对象的notify()方法或者notifyAll()方法 |
void notify() | 唤醒正在等待对象监听的单个线程,如果有多个等待的线程,那么随机唤醒一个。 |
void notifyAll() | 唤醒正在等待对象监听的所有线程 |
(1)厨师类
public class Cooker extends Thread {
/*1,判断桌子上有没有汉堡包
* 2,如果有就等待吃货吃,如果没有就生产
* 3,把生产的汉堡包放在桌子上
* 4,叫醒吃货吃汉堡包*/
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
if (!Desk.flag) {
//生产
System.out.println("厨师正在生产汉堡包");
Desk.flag = true;
Desk.lock.notifyAll();
} else {
//等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
(2)吃货类
public class Foodie extends Thread {
/*1,判断桌子上有没有汉堡包
* 2,如果没有就等待
* 3,如果有就吃
* 4,吃完之后,桌子上就没有了,
* 叫醒生产者,开始生产汉堡包
* 5,吃汉堡包的数量减一*/
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if (Desk.flag){
//有
System.out.println("吃货吃了一个汉堡包");
Desk.flag = false;
Desk.lock.notifyAll();
Desk.count--;
}else{
//没有,等待
//使用什么对象当做锁,那么就必须使用这个对象去调用等待和唤醒方法。
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
(3)桌子类(定义标记锁对象的类)
public class Desk {
//定义一个标记
//true就表示桌子上有汉堡包,吃货执行
//false就表示桌子上没有汉堡包,厨师执行
public static boolean flag = false;
//汉堡包饿总数量
public static int count = 10;
//定义锁对象
public static final Object lock = new Object();
}
(4)测试类
public class Demo {
public static void main(String[] args) {
/*套路
* 1,while(true)死循环
* 2,sychronized锁,锁对象要唯一
* 3,判断共享数据是否结束,结束
* 4,判断共享数据是否结束,没有结束
* 5,*/
Foodie fd = new Foodie();
Cooker c = new Cooker();
fd.start();
c.start();
}
}
4,阻塞队列
常见的BlockingQueue:
ArryBlockingQueue:底层是数组,有界。
LinkedBlockingQueue:底层的hi链表,无界,但不是真正的无界,最大为int的最大值
public class Basic01 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String > abq = new ArrayBlockingQueue<>(1);
//创建阻塞队列的对象,参数是队列种可以放多少个元素
//放元素
abq.put("汉堡包");
//取出数据
System.out.println(abq.take());
//如果取的数据超过存的数据,那么程序就不会停止。
System.out.println(abq.take());
}
}