多线程(非常重要)
一、多线程概述
1.什么是多线程?
线程就是执行一条程序的步骤,多线程就是执行多个程序的步骤
申明:文章通过学习撰写的笔记,里面图片资源来源黑马阿伟教程
2.多线程的作用
提高效率
3.多线程的应用场景?
只要想让软件同时完成几个步骤,就必须用到多线程
比如:软件中的耗时操作,所有的聊天软件,所有的服务器
二、并发和并行
1.并发:同一时刻,有多个指令在单个CPU上交替执行
2.并行:同一时刻,有多个指令在多个CPU上同时执行
三、多线程的三种实现方式
1.继承Thread类的方式进行实现
package itheima.thread;
public class Test03 {
public static void main(String[] args) {
//实例化多线程类
Thread t = new MyThread();
t.start();//只能是调用start方法来执行多线程
for (int i = 0; i < 10; i++) {
System.out.println("Main多线程执行"+i);
}
}
}
/**
*创建多线程类
*/
class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println("Mythread多线程执行"+i);
}
}
}
2.实现Runnable接口的方式进行实现
package itheima.thread;
public class Test01 {
public static void main(String[] args) {
//匿名内部类+Runnable实现
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("多线程Run:"+i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("多线程Main:"+i);
}
}
}
3.利用Callable接口和Future接口方式实现(可获取返回值)
//创建MyCallable的对象(表示多线程要执行的任务)
//创建FutureTask的对象(作用管理多线程运行的结果)
//创建Thread的对象(表示线程)
package itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test04 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();//多线程要执行的任务对象
FutureTask<Integer> ft = new FutureTask<>(mc);//管理多线程任务执行的结果对象
//创建多线程的对象
Thread t = new Thread(ft);
t.setName("线程t");
t.start();
//多线程执行的任务的结果
System.out.println(t.getName()+"线程的结果:"+ft.get());
}
}
/**
* 创建将要执行的线程任务
*/
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1;i <= 100;i++){
sum = sum + i;
}
return sum;
}
}
四、常用的成员方法
1.常用的方法
方法名称 | 说明 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 设置当前线程休眠的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插队线程/插入线程 |
2.代码实现
/*
--String getName() 返回此线程的名称
--void setName(String name) 设置线程的名字(构造方法也可以)
细节:
1、如果外面没有给线程设置名字,线程也是有默认名字的。
格式:Thread-x(x是从0开始的序号)
2、如果外面要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
--static Thread currentThread() 获取当前线程的对象
细节:
当JVM虚拟机启后,会自动启动多条线程
其中有一条就是main线程
他的作用方法就是去调用main方法,并执行里面的代码
在以前,我们写的所有代码,都是运行在main线程中的
--static void sleep(long time) 让线程按照指定时间休眠,单位为毫秒
细节:
1.哪条线程执行到了这个方法,哪么就会休眠对应的时间
2.1s = 1000ms
3.时间到了后会自动醒来继续执行*/
public class Test04 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();//多线程要执行的任务对象
FutureTask<Integer> ft = new FutureTask<>(mc);//管理多线程任务执行的结果对象
//创建多线程的对象
Thread t = new Thread(ft);
//t.setName("线程t");注释设置名字,获取的是默认的名字Thread-0
//设置后就按照设置的来。java是抢占式调度,优先级越高,先执行完的概率就越大
//不设置的话,默认优先级就是5
t.setPriority(3);
t.start();//启动线程
System.out.println("哈喽");
Thread.sleep(1000);//让当前线程按照指定毫秒进行休眠
//多线程执行的任务的结果
System.out.println(t.getName()+"线程的结果:"+ft.get()+", 优先级:"+t.getPriority());
}
}
/**
* 创建将要执行的线程任务
*/
class MyCallable implements Callable<Integer> {
//
@Override
public Integer call() throws Exception {
Thread t = Thread.currentThread();
System.out.println("线程的名字:"+t.getName());
int sum = 0;
for (int i = 1;i <= 100;i++){
sum = sum + i;
}
return sum;
}
}
/**
*练习守护线程
*/
public class Test05 {
public static void main(String[] args) {
//设置备胎线程
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
t2.setName("--女神线程--");
t1.setName("备胎线程");
t1.setDaemon(true);//备胎线程设置成功
t1.start();//备胎线程的优先级低于其他非备胎线程
t2.start();//
}
}
//
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"运行第"+i+"次");
}
}
}
//
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"运行第"+i+"次");
}
}
}
五、线程的生命周期
六、线程锁
1.synchronized//同步代码块
package itheima.thread;
//多线程的安全问题
public class Test06 {
public static void main(String[] args) {
Thread t1 = new MyThread0();
Thread t2 = new MyThread0();
Thread t3 = new MyThread0();
t1.setName("售票台1");
t2.setName("售票台2");
t3.setName("售票台3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread0 extends Thread {
private static int ticket = 1;//票
private static Object o = new Object();//不重复的,唯一的
//为什么要加线程锁
//因为三个线程可能同时抢到cpu的执行权,执行这个打印而不执行ticket++
//这样就会出现三个售票台出售一张票的情况
@Override
public void run() {
while (true) {
synchronized (o) {//门锁住了,就只能等抢到的线程,cpu分配给他的时间结束再去抢
if (ticket <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}//因为父类run方法没有抛出异常,所以重写的run方法是不能抛出异常的
System.out.println(getName() + "正在卖第" + ticket + "张票");
ticket++;
} else {
break;
}
}
}
}
}
2.同步方法写法
package itheima.synchronized1;
public class Test {
public static void main(String[] args) {
MyRun r1 = new MyRun();
Thread t1 = new Thread(r1);//创建售票台1
Thread t2 = new Thread(r1);//创建售票台2
Thread t3 = new Thread(r1);//创建售票台3
t1.setName("售票台1");
t2.setName("售票台2");
t3.setName("售票台3");
t1.start();
t2.start();
t3.start();
}
}
/**
* 创建任务类
*/
class MyRun implements Runnable {
private static int ticket = 0;//设置票数,三个售票台都共享
@Override
public void run() {
while (true) {
if (method()) break;
}
}
//同步方法锁住线程
public synchronized boolean method() {
if (ticket == 100) {
return true;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
}
return false;
}
}
3.lock锁
package itheima.synchronized1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//多线程的安全问题
public class Test01 {
public static void main(String[] args) {
Thread t1 = new MyThread0();
Thread t2 = new MyThread0();
Thread t3 = new MyThread0();
t1.setName("售票台1");
t2.setName("售票台2");
t3.setName("售票台3");
t1.start();
t2.start();
t3.start();
}
}
class MyThread0 extends Thread {
private static int ticket = 1;//票
//lock接口不能实例化,这里用的他实现类new对象
//锁也是被我们三个线程所共享的所以要加上静态
//如果没有静态,就没有把线程同步加锁,就会出现数据不安全的情况(数据重复啊,导致数据无效)
static Lock lock = new ReentrantLock();
//为什么要加线程锁
//因为三个线程可能同时抢到cpu的执行权,执行这个打印而不执行ticket++
//这样就会出现三个售票台出售一张票的情况
@Override
public void run() {
while (true) {
//如果不加try finally那么锁就不会打开,导致程序还会一直运行中
//因为到判断成功后跳出循环了,却漏掉unlock开锁的方法
//这就导致,其他线程,比如线程2和线程3都还在门外等着,所以程序不会停止
lock.lock();
try {
if (ticket <= 100) {
//因为父类run方法没有抛出异常,所以重写的run方法是不能抛出异常的
Thread.sleep(10);
System.out.println(getName() + "正在卖第" + ticket + "张票");
ticket++;
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
4.扩展:死锁
情况就是两把锁,必须同时拿到两把锁才能释放锁,但是A和B同时拿了一把锁,都在等着对方将锁给放下来,然后拿到锁。一直保存就会造成死锁。
5.扩展StringBuilder与StringBuffer的区别
StringBuilder:适用于单线程,因为他的方法没有加锁,所以线程不安全(单线程就不用担心不安全),但是效率更快
StringBuffer:适用于多线程,因为他的方法加了同步锁,所以线程安全(就是数据不会出现异常),但是相对效率慢一点
七、生产消费模式
1.思路
生产者:厨师
--判断桌子有没有食物
--有:等待 没有:生产食物
--生产后,唤醒消费者开始消费
消费者:食客
--判断桌子有没有食物
--如果没有就等待
--如果有就开吃
--吃完后就唤醒厨师
2.常见方法
方法名称 | 说明 |
---|---|
void wait() | More Actions当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
3.代码实现
package itheima.threadmode;
public class ModeTest {
public static void main(String[] args) {
Cook c = new Cook();
Foodie f = new Foodie();
c.setName("厨师");
f.setName("食客");
c.start();
f.start();
}
}
/**
* 厨师
*/
class Cook extends Thread {
/*
* 步骤:
* --循环
* --同步线程锁
* --判断共享数据到了末尾的情况(就是吃够10碗)
* --判断共享数据还没到末尾的情况(就是没吃够,还想要继续)
*
*/
@Override
public void run() {
while (true) {
synchronized (Desk.lock){
//判断共享数据到末尾了没(这里就是碗数到了没)
if (Desk.count==0){
//碗数到了就结束线程
break;
}else{
//碗数未到就判断桌子上有没有食物
if (Desk.foodflag==1){
//有就等待食客吃完后唤醒线程
try {//因为父类run方法没有抛出异常,重写的也是没办法抛出的,只能try
Desk.lock.wait();//绑定线程锁,方便只唤醒这个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//没有就生产食物
Desk.foodflag = 1;
System.out.println("厨师生产一份食物");
//生产完后就唤醒再等待的食客线程
Desk.lock.notifyAll();
}
}
}
}
}
}
/**
* 桌子
*/
class Desk {
//用来判断是到生产还是消费
//判断有没有面条 0:没有 1:有
public static int foodflag = 0;
//总碗数
public static int count = 10;
//线程锁
public static Object lock = new Object();
}
/**
* 食客
*/
class Foodie extends Thread{
/*
* 步骤:
* --循环
* --同步线程锁
* --判断共享数据到了末尾的情况(就是吃够10碗)
* --判断共享数据还没到末尾的情况(就是没吃够,还想要继续)
*
*/
@Override
public void run() {
while (true){
//加上同步线程的锁
synchronized (Desk.lock){
//判断是否吃够总限额数,10碗
if (Desk.count==0){
//这里面表示吃完了
//结束线程
break;
}else {
//还没吃完,就判断桌子上有没有食物
//有就吃
//没有就唤醒厨师生产食物
if (Desk.foodflag==1){
//有就吃
Desk.count--;
//吃完还剩几碗
System.out.println("吃货正在吃面条,还能再吃"+Desk.count+"碗!!!");
//吃完桌子的状态就变成没有食物了
Desk.foodflag = 0;
//吃完就唤醒厨师继续生产
Desk.lock.notifyAll();//唤醒与所有lock锁绑定的等待中的线程
}else {
//没有就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}