多线程
1.1引入:
学习多线程之前,我们要学习进程,因为线程是基于进程存在的
1.什么是进程?
-
通过任务管理器我们可以看到进程的存在
-
我们发现,只有在运行中的程序,才会出现在任务管理器中
-
进程: 运行中的程序
-
进程是系统进行资源分配和调度的单位,每一个进程都有自己的内存空间和系统资源
2.什么是多进程?
-
最早的计算机,都是单核单进程的,也就是说,它一次只能做一件事
-
很显然,我们现在计算机可以同时做很多事,比如:一边聊微信,一边聊qq
-
我们现在的计算机,可以在一个时间段内,同时执行多个任务
3.有什么好处:
它可以提高cpu的使用率
4.我一边聊微信,一边聊qq,请问它是同时进行的吗?
-
不是的,单cpu同一时间点,只能做一件事情
-
而是cpu高速在程序间切换,让我们觉得它是同时运行的。
1.2概念:
在同一个进程内,又可以执行多个任务(我在聊微信的时候,我可以一边发消息,一边传文件)
1.2.1线程:
它实际是程序执行的基本单元,也就是执行路径,是程序使用cpu的最基本单位
1.2.2单线程:
程序只有一条执行的路径(我们之前学习的都是单线程)
1.2.3多线程:
程序有多条执行路径
1.2.4多线程意义?
1.线程的执行是抢占式的,它要去抢占cpu资源
2.一个多线程程序在执行时,如果一些线程必须等待的时候,cpu资源就可以交给其他线程去使用,提高程序的使用率,提高工作效率。
3.抢占资源的时候,是具有随机性的,我们不敢保证哪一个线程在哪一个时间可以抢到cpu资源。
1.2.5并行与并发:
并行:多个处理器或多核处理器在某一个时间段内同时运行多个程序
并发:一个处理器在某一个时间段内同时运行多个程序
1.2.6举例:
并发相当于一个人同时吃三个馒头
并行就相当于三个人同时吃三个馒头,一人吃一个
1.3多线程的实现(Thread):
1.3.1引入:
由于线程是依赖于进程存在的,所以我们首先要创建一个进程,而进程是由系统来创建的,所以我们需要调用系统的某些功能来创建,但是java是不能调用系统功能的。
但是它可以调用C/C++写好的程序来创建进程,进而实现多线程
java将C/C++写好的代码封装到一个类中,然后通过这个类就可以调用创建进程、或者线程的功能,然后间接的实现多线程
1.3.2创建方法:
①将类声明为Thread的子类,该子类重写Thread类的run方法
public class MyThread extends Thread {
@Override
public void run() {
//线程一般执行比较耗时的工作
for(int i=0;i<5;i++){
System.out.println(getName()+":"+i);
}
}
}
class MyThreadDemo{
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread myThread2 = new MyThread();
//myThread.run();调用普通方法
//myThread2.run();
myThread.start();//启动线程
myThread2.start();
}
}
注意:调用run()方法只是普通方法的调用,交给main处理,start()才是jvm启动线程
1.3.3线程重命名:
①利用Setname()改名字
myThread.setName("tom");
myThread2.setName("jack");
②有参构造:在子类中声明有参构造,并显示的指定访问父类的有参
public MyThread(String name){
super(name);
}
MyThread myThread = new MyThread("tom");
MyThread myThread2 = new MyThread("jack");
③获取当前执行线程的名字
System.out.println(Thread.currentThread().getName());
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cPFsah3v-1607342467562)(C:\Users\24582\AppData\Roaming\Typora\typora-user-images\image-20201206115430149.png)]
1.4线程优先级的设置:
1.4.1两种线程调度模型:
1.分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用的cpu时间片
2.抢占式调度:该调度模型会优先让优先级高的线程使用cpu,如果优先级相同,则随机选择。优先级高的线程只代表它几率大。
1.4.2获取优先级的方法及设置:
public class MyThread2 extends Thread{
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(getName()+":"+i);
}
}
}
class MyThread2Demo{
public static void main(String[] args) {
MyThread2 myThread = new MyThread2("线程1");
MyThread2 myThread2 = new MyThread2("线程2");
//优先级设置:(1--10)
myThread.setPriority(1);
myThread2.setPriority(10);
//显示优先级
System.out.println(myThread.getPriority());
System.out.println(myThread2.getPriority());
myThread.start();
myThread2.start();
}
}
默认优先级:5
优先级范围:1-10
1.5线程控制–线程睡眠(sleep):
1.5.1格式:
public static void sleep(long millis)
1.5.2代码:
/**
* 线程睡眠:
*/
public class MyThread3 extends Thread{
public MyThread3(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++){
try {
Thread.sleep(1000);//单位毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":"+i);
}
}
}
class MyThread3Demo{
public static void main(String[] args) {
MyThread3 myThread = new MyThread3("线程1:");
myThread.start();
}
}
1.5.3注意:
sleep在哪个线程中调用,阻塞哪个方法
1.6线程控制–加入线程(join):
1.6.1格式:
public final void join(long millis)
//该方法是调用该方法的线程执行完毕后,再去调用该方法
//不设置毫秒值,则一直等待该线程执行完毕,其他线程才开始执行
1.6.2代码:
public class MyThread4 extends Thread {
public MyThread4(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":"+i);
}
}
}
class MyThread4Demo{
public static void main(String[] args) {
MyThread4 myThread = new MyThread4("线程1:");
MyThread4 myThread2 = new MyThread4("线程1:");
MyThread4 myThread3= new MyThread4("线程1:");
myThread.start();
try {
myThread.join(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread2.start();
myThread3.start();
}
}
1.7线程控制—线程礼让(yield):
1.7.1格式:
public static void yield()
//线程礼让可以让线程间的抢占趋近和平,就是你一下,我一下,但仅仅只是让抢占不激烈,但并不完成是你一下,我一下
1.7.2代码:
/**
* 线程礼让
*/
public class MyThread5 extends Thread {
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println(getName()+":"+i);
Thread.yield();
}
}
}
class MyThread5Demo{
public static void main(String[] args) {
MyThread5 myThread = new MyThread5("线程1:");
MyThread5 myThread2 = new MyThread5("线程2:");
MyThread5 myThread3 = new MyThread5("线程3:");
myThread.start();
myThread2.start();
myThread3.start();
}
}
1.8线程控制–线程守护SetDaemon
1.8.1格式:
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
public final void setDaemon(boolean on)
1.8.2代码:
/**
* 守护线程
*/
public class MyThread6 extends Thread{
public MyThread6(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println(getName()+":"+i);
}
}
}
class MyThread6Demo{
public static void main(String[] args) {
MyThread6 myThread = new MyThread6("亚索");
MyThread6 myThread2 = new MyThread6("永恩");
myThread.setDaemon(true);
myThread2.setDaemon(true);
myThread.start();
myThread2.start();
Thread.currentThread().setName("主水晶");
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
1.9线程控制—终止线程(stop):
1.9.1格式:
public final void stop()
public void interrupt()
1.9.2代码:
/**
* 终止线程
*/
public class MyThread7 extends Thread{
public MyThread7(String name) {
super(name);
}
@Override
public void run() {
System.out.println("线程开始:" + new Date());
try {
for (int i = 0; i < 20; i++) {
Thread.sleep(5000);
System.out.println(getName() + ":" + i);
}
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("线程被终止了!");
}
System.out.println("线程结束:" + new Date());
}
}
class MyThread7Demo{
public static void main(String[] args) {
MyThread7 myThread = new MyThread7("线程1:");
myThread.start();
try {
Thread.sleep(3000);
myThread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.9.3注意:
interrupt和stop不同,前者走的是异常处理机制,如果你的线程终止还有一些代码必须执行的话,你可以把这些代码写在finally或try-catch结构外。
1.10线程的声明周期:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxxemx9r-1607342467564)(C:\Users\24582\AppData\Roaming\Typora\typora-user-images\image-20201206155531002.png)]
1.11创建多线程的第二种方法:
1.11.1Runnable接口:
/**
* 第二种创建方式:
*/
public class RunnableDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class RunnableTest{
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread t = new Thread(runnableDemo);
Thread t2 = new Thread(runnableDemo);
t.setName("线程1:");
t2.setName("线程2:");
t.start();
t2.start();
}
}
1.11.2问题:
为什么还会有第二种方法?
答:因为解决了单继承的局限性
例如:子类实现父类,但子类想实现多线程,只能用第二种。
1.12综合练习:
/**
* 使用多线程模拟电影院售票
* 需求:电影院上映了一部电影,该电影院只能容乃100张,并且有3个窗口同时售票:
*/
public class WorkDemo extends Thread {
public static int ticket =100;
@Override
public void run() {
while (true){
if(ticket>0){
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(getName()+"正在售票:"+"第"+(ticket--)+"张");
}
if(ticket==0){
Thread.currentThread().interrupt();
System.out.println("售罄!!!");
}
}
}
}
class WorkDemoTest{
public static void main(String[] args) {
WorkDemo workDemo = new WorkDemo();
WorkDemo workDemo2 = new WorkDemo();
WorkDemo workDemo3 = new WorkDemo();
workDemo.setName("窗口1:");
workDemo2.setName("窗口2:");
workDemo3.setName("窗口3:");
workDemo.start();
workDemo2.start();
workDemo3.start();
}
}
1.网络延迟sleep会导致出现一票多卖,为什么?
cpu执行具有原子性
2.出现0或者负票数,为什么?
1.13解决问题:解决多线程安全问题:
解决多线程安全问题:
1.是否是多线程
2.是否共享数据
3.是否有多条语句操作共享数据
思路:
考虑把这多条操作共享数据的语句进行包裹,当某一个程序在执行被包裹的语句时,其他线程不能执行这些语句
java为我们提供了一种机制解决我们的需求:同步机制
格式:
synchronized(任意对象){
}
代码:
public void run() {
synchronized (object) {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "正在售票:" + "第" + (ticket--) + "张");
Thread.yield();
}
}
}
}
死锁问题:
死锁:就是指两个或两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象。
1.14生产者消费者模型:
需求:
不同线程操作同一个学生对象,一个线程用来设置学生对象,一个线程用来获取学生对象。
分析:
资源类:Student
设置线程:SetThread—生产者
获取线程:getThread—消费者
测试类
问题:
可能出现生产者未产生数据,而消费者则抢占资源,get就会返回null
再或者生产者连续抢占资源,则新产生的资源会覆盖掉之前的,也没啥用
优化:
Object类中的线程操作方法:
wait():当前线程等待
notify():唤醒等待的单个线程
notifyAll():唤醒等待的多个线程
代码:
①学生类:
public class Student {
private String name;
private int age;
private boolean flag;//默认false,无数据
public synchronized void set(String name,int age){
//没有数据
if(this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//添加数据
this.name =name;
this.age = age;
//修改标记和唤醒
this.flag =true;
this.notify();
}
public synchronized void get(){
//没有数据就等待
if(!this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有数据就输出
System.out.println(this.name+"---"+this.age);
this.flag = false;
this.notify();
}
}
②设置线程类:
public class SetThread implements Runnable {
private Student student;
public SetThread(Student student){
this.student = student;
}
@Override
public void run() {
student.set("tom",18);
student.set("jack",20);
}
}
③获取线程类:
public class GetThread implements Runnable {
private Student student;
public GetThread (Student student){
this.student = student;
}
@Override
public void run() {
student.get();
student.get();
}
}
④学生测试类:
public class StudentDemo {
public static void main(String[] args) {
Student student = new Student();
SetThread setThread = new SetThread(student);
GetThread getThread = new GetThread(student);
Thread thread = new Thread(getThread);
Thread thread2 = new Thread(setThread);
thread.start();
thread2.start();
}
}
1.15线程组:
概述:
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
获取线程组方法:
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
设置线程分组:
我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)
好处:
便于统一管理线程组。
代码:
public class ThreadGroupDemo {
public static void main(String[] args) {
method1();
method2();
}
public static void method1(){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable,"tom");
Thread thread2 = new Thread(myRunnable,"jack");
ThreadGroup tg1 =thread.getThreadGroup();
ThreadGroup tg2 =thread2.getThreadGroup();
System.out.println(tg1.getName());
System.out.println(tg2.getName());
System.out.println(Thread.currentThread().getThreadGroup().getName());
// thread.start();
// thread2.start();
}
public static void method2(){
ThreadGroup threadGroup = new ThreadGroup("NewThread");
MyRunnable myRunnable2 = new MyRunnable();
Thread thread = new Thread(threadGroup,myRunnable2,"tom");
Thread thread2 = new Thread(threadGroup,myRunnable2,"jack");
System.out.println(thread.getThreadGroup().getName());
System.out.println(thread2.getThreadGroup().getName());
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
}
1.16线程池:
概述:
程序启动一个新线程成本是比较高的,因为它涉及到与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存周期很短的线程时,更应该考虑使用线程池。
特点:
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象继续使用。
新特性:
在JDK5以前,我们必须手动实现自己的线程池,从jdk5以后,java内置支持线程池。
创建线程池方法:
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
执行方法:
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供如下方法
Future<?> submit(Runnable task)
Future submit(Callable task)
代码:
/**
* 线程池创建的第一种方法:带缓冲区
* public static ExecutorService newCachedThreadPool()
*/
public class ExecutorsDemo {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.submit(new MyRunnable());
cachedThreadPool.submit(new MyRunnable());
cachedThreadPool.submit(new MyRunnable());
// cachedThreadPool.execute(new MyRunnable());
cachedThreadPool.shutdown();
}
}
/**
* 创建线程池的第二种方法:指定个数
* public static ExecutorService newFixedThreadPool(int nThreads)
*/
public class ExecutorsDemo2 {
public static void main(String[] args) {
//创建线程池对象,并指定个数
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//创建三个线程
fixedThreadPool.submit(new MyRunnable());
fixedThreadPool.submit(new MyRunnable());
fixedThreadPool.submit(new MyRunnable());
//停止线程池
fixedThreadPool.shutdown();
}
}
/**
* 创建线程池的第三种方法:默认一个,且执行有序
* public static ExecutorService newSingleThreadExecutor()
*/
public class ExecutorsDemo3 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("我是第一个");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("我是第二个");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("我是第三个");
}
});
executorService.shutdown();
}
}
1.17实现线程的第三种方法:
格式1:
实现Callable接口
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
return null;
}
}
测试类
public class CallableDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new MyCallable());
executorService.submit(new MyCallable());
executorService.shutdown();
}
}
格式2:
实现泛型
public class MyCallable2 implements Callable<Integer> {
private int num;
public MyCallable2(int num){
this.num =num;
}
@Override
public Integer call() throws Exception {
int result = 0;
for (int i=0;i<num;i++){
result += i;
}
return result;
}
}
测试类
public class CallableDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Integer> result1 = executorService.submit(new MyCallable2(5));
Future<Integer> result2 = executorService.submit(new MyCallable2(100));
Integer i1 = result1.get();
Integer i2 = result2.get();
System.out.println(i1);
System.out.println(i2);
executorService.shutdown();
}
}
好处:
可以有返回值
可以抛出异常
缺点:
代码比较复杂,所以一般不用
1.18使用匿名内部类实现多线程:
格式:
new Thread(){代码;}.start();
new Thread(new Runnable(){代码;}){}.start();
代码:
public class NonameThreadDemo1 {
public static void main(String[] args) {
//继承Thread类
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
//实现Runnable接口
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
注意:
面试题写法,开发中不用
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("1");
}
}){
@Override
public void run() {
System.out.println("2");
}
}.start();
结果会输出2,jvm会走Thread的run方法,而不走Runnable的run方法
1.19定时器:
概述:
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务,以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。
Timer:
public Timer()
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task,long delay,long period)
TimerTask:
抽象类,可同类自定义类继承实现方法
public abstract void run()
public boolean cancel()
代码案例:
public class TimerDemo {
public static void main(String[] args) {
//创建对象
Timer timer = new Timer();
// //延迟多少时间后执行任务
// //public void schedule(TimerTask task, long delay)
// timer.schedule(new MyTask(),500);
//
// //指定时间后,重复执行
// //public void schedule(TimerTask task,long delay,long period)
// timer.schedule(new MyTask(),3000,1000);//三秒后执行并且每隔一秒执行一次
//停止
timer.schedule(new MyTask(timer),3000);
}
}
class MyTask extends TimerTask{
private Timer t;
public MyTask(){
}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
System.out.println("~~~");
t.cancel();
}
}
练习:
/**
* 需求在指定时间删除目录下所有文件和文件夹
*
*/
public class TimerDemo2 {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
String s="2020-12-7 18:43:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s);
timer.schedule(new delFile(),d);
}
}
class delFile extends TimerTask{
@Override
public void run() {
File file = new File("TEST");
del(file);
}
public void del(File f){
File [] files = f.listFiles();
if(files!=null){
for(File file: files){
if(file.isDirectory()){
del(file);
}else {
file.delete();
}
}
f.delete();
}
}
}