java中的多线程是一个同时执行多个线程的进程,线程是一个轻量级的子进程,是最小的处理单位。线程是进程的一条执行路径,
而进程是线程的集合。
1.优点:
① 他不会阻塞用户,因为线程是独立的,可以同时执行多个操作。
② 可以一起执行许多操作,因此可以节省时间。(提高程序效率)
③ 线程是独立的,因此如果单个线程中发生异常,他不会影响其他线程。
2.多任务处理
多任务处理是同时执行多个任务的过程。使用多任务来利用CPU,多任务处理可以通过两种方式来实现:
① 基于进程的多任务处理(多进程)
特点:每个进程在内存中都单独分配有一个内存区域,进程是重量级的。进程之间的通信成本很高。从
一个进程切换到另一个进程需要一些时间来保存和加载寄存器、内存映射、更新列表等。
② 基于线程的多任务处理 (多线程)
特点:线程共享相同的地址空间;线程之间是轻量级的;线程之间的通信成本很低。
3.Thread类
java提供了Thread类来实现线程编程。Thread类提供了在线程上创建和执行操作的构造函数和方法。Thread类
扩展了object类并实现了runnable接口。
创建线程的方式: MyThread myThread = new MyThread();
4.runnable接口
创建线程有两种方式,一种是直接继承Thread类,另外一种是实现runnable接口。相比较Thread而言,通过实现
runnable的方式可以更容易地实现资源共享,并且接口可以多实现且还能再继承其他类。
总结:① 适合多个相同的程序代码的线程去处理同一个资源
② 可以java中单继承的限制
③ 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
创建线程的方式: MyThread myThread = new MyThread(); //只需要创建一个实例化对象然后被多个线程使用
Thread Thread1 = new Thread(myThread); //线程1
Thread Thread2 = new Thread(myThread); //线程2
5.线程生命周期
① 新建状态:通过new创建一个线程,跟其他对象的创建时一样的。此时虚拟机会为其分配内存,并初始化
其成员变量的值。
② 就绪状态:当线程对象调用了start方法之后,线程就进入了就绪状态。程序就会为其创建方法调用栈和程
序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了,至于何时运
行,取决于jvm中的线程调度器的调度。
③ 运行状态:当处于就绪状态的线程获得cpu,开始执行run()方法的线程执行体时,线程就进入了运行状态,
线程调度的细节取决于底层平台采用的策略(抢占式调度策略和协作式调度策略)
④ 阻塞状态:运行状态的线程遇到以下情况就会进入阻塞状态:
1.线程自己调用sleep方法主动放弃处理器资源;
2.该线程调用了一个阻塞式IO方法,在该方法返回之前,该线程处于阻塞状态。
3.该线程试图获得一个同步监视器,但该同步监视器正被其他线程持有。
4.线程正在等待某个通知(notify)
5.程序调用了线程的suspend方法,将线程挂起(容易导致死锁,避免使用)
⑤ 死亡状态:有三种情况会使线程进入死亡状态:
1.run方法执行完成,线程正常结束。
2.线程抛出未被捕获的exception或者error
3.人为停止,比如调用stop方法(暴力停止,不建议使用),以及其他停止策略。
6.线程中常用的方法
package com.multithreading.test;
//创建线程类,方法一
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String threadName,ThreadGroup tg) {
super(tg,threadName);
start();
}
//线程功能函数
public void run() {
for(int i=1;i<=5;i++) {
try {
Thread.sleep(500);
} catch (Exception e) {
System.out.println(e.toString());
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//创建线程的方式1
MyThread myThread = new MyThread();
myThread.start();
myThread.interruptAndIsInterruptedAndInterrupted();
//创建线程的方式2
MyThread3 MyThread3 = new MyThread3();
//线程1
Thread t1 = new Thread(MyThread3);
t1.start();
//线程2
Thread t2 = new Thread(MyThread3);
t2.start();
}
//1. start() 启动线程 线程方法
public static void starts() {
MyThread myThread= new MyThread();
myThread.start();
}
//2. run() 执行线程 线程方法
public static void runs() {
//开启线程之后,会自动运行run方法执行线程代码功能。如上上
MyThread myThread= new MyThread();
myThread.start();
}
//3. sleep() 指定的时间内休眠线程
public static void sleep() throws InterruptedException {
MyThread myThread= new MyThread();
myThread.start();
myThread.sleep(1000); //休眠一秒钟(多线程中不会释放对象锁,与wait的比较)
}
//4. currentThread() 返回对当前正在执行线程对象的引用 线程方法
public static void currentThreads() {
MyThread myThread= new MyThread();
myThread.setName("我是MyThread");
myThread.start();
Thread t = Thread.currentThread(); //主线程
System.out.println(myThread.getName());
}
//5. join() 实现线程的执行顺序 线程方法
public static void joins() {
MyThread myThread1= new MyThread();
myThread1.setName("myThread1");
MyThread myThread2= new MyThread();
myThread2.setName("myThread2");
MyThread myThread3= new MyThread();
myThread3.setName("myThread3");
myThread1.start();
try {
myThread1.join(1500); //myThread1优先执行1500毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
myThread2.start();
myThread3.start();
}
//6. setpriority() 设置线程优先级(1-10,默认为5,数字越大优先级越高,但不代表级别越高就一定优先执行,只是优先执行的概率更大)
//7. getPriority() 返回线程优先级级别
//另外还有三个常规优先级属性 MIN_PRIORITY设置最小优先级,值为1 MAX_PRIORITY设置最大优先级,值为10 NORM_PRIORITY普通优先级值为5
public static void setPriorityAndGetPriority() {
MyThread myThread1= new MyThread();
MyThread myThread2= new MyThread();
myThread1.setPriority(1);
myThread1.setName("myThread1");
System.out.println(myThread1.getPriority());
myThread2.setPriority(9);
myThread2.setName("myThread2");
System.out.println(myThread2.getPriority());
myThread1.start();
myThread2.start();
//myThread1.setPriority(Thread.MIN_PRIORITY);
//myThread1.setPriority(Thread.MAX_PRIORITY);
//myThread1.setPriority(Thread.NORM_PRIORITY);
}
//8. setName() 设置线程名称
//9. getName() 获取线程名称
public static void setNameAndGetName() {
//以上方法中已经多次应用
}
//返回线程编号
public static void geId() {
MyThread myThread= new MyThread();
myThread.getId();
System.out.println(myThread.getId());
}
//19. isAlive() 测试线程是否活着 线程方法
public static void isAlives() {
MyThread myThread= new MyThread();
myThread.start();
System.out.println(myThread.isAlive());
}
//11. yield() Thread类的yield()方法导致当前正在执行的线程对象暂停并允许其他线程执行(其实就是降低优先级,但不代表不会先执行)
public static void yield() {
MyThread myThread1= new MyThread();
myThread1.setName("myThread1");
MyThread myThread2= new MyThread();
myThread2.setName("myThread2");
myThread1.start();
myThread2.start();
for(int i=0;i<3;i++) {
//如果myThread1线程正在执行,则执行该方法,意味着自己不运行run,重新回到就绪状态与其他线程一同竞争cpu时间
myThread1.yield();
System.out.println(myThread1.getName()+":我们一同竞争吧");
}
}
//12. stop() 暴力停止线程,强制中断线程的执行。容易造成数据不一致,属于不安全的方法,包括suspend()(阻塞线程) resume()(恢复线程) 等已经弃用
public static void stops() {
//略
}
//13. destroy() 销毁线程组以及子线程组
//ThreadGroup 线程组类,管理线程的类,即线程组就是由线程组成的管理线程的类,这个类是java.lang.ThreadGroup类
public static void destroys() throws InterruptedException {
//创建一个名为Parent thread的线程组
ThreadGroup g1 = new ThreadGroup("Parent thread");
//创建一个名为Child thread的线程组
ThreadGroup g2 = new ThreadGroup("Child thread");
//在线程组中创建线程,在有参构造函数已经启动了线程
MyThread myThread1= new MyThread("myThread1",g1);
MyThread myThread2= new MyThread("myThread2",g2);
myThread1.join();
myThread2.join();
g1.destroy();
System.out.println(g1.getName()+" is destoried");
g2.destroy();
System.out.println(g2.getName()+" is destoried");
}
// 14. isDaemon() 该线程是否是一个守护线程
// 15. setDaemon() 将线程标记为守护线程或用户线程
public static void isDaemons() {
MyThread myThread= new MyThread();
System.out.println(myThread.isDaemon());
myThread.setDaemon(true);
System.out.println(myThread.isDaemon());
//守护线程与用户线程(非守护线程)
//守护线程是为用户线程服务的,比如垃圾回收线程,非必要线程。当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程
//用户线程就是执行业务功能代码的线程。
}
//16. interrupt() 标记需要中断的线程,中断的执行由具体实现代码决定
//17. isInterrupted() 用来判断当前线程的中断状态
//18. interrupted() 此方法清除线程的中断状态,这意味着如果要连续两次调用此方法,则第二次调用将返回false。 如果线程的中断状态为true,则此方法将状态设置为false
public void interruptAndIsInterruptedAndInterrupted() {
MyThread2 myThread= new MyThread2();
myThread.start();
System.out.println(myThread.isAlive());
myThread.interrupt();
System.out.println(myThread.isAlive());
}
class MyThread2 extends Thread{
public void run() {
for(int i=1;i<=5;i++) {
try {
//为true时,说明被标记为需要中断的线程,然后通过return直接返回来实现中断线程
if(Thread.currentThread().isInterrupted()) {
return;
}
} catch (Exception e) {
System.out.println(e.toString());
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
//19. notify() 随机唤醒wait等待池中的线程,从而可以去竞争获取对象锁
//20. notifyAll() 唤醒所有wait等待池中的线程
public static void notifyAndNotifyAll() {
//略
}
//21. wait 使线程进行阻塞状态,并且会释放锁
public static void waits() {
//略
}
}
//创建线程方法2,实现rnnable接口
class MyThread3 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
7.monitor(监视器)
sychronized、wait、notify是任何对象都具有的同步工具。
他们是应用于同步问题的人工线程调度工具。
java中的每一个对象都有一个监视器来检测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在
sychronized范围内,监视器发挥作用。
wait/notify必须存在于sychronized块中。并且这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着
wait之后,其他线程就可以进入同步块执行。
当某代码并不持有监视器的使用权时(如上图中5的状态,即脱离同步块)去wait或notify,会抛出java.lang.IllegalMo
nitorStateException。也包括在sychronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会
抛出此异常。
用法:
sychronized单独使用:
① 代码块:如下,在多线程环境下,sychronized块中的方法获取lock实例的monitor,如果实例相同,那么只有一个线程
可以执行该代码块。
public class Thread1 implements Runnable {
Object lock;
public void run() {
synchronized(lock){
..do something
}
}
}
② 直接用于方法:相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的
是static方法,则锁定该类所有实例。
public class Thread1 implements Runnable {
public synchronized void run() {
..do something
}
}
sychronized、wait、notify结合:典型场景生产者消费者问题
/**
* 生产者生产出来的产品交给店员
*/
public synchronized void produce()
{
if(this.product >= MAX_PRODUCT)
{
try
{
wait();
System.out.println("产品已满,请稍候再生产");
}
catch(InterruptedException e)
{
e.printStackTrace();
}
return;
}
this.product++;
System.out.println("生产者生产第" + this.product + "个产品.");
notifyAll(); //通知等待区的消费者可以取出产品了
}
/**
* 消费者从店员取产品
*/
public synchronized void consume()
{
if(this.product <= MIN_PRODUCT)
{
try
{
wait();
System.out.println("缺货,稍候再取");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return;
}
System.out.println("消费者取走了第" + this.product + "个产品.");
this.product--;
notifyAll(); //通知等待去的生产者可以生产产品了
}
volatile: https://blog.youkuaiyun.com/weixin_41690908/article/details/86771819
8.高级多线程控制类
Java1.5提供了一个非常高效实用的多线程包:java.util.concurrent,提供了大量高级工具,可以帮助开发者编写高效、
易维护、结构清晰的Java多线程程序。
①ThreadLocal类
用处: 保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线
程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session
信息。
实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里
放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。
主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容
器。
②原子类(AtomicInteger、AtomicBoolean……)
为八大基本数据类型配置了对应的原子类。。。
③lock类
lock : 在java.util.concurrent包内。共有三个实现:
ReentrantLock ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock
主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有
一些区别。
lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序) 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。 本质上和监视器锁(即synchronized是一样的) 能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。 和Condition类的结合。 性能更高,对比如下图:
synchronized和Lock性能对比
ReentrantLock
可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。
使用方法是:
先new一个实例 static ReentrantLock r = new ReentrantLock();
加锁:r.lock()或r.lockInterruptibly();
两种加锁方式也是不同,后者可被打断。当a线程lock后,b线程阻塞,此时如果是lockinterruptibly,
那么在调用b.interrupt()之后,b线程退出阻塞,并放弃对资源的争夺,进入catch块(如果使用后者,
必须throw interruptable exception 或catch)
释放锁:r.unlock()
放在finally里面。以防止异常跳出了正常流程,导致灾难。这里补充一个小知识点,finally是可以
信任的:经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。
ReentrantReadWriteLock
可重入读写锁(读写锁的一个实现)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock() ReadLock r = lock.readLock(); WriteLock w = lock.writeLock();
两者都有lock,unlock方法。写写,写读互斥;读读不互斥。可以实现并发读的高效线程安全代码。
9.容器类
这里就讨论比较常用的两个:BlockQueue和ConcurrentHashMap
① BlockQueue
阻塞队列。该类是java.util.concurrent包下的重要类,BlockQueue是单向队列,可以在队列头部添加元素
或者在队列尾部删除取出元素类似于一个管道,特别适用于先进先出策略的一些应用场景。普通的queue
接口主要实现有PriorityQueue(优先队列)
BlockingQueue在队列的基础上添加了多线程协作的功能:
除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer
和poll。put会在队列满的时候阻塞,直到有空间时被唤醒,take在队列空的时候阻塞,直到有东西可以被拿
的时候才被唤醒。用于生产者-消费者模型尤其好用。
常见的阻塞队列有:
ArrayListBlockingQueue
LinkedListBlockingQueue
DelayQueue
sychronizedBlockingqueue
② ConcurrentHashMap
高效的线程安全哈希map。对比hashTable、concurrentHashMap、HashMap
10.管理类
管理类的概念比较范,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的一些工具做一些封装。
值得一提的管理类:ThreadPoolExecutor和JMX框架下的系统级管理类ThreadMXBean。
ThreadPoolExecutor
ExecutorService e = Executors.newCachedThreadPool();
ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool(3);
// 第一种是可变大小线程池,按照任务数来分配线程,
// 第二种是单线程池,相当于FixedThreadPool(1)
// 第三种是固定大小线程池。
// 然后运行
e.execute(new MyRunnableImpl());
该类内部是通过ThreadPoolExecutor实现的,掌握该类有助于线程池的管理,本质上他们都是ThreadpoolExecutor
类的各种实现版本。
以上各参数:
corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。
maximumPoolSize:线程最大值,线程的增长始终不会超过该值。
keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态
unit:
时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS
workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving)
threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。