妄想成为Java程序员Day13:多线程
5多线程
5.1线程和进程
进程:
是一个内存中运行的应用程序,每个进程有一个独立的空间。
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以切换,并发执行。一个进程最少一个线程
多线程:在一个程序的进程中开辟多个执行路径。
5.2 线程调度
分时调度:
所有的线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间。
抢占式调度:
优先级高的线程使用CPU,如果线程优先级别相同,那么会随机选择一个(线程随机性),Java使用的是抢占式调度。
抢占式调度
CPU使用抢占式调度模式在多个线程间进行高速切换,对于CPU的一个核心而已,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们感觉要快,看上去有就是在同一时刻。其实,多线程程序并不能提高程序的运行效率,让CPU的使用率更高。
数据库面试题:如果有一千个用户访问你的数据库,是一起分时调度做速度快,还是排队执行速度快。
实际上是排队执行的速度快,因为省去了切换的时间。
5.2同步与异步
同步:排队执行,效率低但是安全。(效率低,但是资源不会发生抢占。)
异步:同时执行,效率高但是数据不安全。(资源抢占,容易发生死锁。)
5.3并发与并行
并发:指两个或者多个事件在同一个时间段内发生。
并行:指两个或者多个事件在同一时刻发生。
多线程的实现方式
有两种方法可以创建新的执行线程。 一种是将类声明为Thread的子类。 此子类应覆盖类Thread的run方法。 然后可以分配和启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并开始运行:
PrimeThread p = new PrimeThread(143);
p.start();
创建线程的另一种方法是声明一个实现Runnable接口的类。 该类然后实现run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,然后启动。 此其他样式中的相同示例如下所示:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并开始运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
每个线程都有一个用于识别目的的名称。 多个线程可能具有相同的名称。 如果在创建线程时未指定名称,则会为其生成新名称。
继承Thread
package com.kaikeba.Day13;
public class duckThreadmain {
/*
*
* 多线程测试
*
* */
public static void main(String[] args) {
duckThread01 d = new duckThread01();
d.start();
for (int i = 0; i <10 ; i++) {
System.out.println("锄禾日当午"+i);
}
}
}
package com.kaikeba.Day13;
public class duckThread01 extends Thread{
/*
* run方法就是线程要执行的任务方法
* */
@Override
public void run(){
//在这里的代码 ,就是一条新的执行路径。
//这个执行路径的触发方式,不是调用run方法 , 而是通过thread对象的start()来启动任务
for (int i = 0; i <10 ; i++) {
System.out.println("锄禾日当午"+i);
}
}
}
每个线程都有一个自己的栈空间,共用一个堆内存。
由一个线程调用的方法,也会在这个线程里面执行。
public class duckRunablemain {
/*
*
* 多线程实现方法2
*
* Runable 与 继承Thread相比有如下优势:
* 1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
* 2.可以避免蛋单继承所带来的局限性
* 3.任务与程序分离,提高了程序的健壮性。
* 4.后续学习的线程池技术,接受Runable类型的任务,不接受Thread类型的线程;
*
* */
public static void main(String[] args) {
//实现Runable
//1 .创建一个任务对象
duckRunable02 d = new duckRunable02();
//2 .创建一个线程,并为其分配一个任务
Thread t = new Thread(d);
//执行这个线程
t.start();
}
}
public class duckRunable02 implements Runnable {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(“窗前明月光”+i);
}
}
}
Runable 与 继承Thread相比有如下优势:
* 1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
* 2.可以避免蛋单继承所带来的局限性
* 3.任务与程序分离,提高了程序的健壮性。
* 4.后续学习的线程池技术,接受Runable类型的任务,不接受Thread类型的线程;
但是 继承Thread可以使用匿名内部类直接添加线程
new Thread() {
@Override
public void run(){
for (int i = 0; i <10 ; i++) {
System.out.println(“哈哈哈哈”+i);
}
}
}.start();
如何获取或设置线程名称
Thread.currentThread().getName()
线程中断
package com.kaikeba.Day13;
public class duckNotrun {
//线程的中断
//线程是一个独立的执行路径,他是否应该结束,应该是有自身决定。
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
//给线程t1添加中断标记,触发异常
t1.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//e.printStackTrace();
// System.out.println(“发现中断标记,但是不死亡。”);
System.out.println(“发现中断标记,线程死亡。”);
return;//杀死线程
}
}
}
}
}
守护线程
线程分为守护线程和用户线程
用户线程:当一个进程不包括任何的存活的用户线程时,死亡。
守护线程:守护用户线程,当最后一个用户线程结束时,所有的守护线程自动死亡
线程安全和不安全
public class duckSafe05 {
public static void main(String[] args) {
//线程不安全
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count =10;
@Override
public void run() {
while (count > 0){
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count --;
System.out.println("出票成功,余票:" + count);
}
}
}
}
显式锁和隐式锁
1.从sync和lock的出身(原始的构成)来看看两者的不同。
Sync:Java中的关键字,是由JVM来维护的。是JVM层面的锁。
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁
2.Sync是隐式锁。Lock是显示锁
所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。
隐式锁
public class duckSafe05 {
public static void main(String[] args) {
/*
* 线程同步:synchronized
* */
//线程不安全
//解决方案1.同步代码块
//格式 : synchronized(锁对象){
//
// }
//排队,有锁对象的排队。(同一个锁对象)
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable {
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) { if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
}
}
}
}
}
}
隐式锁2
public class duckSafe06 {
public static void main(String[] args) {
/*
* 线程同步:synchronized
* */
//线程不安全
//解决方案2.同步方法
//格式 : public synchronized boolean (){
//
// }
//排队,有锁对象的排队。(同一个锁对象)
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable {
private int count = 10;
@Override
public void run() {
while (true) {
boolean flag = sale();
if (!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
return true;
}else
return false;
}
}
显示锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class duckSafe07 {
/*
* 同步代码块和同步方法都是隐式锁
* 线程同步:Lock
* Lock l = new ReentrantLock();
* l.lock
*
* /
public static void main(String[] args) {
/
* 线程同步:synchronized
* */
//线程不安全
//解决方案1.同步代码块
//格式 : synchronized(锁对象){
//
// }
//排队,有锁对象的排队。(同一个锁对象)
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
return;
}
static class Ticket implements Runnable {
private int count = 10;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("出现异常");
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
}else {
break;
}
l.unlock();
}
}
}
公平锁和不公平锁
公平锁:先到先执行
//Lock l = new ReentrantLock(true);true为公平 (默认)false为不公平
不公平锁:谁抢到谁执行
线程阻塞
//线程阻塞 :所有比较消耗时间的操作
耗时操作
5.4Thread
嵌套类汇总
嵌套类
变量和类型 类 描述
static class Thread.State 线程状态。
static interface Thread.UncaughtExceptionHandler 当 Thread由于未捕获的异常而突然终止时调用的处理程序接口。
字段汇总
字段
变量和类型 字段 描述
static int MAX_PRIORITY 线程可以拥有的最大优先级。
static int MIN_PRIORITY 线程可以拥有的最低优先级。
static int NORM_PRIORITY 分配给线程的默认优先级。
控制字段优先级
构造方法摘要
构造方法
构造器 描述
Thread() 分配新的 Thread对象。
Thread(Runnable target) 分配新的 Thread对象。
Thread(Runnable target, String name) 分配新的 Thread对象。
Thread(String name) 分配新的 Thread对象。
Thread(ThreadGroup group, Runnable target) 分配新的 Thread对象。
Thread(ThreadGroup group, Runnable target, String name) 分配新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,并且属于 group引用的线程组。
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 分配新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,并且属于 group引用的线程组,并具有指定的 堆栈大小 。
Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) 分配新的Thread对象,使其具有target作为其运行对象,具有指定的name作为其名称,属于group引用的线程组,具有指定的stackSize ,并且如果inheritThreadLocals是true ,则继承inheritable thread-local变量的初始值。
Thread(ThreadGroup group, String name) 分配新的 Thread对象。
方法摘要
所有方法 静态方法 实例方法 具体的方法 弃用的方法
变量和类型 方法 描述
static int activeCount() 返回当前线程thread group及其子组中活动线程数的估计值。
void checkAccess() 确定当前运行的线程是否具有修改此线程的权限。
protected Object clone() 抛出CloneNotSupportedException,因为无法有意义地克隆线程。
int countStackFrames() 不推荐使用,要删除:此API元素将在以后的版本中删除。
此调用的定义取决于suspend() ,已弃用。
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
static void dumpStack() 将当前线程的堆栈跟踪打印到标准错误流。
static int enumerate(Thread[] tarray) 将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中。
static Map<Thread,StackTraceElement[]> getAllStackTraces() 返回所有活动线程的堆栈跟踪映射。
ClassLoader getContextClassLoader() 返回此线程的上下文 ClassLoader 。
static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 返回由于未捕获的异常而导致线程突然终止时调用的默认处理程序。
long getId() 返回此Thread的标识符。
String getName() 返回此线程的名称。
int getPriority() 返回此线程的优先级。
StackTraceElement[] getStackTrace() 返回表示此线程的堆栈转储的堆栈跟踪元素数组。
Thread.State getState() 返回此线程的状态。
ThreadGroup getThreadGroup() 返回此线程所属的线程组。
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() 返回此线程由于未捕获的异常而突然终止时调用的处理程序。
static boolean holdsLock(Object obj) 当且仅当当前线程在指定对象上保存监视器锁时,返回 true 。
void interrupt() 中断此线程。
static boolean interrupted() 测试当前线程是否已被中断。
boolean isAlive() 测试此线程是否存活。
boolean isDaemon() 测试此线程是否为守护程序线程。
boolean isInterrupted() 测试此线程是否已被中断。
void join() 等待这个线程死亡。
void join(long millis) 此线程最多等待 millis毫秒。
void join(long millis, int nanos) 此线程最多等待 millis毫秒加上 nanos纳秒。
static void onSpinWait() 表示调用者暂时无法进展,直到其他活动发生一个或多个操作为止。
void resume() 已过时。
此方法仅适用于suspend() ,由于它易于死锁,因此已被弃用。
void run() 如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
void setContextClassLoader(ClassLoader cl) 为此Thread设置上下文ClassLoader。
void setDaemon(boolean on)
daemon守护线程
守护用户线程
当所有用户线程死亡
守护线程死亡 将此线程标记为 daemon线程或用户线程。
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置当线程由于未捕获的异常而突然终止时调用的默认处理程序,并且没有为该线程定义其他处理程序。
void setName(String name) 将此线程的名称更改为等于参数 name 。
void setPriority(int newPriority) 更改此线程的优先级。
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 设置当此线程由于未捕获的异常而突然终止时调用的处理程序。
static void sleep(long millis)
//暂时停止执行
millis 毫秒数 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static void sleep(long millis, int nanos) 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法。
void stop() 已过时。
这种方法本质上是不安全的。
void suspend() 已过时。
此方法已被弃用,因为它本身就容易出现死锁。
String toString() 返回此线程的字符串表示形式,包括线程的名称,优先级和线程组。
static void yield()
5.3线程的六种状态
new:创建
Runnable:运行
blocked:等待
Waiting:睡眠
TimeWaiting:计时等待
Trminated:死亡
5.4Callable
等主线程完成再完成返回一个值
也可以和主线程一起执行;
5.5线程池
开发过程中会不可避免得创建大量的线程
5.6缓存线程池
1.判断线程池中是否存在空闲线程
2.存在则使用
3.不存在,则创建线程并且放入线程池,然后使用。
本文地址:https://blog.csdn.net/weixin_44190985/article/details/107592010
上一篇: 数据结构算法--柱状图中最大的矩形
下一篇: python多线程爬虫实例分析
推荐阅读