一、多线程概述
1、进程: 正在运行的程序,是系统进行资源分配和调用的独立单位。 每一个进程都有它自己的内存空间和系统资源。
2、线程: 是进程中的单个顺序控制流,是一条执行路径 一个进程如果只有一条执行路径,则称为单线程程序。 一个进程如果有多条执行路径,则称为多线程程序。
3、Java程序运行原理 java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
4、jvm虚拟机的启动是多线程的。 主线程与垃圾回收线程
二、多线程实现
java提供了一个类描述线程 Thread,一个JVM中可以创建多个线程同时执行,每个线程都有优先权
java中创建线程对象的几种方式:
1、自己写一个A类继承Thread类,重写run方法,这个A类就叫线程类
注意:
1、run方法就是一个线程对象要做的事情
2、线程对象不能靠调用run方法来启动线程,只能算普通的对象调用了一个普通的方法,和线程没关系了。
3、要想启动一个线程,应该调用start()方法来进行启动,JVM进程中会为这个线程开辟一个对象,这个线程内部调用对应的run方法
4、调用完start()方法之后,线程只是具备了执行的资格,但是还没有真正的执行,只有抢到了CPU执行权的时候,才会执行(执行run方法里面的逻辑,run方法执行完了,线程也就结束了)
Thread类构造方法:
Thread() 分配一个新的 Thread对象。
Thread(String name) 分配一个新的 Thread对象。
Thread类中的成员方法:
public final String getName() 获取当前线程的名字
public final void setName(String name) 将此线程的名称更改为等于参数name 。
public static Thread currentThread() 获取当前方法所在的线程 主线程的名字叫做main
package com.shujia.day15; class MyThread1 extends Thread { MyThread1(){ super(); } MyThread1(String name){ super(name); } @Override public void run() { for (int i = 1; i <= 200; i++) { System.out.println(getName()+" -- "+i); // System.out.println("--------------------------"+currentThread().getName()+"--------------------------"); } } } public class ThreadDemo1 { public static void main(String[] args) { // MyThread1 t1 = new MyThread1(); // 在使用无参构造方法的时候,给创建的对象起了名字 // t1.setName("AAA"); // MyThread1 t2 = new MyThread1(); // t2.setName("BBB"); // //启动线程 //// t1.run(); //// t2.run(); // t1.start(); // t2.start(); //使用带参数的构造方法,在创建线程对象的时候给线程起名字 MyThread1 t1 = new MyThread1("AAA"); MyThread1 t2 = new MyThread1("BBB"); t1.start(); t2.start(); System.out.println("--------------------------"+Thread.currentThread().getName()+"--------------------------"); } }
2、自己写一个A类实现Runnable接口,实现run方法,这个A类就叫线程类
实现接口方式的好处:可以避免由于Java单继承带来的局限性。 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
package com.shujia.day15; /* 第二种实现线程方式:自己写一个A类实现Runnable接口,实现run方法,这个A类就叫线程类,需要借助Thread类来创建线程对象 */ class MyRunnable implements Runnable{ // @Override public void run() { // 线程要执行的逻辑在run方法中编写 for (int i = 1; i <= 200; i++) { //因为第二种是实现了Runnable接口,接口中只有一个抽象的run方法,找不到getName()方法,所以报错 //如果想要在这里输出线程的名字的话,可以间接地获取当前线程对象,然后再获取线程名字 System.out.println(Thread.currentThread().getName()+" -- "+i); } } } public class MyRunnableDemo1 { public static void main(String[] args) { //如何创建线程对象并启动呢? MyRunnable myRunnable = new MyRunnable(); //需要借助Thread类来创建线程对象 public Thread(Runnable target) // Thread t1 = new Thread(myRunnable); // Thread t2 = new Thread(myRunnable); // Thread t3 = new Thread(myRunnable); // t1.setName("大哥"); // t2.setName("二哥"); // t3.setName("三弟"); //public Thread(Runnable target, String name) 创建线程对象的同时给线程起名字 Thread t1 = new Thread(myRunnable, "大哥"); Thread t2 = new Thread(myRunnable, "二哥"); Thread t3 = new Thread(myRunnable, "三第"); //启动线程 t1.start(); t2.start(); t3.start(); } }
3、自己写一个A类实现Callable接口,实现call方法,这个A类就叫线程类,需要结合线程池才能使用
好处: 可以有返回值 可以抛出异常 弊端: 代码比较复杂,所以一般不用
三、线程的调度
1、线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
2、线程的优先级
每个线程都有优先级。
默认情况下,线程的优先级都是5,优先让优先级高的线程使用CPU
public final int getPriority() 获取线程的优先级
public final void setPriority(int newPriority) 修改线程的优先级 范围是[1,10]
注意:优先级高的线程只是说先执行的概率会大一些,并不是一定会先执行。
四、线程控制
线程休眠:public static void sleep(long millis) 毫秒
线程加入: public final void join() 其他线程会等待该线程执行结束
线程礼让 public static void yield()
后台线程 public final void setDaemon(boolean on)
后台线程(守护线程):
线程分为两种:
用户线程:是没有设置Daemon的线程
守护线程:当没有用户线程的时候守护线程自动死亡
中断线程 public final void stop()
public void interrupt()
线程的生命周期图
五、解决线程安全问题
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
基本思想:让程序没有安全问题的环境
实现:把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
具体实现:
①:同步代码块 :
格式: synchronized(对象){需要同步的代码;}
同步的前提
多个线程
多个线程使用的是同一个锁对象
同步的好处: 同步的出现解决了多线程的安全问题。
同步的弊端 :当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
②同步方法 就是把同步关键字加到方法上
如果锁对象是this,就可以考虑使用同步方法。 否则能使用同步代码块的尽量使用同步代码块。
解决线程安全问题的第二种方案:使用Lock锁来实现
但是通过观察API后发现,Lock本身是一个接口,所以找一个实现类:ReentrantLock
void lock() 加锁
void unlock() 释放锁
死锁的问题:
同步弊端 效率低 如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象 同步代码块的嵌套案例
六、线程中的通信
代码改进:A:通过等待唤醒机制实现数据依次出现
B:把同步代码块改进为同步方法实现
线程的状态转换图
七、线程组
概述:Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)
直接对组进行操作,也会影响到组中的线程,有了线程组,方便管理和分类
八、线程池 Executors
概述:程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
线程池的分类:
tatic ExecutorService newCachedThreadPool() 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
static ExecutorService newSingleThreadExecutor() 创建一个使用从无界队列运行的单个工作线程的执行程序。
static ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
注意:
线程一旦被放入了线程池中就会开始执行(相当于调用了start()方法,具备了执行资格);
线程池中的线程之间会互相抢CPU执行权;
线程池需要手动关闭,一般情况下需要不断地放入线程执行,一般不需要关。
九、定时器
概述:定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
构造方法:Timer() 创建一个新的计时器。
方法:
void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。
void schedule(TimerTask task, long delay, long period) 在指定的延迟之后开始 ,重新执行固定延迟执行的指定任务。
十、设计模式
概述:设计模式:是历代程序员开发总结得出的经验,并被后人持续的使用
分类:
创建型模式
简单工厂模式:
简单工厂模式(一个工厂中可以造很多的各种各样的对象)
这个模式的缺陷是如果有新的对象的话,需要修改很多类,而一般情况下,工厂类不轻易修改
工厂方法模式:每一个对象都由自身的一个工厂创建出来
单例模式:指的是在程序运行过程中,类内存中有此仅有一个对象
懒汉式
饿汉式
面试的时候说懒汉式,开发的时候使用饿汉式。因为懒汉式有可能存在线程安全的问题。懒汉式写的时候需要加上synchronized关键字
行为型模式
结构型模式
简单工厂模式(一个工厂中可以造很多的各种各样的对象)
这个模式的缺陷是如果有新的对象的话,需要修改很多类,而一般情况下,工厂类不轻易修改
标签:执行,Java,Thread,void,线程,run,多线程,public From: https://www.cnblogs.com/SIKE231310/p/17818196.html