多线程学习笔记

线程与进程

使用多线程提高程序的效率,每个线程互相独立运行,互不影响。

应用程序:可以执行的软件.

进程:就是正在运行的程序,是线程的集合,有独立的运行内存空间。在进程中一定有一个主线程,例如程序中的main函数

线程:就是正在独立运行的一个执行路径,一个独立的执行单元.

每个进程有自己独立的内存空间,一个线程的所有进程共享一个内存空间。

创建线程有哪些方式

1、继承Thread类,重写run方法。
2、实现Runnable接口,重写run方法,将Runnable的实现类实例作为参数创建线程。
3、实现callable接口

package thread;

public class Demo extends Thread{
    public static void main(String[] args) {
        new Demo().start();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"启动一个线程");
    }
}

package thread;

public class Demo1 implements Runnable{
    public static void main(String[] args) {
        Demo1 task = new Demo1();
        new Thread(task).start();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"启动一个线程");
    }
}

使用继承Thread类还是使用Runnable接口好?

1、使用实现Runnable接口好,原因是实现了接口还可以继续继承,继承了类就不能再继承了。
2、实现Runnable接口,可以将实现类的实例传给多个线程,增强了代码的健壮性。

守护线程与非守护线程

Java线程可以分为两种:守护线程和用户线程,我们直接创建的线程都是用户线程。
将一个线程设置为守护线程:

线程对象.setDaemon(true)

守护线程:当最后一个用户线程结束时,守护线程结束。
用户线程:用户自己创建的线程。如果主线程停止掉,不会影响用户线程,非守护线程。

Thread类常用方法

start() 启动线程。
currentThread() 获取当前线程对象
getID() 获取当前线程ID Thread-0 该编号从0开始
getName() 获取当前线程名字
sleep(long mill) 休眠线程
interrupt()设置中断标记,中断线程
setPriority()设置线程优先级,优先级数字越大,优先级越大,被优先调用的概率会大

join方法

join作用是让其他线程变为等待,t1.join(),让其他线程变为等待,直到当前t1线程执行完毕,才释放

thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如线程b中调用了线程a的join方法,直到a执行完毕,才会继续执行线程b
就是优先权的事

synchronized关键字

可以在任意对象及方法上加锁,而加锁的这段代码称为互斥区或临界值

线程安全

就是当多个线程访问某一个类(对象或方法)时,这个类(对象或方法)始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

为什么有线程安全问题?

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
同步代码块:
synchronized (锁对象) {
同步代码块
}

static class MyRunnable implements Runnable {
    private Object o = new Object();
    @Override
    public void run() {
        synchronized (o) {
            System.out.println(Thread.currentThread());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

同步方法

同步方法锁的是调用该方法的对象。
静态同步方法锁的是该方法所在的类。

public class Demo implements Runnable {
    public synchronized void fun(){
        System.out.println(Thread.currentThread());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        fun();
    }
}

显示锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo implements Runnable {

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        System.out.println(Thread.currentThread());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
    }
}

公平锁
公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁,类似于排队吃饭。

非公平锁:每个线程抢占锁的顺序不定,谁运气好,谁就获取到锁,和调用lock方法的先后顺序

Lock lock = new ReentrantLock(true);

6.多线程的几种状态
新建状态、准备状态、运行状态、阻塞状态、死亡状态
多线程学习笔记

6线程之间通信

概念
线程是操作系统中独立的个体,但这些个体不经过特殊处理就不能成为一个整体,线程间的通信就是成为整体的必用方式之一。

当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。
使用wait和notify方法实现线程间的通信
(注意这两个方法都是Object的方法,也就是说Java为所有的对象都提供了这两个方法)

wait和notify必须配合synchronized关键字使用
wait释放锁,notify不释放锁

线程池概念
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程
就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容
器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

线程池的优点

降低资源消耗。
提高响应速度。
提高线程的可管理性

Java中的四种线程池

1. 缓存线程池

**
  * 缓存线程池.
  * (长度无限制)
  * 执行流程:
  *   1. 判断线程池是否存在空闲线程
  *   2. 存在则使用
  *   3. 不存在,则创建线程 并放入线程池, 然后使用
  */
 ExecutorService service = Executors.newCachedThreadPool();
 //向线程池中 加入 新的任务
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });

2. 定长线程池

 /**
  * 定长线程池.
  * (长度是指定的数值)
  * 执行流程:
3. 单线程线程池
4. 周期性任务定长线程池
  *   1. 判断线程池是否存在空闲线程
  *   2. 存在则使用
  *   3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  *   4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
  */
 ExecutorService service = Executors.newFixedThreadPool(2);
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });
  1. 单线程线程池
效果与定长线程池 创建时传入数值1 效果一致.
 /**
  * 单线程线程池.
  * 执行流程:
  *   1. 判断线程池 的那个线程 是否空闲
  *   2. 空闲则使用
  *   4. 不空闲,则等待 池中的单个线程空闲后 使用
  */
 ExecutorService service = Executors.newSingleThreadExecutor();
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });
 service.execute(new Runnable() {
   @Override
   public void run() {
     System.out.println("线程的名称:"+Thread.currentThread().getName());
   }
 });
  1. 周期性任务定长线程池
public static void main(String[] args) {
 /**
  * 周期任务 定长线程池.
  * 执行流程:
  *   1. 判断线程池是否存在空闲线程
  *   2. 存在则使用
  *   3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
  *   4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
  *
  * 周期性任务执行时:
  *   定时执行, 当某个时机触发时, 自动执行某任务 .
   */
 ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
 /**
  * 定时执行
  * 参数1.  runnable类型的任务
  * 参数2.  时长数字
  * 参数3.  时长数字的单位
  */
 /*service.schedule(new Runnable() {
   @Override
   public void run() {
     System.out.println("俩人相视一笑~ 嘿嘿嘿");
   }
 },5,TimeUnit.SECONDS);
 */
 /**
  * 周期执行
  * 参数1.  runnable类型的任务
  * 参数2.  时长数字(延迟执行的时长)
  * 参数3.  周期时长(每次执行的间隔时间)
  * 参数4.  时长数字的单位
  */
 service.scheduleAtFixedRate(new Runnable() {
   @Override
   public void run() {
     System.out.println("俩人相视一笑~ 嘿嘿嘿");
   }
 },5,2,TimeUnit.SECONDS);
}
上一篇:【Java零基础】Java核心知识点之:JAVA 线程实现/创建方式


下一篇:多使用 CompletableFuture 提升接口性能