线程:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
进程:
进程是程序的基本执行实体
举个例子:360运行之后,它就可以看做是一个进程,但运行我的电脑,木马查杀,电脑清理等功能时,就是线程。
可以同时运行多个功能就是多线程
1.什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
2.多线程的作用?
提高效率
3.多线程的应用场景?
只要你想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行并发:来回交替不断
并行:直接两个一起运行
(1)继承Thread类的方式进行实现
package homework;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第一种启动方式
* 1.自己定义一个类继承Thread
* 2.重写 run 方法
* 3.创建子类的对象,并启动线程
*
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
package homework;
public class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<100;i++)
{
System.out.println( getName() + " HelloWorld");
}
}
}
//打印结果能发现运行线程1 和线程2 交织运行
(2)实现Runnable接口的方式进行实现
package homework;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第二种启动方式
* 1.自己定义一个类继承Thread
* 2.重写 run 方法
* 3.创建子类的对象,并启动线程
* 4.创建一个Thread类的对象,并且开启线程
*/
//创建MyRun的对对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
package homework;
public class MyRun implements Runnable{
@Override
public void run()
{
for(int i=0;i<100;i++)
{
//先获取到当前线程的对象
// Thread t = Thread.currentThread();
//
// System.out.println( t.getName() + " Hello,world!");
System.out.println(Thread.currentThread().getName() + " Hello,world!");
}
}
}
//打印结果的时候依旧能看到线程的交织
//唯一的区别就是这一个方法运用的是线程的接口
//不能直接调用父类的获取名称方法
//需要先获取一个对象,通过对象再去调用方法
(3)利用Callable接口和Future接口方式实现
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/**
* 多线程的第三种启动方式(是对前面两种方法的补充,前面的两种启动方法都没有返回值)
* 特点:可以获取到多线程运行的结果
* 1.创建一个类 MyCallable 实现 Callable 接口
* 2.重写 call 方法 (是有返回值的,表示多线程运行的结果)
* 3.创建 MyCallable 的对象(表示多线程要执行的任务)
* 4.创建 FutureTask 的对象(作用管理多线程运行的结果)
* 5.创建 Thread 类的对象,并启动(表示线程)
*
*
*/
//创建 MyCallable 的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建 FutureTask 的对象(作用管理多线程运行的结果)
FutureTask ft = new FutureTask<>(mc);
//创建线程的对象
Thread t1 = new Thread(ft);
t1.start();
try {
Integer result = ft.get();
System.out.println(result);
}catch (Exception e)
{
e.printStackTrace();
System.out.println("网络出现错误!");
}
}
}
package homework;
import javax.xml.ws.WebServiceException;
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
@Override
public Integer call() throws Exception
{
int sum = 0;
for(int i=1;i<=100;i++)
{
sum = sum + i;
}
return sum;
}
}
如果想要获取结果,使用 Callable 接口重写方法可以获得返回值!
注意事项:
- static Thread currentThread() 方法是静态方法(也就是类方法),可以直接调用
- static void sleep(long time) 停留的时间由传入的参数决定
- 在 Java 的线程当中,最小的优先级是 1 ,最大是 10 ,默认是中间的 5
- 线程的优先级越大,抢占到 cpu 的概率越大
抢占式调度:随机性
非抢占式调度: 轮流执行
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import lambdatest.MyThread;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
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.方法的参数,就表示睡眠的时间,单位是:ms
1 s = 1000 ms
3.当时间到了之后,线程会自动醒来,继续执行下面的其它代码
*/
// 1.创建线程的对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//2.开启线程
t1.start();
t2.start();
/* 如果要创建自带名字的对象,要使用构造器
但是 MyThread 是 Thread 的子类
子类不能直接调用父类的方法
必须在子类里面重写父类的方法*/
MyThread t3 = new MyThread("飞机");
MyThread t4 = new MyThread("坦克");
//哪条线程执行到这个方法,此时获取的就是哪条线程的对象
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);
try {
System.out.println("1111111111111");
Thread.sleep(5000);
System.out.println("2222222222222");
}catch(Exception e)
{
e.printStackTrace();
}
}
}
package lambdatest;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run()
{
for(int i=0;i<100;i++)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + " @ " + i);
}
}
}
Java 使用的抢占式调度,即优先级越高,抢占到 cpu 的概率更大
优先级代码:
通过不断运行结果可见,坦克先打印完100的次数大于飞机先打印完100的次数;也就是说,通过设定线程的优先级可以改变线程抢占到 cpu 的概率
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import lambdatest.MyRunnable;
import lambdatest.MyThread;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
*/
//创建线程要执行的参数对象
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr,"飞机");
Thread t2 = new Thread(mr,"坦克");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
package lambdatest;
public class MyRunnable implements Runnable{
@Override
public void run()
{
for(int i=1;i<=100;i++)
{
System.out.println(Thread.currentThread().getName() + " " + i );
}
}
}
守护线程:
当其他的非守护线程执行完毕之后,守护线程就会陆续结束。
通俗一点理解:其他线程是老大,守护线程是小弟,其他线程结束了(老大没了),小弟就没有必要存在了。
应用场景:例如QQ聊天的时候,聊天界面作为一个线程,传输文件作为一个线程,如果关闭了聊天界面,那么传输文件就没有继续下去的必要了
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import exericise.MyThread;
import exericise.MyThread2;
import lambdatest.MyRunnable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
细节:
当其他的非守护线程执行完毕之后,守护线程就会陆续结束
可以理解为:可以认为守护线程是其他线程的备胎
其他线程都没有了
那么备胎线程也没有存在的必要了
*/
MyThread t1 = new MyThread();
MyThread2 t2 = new MyThread2();
//将第二个线程设置为守护线程(也可以称备胎线程)
t2.setDaemon(true);
t1.setName("ice cream");
t2.setName("milk tea");
t1.start();
t2.start();
}
}
package exericise;
public class MyThread extends Thread{
@Override
public void run()
{
for(int i=1;i<=10;i++)
{
System.out.println(getName() + " " + i );
}
}
}
package exericise;
public class MyThread2 extends Thread{
@Override
public void run()
{
for(int i=1;i<=100;i++)
{
System.out.println(getName() + " @ " + i );
}
}
}
礼让线程:
让目前抢占到 CPU 控制权的线程让出来,再让已创建的线程重新抢夺 CPU 的控制权
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import exericise.MyThread2;
import lambdatest.MyRunnable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
public static void yield() 出让线程/礼让线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("ice cream");
t2.setName("milk tea");
t1.start();
t2.start();
}
}
package homework;
public class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<100;i++)
{
System.out.println( getName() + " " + i );
//表示出让当前 CPU 的执行权
//原本的线程在运行到下面这个代码会让出 CPU 的控制权
//出让 CPU 的控制权后,已有的线程重新争夺 CPU 的控制权
//这样让结果尽可能的均匀
Thread.yield();
}
}
}
插入线程:
插入线程就是插队,让某个线程先执行完毕再让其他的执行
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import exericise.MyThread2;
import lambdatest.MyRunnable;
import lambdatest.MyThread;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
public final void join() 插入线程/插队线程
*/
MyThread t = new MyThread();
t.setName("我是不重要的线程 o_o");
t.start();
//表示把 t 这个线程,插入到当前线程之前
//t:我是不重要的线程 o_o 执行完毕之后才会执行 main 线程
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//执行在 main 线程当中
for(int i=0;i<10;i++)
{
System.out.println("main 线程 " + i);
}
}
}
package exericise;
public class MyThread extends Thread{
@Override
public void run()
{
for(int i=1;i<=10;i++)
{
System.out.println(getName() + " " + i );
}
}
}
package test;
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run()
{
for(int i=0;i<100;i++)
{
System.out.println(getName() + " @ " + i);
}
}
}
sleep方法让线程睡眠,睡眠时间到了以后,线程需要变为就绪状态,直到抢到 CPU 才会执行代码!!!
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
如果使用单纯的多线程,结果出现了一下的问题:
- 相同的票出现了多次
- 出现了超出范围的票
出现问题的原因:
线程执行的时候,有随机性,CPU 的控制权很有可能会被其他线程给抢走
同步代码块:
锁的特点:
- 锁默认打开,有一个线程进去了,锁自动关闭
- 里面的代码全部执行,线程出来,锁自动打开
使用同步代码块的时候要注意:
- 使用this关键字可以锁定当前实例对象,即只有获得该实例对象的锁才能执行同步代码块。这意味着如果两个不同的线程分别调用相同的实例对象上的同步方法或同步代码块,它们不能同时执行。
- 使用当前类的字节码文件作为锁对象:这意味着只有获得了该类的字节码的锁,才能执行同步代码块。这样就能避免不同线程之间调用不同实例对象的同步方法时互相干扰。
- 在使用synchronized关键字时,如果需要同时控制多个不同实例对象上的同步方法或同步代码块,则应该使用当前类的字节码文件作为锁对象。如果需要控制单个实例对象的同步方法或同步代码块,则应该使用this关键字作为锁对象。
简单来说:类的字节码指向的是类,而 this 关键字指向的是实例
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import exericise.MyThread2;
import lambdatest.MyRunnable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package homework;
public class MyThread extends Thread{
static int ticket = 0;
@Override
public void run(){
while(true)
//同步代码块
{
synchronized (MyThread.class)
{
if(ticket<100)
{
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
ticket++;
System.out.println(getName() + "正在出售第" + ticket +"张票!" );
}
else
{
break;
}
}
}
}
}
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import exericise.MyThread;
import exericise.MyThread2;
import lambdatest.MyRunnable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
利用同步方法完成
技巧:先写同步代码块,再改成同步方法
*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package lambdatest;
public class MyRunnable implements Runnable{
int ticket = 0;
//1.循环
//2.同步代码块(同步方法)
//3.判断共享数据是否到了末尾,如果到了末尾
// 4.判断共享数据是否到了末尾,如果没有到末尾
@Override
public void run()
{
while (true)
{
synchronized (MyRunnable.class)
{
if(method()) break;
}
}
}
private synchronized boolean method()
{
if(ticket == 100)
{
return false;
}
else
{
ticket++;
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票!!!");
}
return false;
}
}
手动锁:
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
package homework;
import com.sun.corba.se.impl.orbutil.closure.Future;
import exericise.MyThread2;
import lambdatest.MyRunnable;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) {
/*
利用同步方法完成
技巧:先写同步代码块,再改成同步方法
*/
// MyRunnable mr = new MyRunnable();
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
package homework;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run(){
while(true)
//同步代码块
{
lock.lock();
try {
if(ticket==100)
{
break;
}
else
{
ticket++;
System.out.println(getName() + "正在出售第" + ticket +"张票!" );
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
死锁:
死锁是错误!!!死锁是锁的嵌套,写程序的时候要防止死锁的错误。
死锁产生的原因主要可以归结为以下四个必要条件:
- 互斥条件:每个资源同时只能被一个进程持有或使用。
- 请求和保持条件:一个进程因请求资源而阻塞时,已经获得的资源保持不放。
- 不剥夺条件:已经分配给某个进程的资源不能被其他进程强制性地抢占,只能由该进程自行释放。
- 环路等待条件:存在一种进程资源的环形等待链,链中的每个进程都在等待下一个进程所持有的资源。
为了避免死锁,应该采取以下措施:
- 避免一次性请求过多资源,实现资源按需获取。
- 避免持有资源时阻塞请求其他资源。
- 尽量避免循环等待,在必要时对资源使用顺序进行限制。
- 设置资源超时时间,及时释放长期占用的资源。
- 使用同步模块来确保线程不会在临界区中互相等待。
例如上面这个代码:由于编码时产生了锁的嵌套,导致A、B锁都在等对方解锁而导致程序无法进行下去而产生死锁。
生产者和消费者(等待唤醒机制)
生产者消费者模式是一个十分经典的多线程协作的模式
package homework;
import exericise.Cook;
import exericise.Foodie;
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();
}
}
public class Cook extends Thread {
@Override
public void run()
{
/*
1.循坏
2.同步代码开
2.判断共享数据是否到了末尾(到了末尾)
2.判断共享数据是否到了末尾(没有到位末尾,执行核心逻辑)
*/
while(true)
{
synchronized (Desk.lock)
{
if(Desk.count == 0)
{
break;
}
else
{
if(Desk.foodFlag==1)
{
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.getStackTrace();
}
}
else {
System.out.println("厨师做了一碗面条");
Desk.foodFlag = 1;
Desk.lock.notifyAll();
}
}
}
}
}
}
package exericise;
public class Foodie extends Thread{
@Override
public void run()
{
/*
1.循坏
2.同步代码开
2.判断共享数据是否到了末尾(到了末尾)
2.判断共享数据是否到了末尾(没有到位末尾,执行核心逻辑)
*/
while(true)
{
synchronized (Desk.lock)
{
if(Desk.count == 0)
{
break;
}
else
{
//判断桌子上是否有面条
//如果没有,就等待
//如果有,就开吃
//吃完之后,要唤醒厨师继续做
//面条数量-1
//修改桌子的状态,就是有误面条的情况判断
if(Desk.foodFlag == 0)
{
try {
Desk.lock.wait();//让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.getStackTrace();
}
}else {
Desk.count--;
System.out.println(getName() + "正在吃面条,能继续吃" + Desk.count + "碗") ;
Desk.lock.notify();
Desk.foodFlag = 0;
}
}
}
}
}
}package exericise;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Desk {
/*
控制生产者和消费者的执行
*/
//桌子上时候有面条: 0:没有面条 1:有面条
public static int foodFlag=0;
//总个数:
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
package homework;
import exericise.Cook;
import exericise.Foodie;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo{
public static void main(String [] args)
{
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(1);
Cook c= new Cook(queue);
Foodie f = new Foodie(queue);
c.setName("厨师");
f.setName("吃货");
c.start();
f.start();
}
}
package exericise;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread {
ArrayBlockingQueue queue;
public Foodie(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
String food = queue.take();
//take 方法本身也有锁
System.out.println(food);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
线程的状态
Java中是没有定义运行状态的, 一旦抢占到 cpu ,Java虚拟机就会将执行交给操作系统
线程池:
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
package homework;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
/*
public static ExecutorService newCachedThreadPool()
创建一个没有上线的线程池
*/
ExecutorService pool1 = Executors.newCachedThreadPool();
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
// pool1.shutdown();
}
}
package homework;
public class MyRunnable implements Runnable{
@Override
public void run()
{
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName() + " 正在运行 " + i);
}
}
}
package homework;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) {
/*
public static ExecutorService newFixedThreadPool (int nThreads)
创建有上限的线程池
*/
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//定义了上限之后,能看到打印结果只有3个线程
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
// pool1.shutdown();
}
}
package homework;
public class MyRunnable implements Runnable{
@Override
public void run()
{
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName() + " 正在运行 " + i);
}
}
}