线程与进程
- 进程 Process是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位
- 线程 thread是操作系统能够进行运算调度的最小单位。
- 一个进程里有多个线程
同步与异步
- 同步:排队执行,效率低但是安全(在时间上出现一致性与统一化的现象)
- 异步:同时执行,效率高但是不安全
并发与并行
- 并发:指两个或多个事件在同一个时间段内一起发生
- 并行:指两个或多个事件在同一时刻一起执行(同时执行)
多线程
-
启动多线程的两种方式:
- 继承Thread类
public class Demo extends Thread{ @Override public void run() { for(int i=0; i<10; i++) System.out.println("Demo:" + i); sleep(1000); } } public class Test { public static void main(String[] args) { // 创建多线程对象 Demo d1 = new Demo(); // 启动线程 d1.start(); } }
- 实现Runnable接口
public class Demo implements Runnable { @Override public void run() { for(int i=0; i<10; i++){ System.out.println("Demo: " + i + " name: " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Test { public static void main(String[] args) { Demo demo0 = new Demo(); new Thread(demo0).start(); } }
-
实现Runnable与继承Thread相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况。
- 可以避免单继承带来的局限性
- 任务与线程本身是分离的,提高了程序的健壮性
- 线程池技术,接受Runnable类型的对象,不接受Thread类型的对象
Thread
-
字段
static int MAX_PRIORITY 线程可以拥有的最大优先级。 static int NORM_PRIORITY 分配给线程的默认优先级。 static int MIN_PRIORITY 线程可以拥有的最低优先级。 -
常用构造方法
Thread() 分配新的 Thread对象。继承Thread方式 Thread(String name) 分配新的 Thread对象。继承Thread方式,指定线程名称 Thread(Runnable target) 分配新的 Thread对象。实现Runnable方式 Thread(Runnable target, String name) 分配新的 Thread对象。实现Runnable方式,指定线程名称 -
常用方法
static void sleep(long millis) 让线程休眠指定时间,单位毫秒具体取决于系统计时器和调度程序的精度和准确性 static void sleep(long millis, int nanos) 让线程休眠指定时间,参数1单位毫秒,参数2单位纳秒 void start() 启动线程 void interrupt() 外部发送中断某个线程的信号 long getId() 返回此Thread的标识符 String getName() 返回此线程的名称 Thread.State getState() 返回此线程的状态
线程相关概念讲解:
-
线程休眠sleep: 可传毫秒,毫秒+纳秒,延迟执行
-
**线程阻塞: ** 也称耗时操作,例如Scanner等待用户输入
-
线程的中断:
-
一个线程是一个独立的执行路径,它是否该结束,应该由它自身决定(例如操作IO资源,若是没有关闭被其他线程关闭,会导致资源不被释放)stop过时,会导致bug,应该使用interrupt()方法进行标记,线程内检测到标记会进入InterruptedException中断,处理完相关操作后,return自杀结束线程。
-
示例代码:
public class Demo implements Runnable { @Override public void run() { for(int i=0; i<10; i++) { try { System.out.println("Demo:" + i); Thread.sleep(1000,100); } catch (InterruptedException e) { System.out.println("发现中断标记,线程自杀"); return; } } } } public static void main(String[] args) throws InterruptedException { // 1. 创建一个任务 Demo d1 = new Demo(); // 2. 创建一个线程,并为其分配任务 Thread t = new Thread(d1); // 3. 启动线程 t.start(); Thread.sleep(1000); // 等待一秒钟之后,将d1线程杀死 t.interrupt(); }
-
-
守护线程
-
用户线程:当一个线程不包含任何存活的用户线程时,进行结束。
-
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。
-
应用场景:通常来说,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,守护线程是你的首选。
-
创建方法:使用 Thread 类的 setDaemon(true)方法可以将线程设置为守护线程,注意:在调用start()方法之前,调用这个方法,否则会抛出IllegalThreadStateException 异常
-
线程安全
多条线程并行操作同一块内存时,数据会出现混乱。
解决方案:核心思想,一个线程在操作数据时,让其他线程等待不能操作。
-
线程同步方案1:同步代码块(隐式锁)
-
线程同步:synchronized
格式:synchronized(锁对象){}
底层原理:线程在执行到synchronized语句时,会检测传入的锁对象是否有其他线程正在执行,有就等待,没有就执行,执行时又给锁对象添加上锁标记,防止其他线程执行。
注意:加入的锁对象必须是同一个对象,不能是每个线程新创建的。
public class Test { public static void main(String[] args) throws InterruptedException { Runnable ticket = new Ticket(); new Thread(ticket).start(); new Thread(ticket).start(); new Thread(ticket).start(); } } class Ticket implements Runnable{ private int ticket = 10; private Object o = new Object(); @Override public void run() { while(true){ synchronized(o){ if(ticket > 0){ System.out.println("正在卖票"); ticket--; System.out.println(Thread.currentThread().getName() + " 卖票成功,剩余票数:" + ticket); }else{ return; } } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
-
线程同步方案2:同步方法(隐式锁)
还是使用synchronized关键字,在方法返回值前加入synchronized关键字,和上同步代码块述传入锁对象类似,只是用法不同,在成员方法使用中,本质是锁this对象,静态方法使用,本质是锁对象.class对象。
示例代码:
public class Test { public static void main(String[] args) throws InterruptedException { Runnable ticket = new Ticket(); new Thread(ticket).start(); new Thread(ticket).start(); new Thread(ticket).start(); } } class Ticket implements Runnable{ private int ticket = 10; private Object o = new Object(); @Override public void run() { while(true){ if(!sellTicket()){ break; } } } public synchronized boolean sellTicket(){ if(ticket > 0){ System.out.println("正在卖票"); ticket--; System.out.println(Thread.currentThread().getName() + " 卖票成功,剩余票数:" + ticket); return true; }else{ return false; } } }
-
线程同步方案2:显示锁
自己创建Lock对象,对需要上锁的代码块前调用lock()方法,结束调用unlock()方法解锁。
示例代码:
public class Demo1 { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread t0 = new Thread(ticket); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); t0.start(); t1.start(); t2.start(); t3.start(); } } class Ticket implements Runnable{ private int tickets = 10; private Object o = new Object(); @Override public void run() { while(true){ if(!sellingTickets()) break; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } // 显示锁 Lock l = new ReentrantLock(); public boolean sellingTickets(){ l.lock(); try{ if(tickets > 0){ System.out.println(Thread.currentThread().getName() + ":即将卖票~"); tickets--; System.out.println(Thread.currentThread().getName() + ":卖出成功,剩余票数量:" + tickets); Thread.sleep(1000); } }catch (InterruptedException e) { e.printStackTrace(); }finally { l.unlock(); } if(tickets > 0){ return true; }else{ return false; } } }
-
隐式锁sync和显示锁lock的区别
一.出身不同
Sync是java中的关键字,由JVM来维护。
Lock是JDK1.5以后出现的具体的类。
二.使用方式不同
所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去加入锁和释放锁的操作。
Sync是由系统维护的,非逻辑的问题不会出现死锁。
Lock需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。
三.等待是否可中断
Sync是不可中断的,除非抛出异常或者正常运行完成。
Lock可以中断的。中断方式:1.在try中调用lock.lockInterruptibly()方法锁代码。2.使用lock.tryLock(time t, TimeUnit tunit)方法,超时会让其他等待的任务破锁而入。
四.加锁是否公平
Sync:非公平锁
Lock:默认非公平锁,构造方法传入true值成为公平锁
五.绑定多条个
Sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。
六.从性能比较
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁,就是每次不加锁而是假设没有冲突去完成某项操作,如果因为冲突失败就重试,知道成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,就会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
以上隐式锁sync和显示锁lock的区别参考文章出自:https://www.cnblogs.com/kaigejava/p/12710602.html
-
公平锁和非公平锁
公平锁:线程排队等待,先排队先执行Lock构造方法传入true就是公平锁
非公平所:线程不排队,锁解开随机随机执行等候的线
-
死锁
一个锁内部调用另一个可能产生锁的代码时,可能会造成死锁,避免方法就是锁内部不调用另一个产生锁的代码
线程间等待和唤醒
-
为了更高效的处理一些时间片短,任务量大的任务,我们可能会经常用到多线程。但是多线程的环境下,很容易出现线程并发问题,线程死锁就是很常见的一种并发问题。为了避免此类问题,我们会用到线程间的通信,而等待唤醒机制,就是线程间通信的一种形式。
-
常用方法:
void notify() | 唤醒正在此对象监视器上等待的单个线程 |
---|---|
void notifyAll() | 唤醒等待此对象监视器的所有线程 |
void wait() | 导致当前线程等待它被唤醒,通常是通知或中断 |
void wait(long timeoutMillis) | 导致当前线程等待它被唤醒,通常是通知或中断 ,或者直到经过一定量的实时 |
示例代码:
package com.xiaojumao;
public class Hotel {
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
}
class Cook extends Thread{
private Food f;
public Cook(Food f){
this.f = f;
}
@Override
public void run() {
for(int i=0; i<100; i++){
if(i%2==0){
f.setNameAndTaste("A","好吃到爆aaaaaaa");
}else{
f.setNameAndTaste("B", "差评一点不好吃");
}
}
}
}
class Waiter extends Thread{
private Food f;
public Waiter(Food f){
this.f = f;
}
@Override
public void run() {
for(int i=0; i<100; i++){
f.get();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Food{
private String name;
private String taste;
public synchronized void setNameAndTaste(String name, String taste){
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void get(){
System.out.println("服务员端走菜系名称:" + name + ",味道:" + taste);
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程的六种状态
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。TERMINATED
已退出的线程处于此状态。
线程创建的第三种方式:Callable
Runnable 与 Callable
接口定义 //Callable接口 public interface Callable<V> { V call() throws Exception; } //Runnable接口 public interface Runnable { public abstract void run(); }
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。
示例代码:
public class Demo2 { public static void main(String[] args) throws ExecutionException, InterruptedException { MyCallable myCallable = new MyCallable(); FutureTask<String> f = new FutureTask<>(myCallable); new Thread(f).start(); String res = f.get(); System.out.println("线程返回的内容:" + res); for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } static class MyCallable implements Callable<String> { @Override public String call() throws Exception { for (int i=0; i<10; i++) System.out.println(Thread.currentThread().getName() + ":" + i); return "来了老弟"; } } }
线程池
一、缓存线程池
长度无限制,执行流程:1.判断线程池是否存在空闲线程;2.存在则使用,不存在则创建线程,并放入线程池,然后使用
示例代码:
public static void main(String[] args) throws InterruptedException { // 1.创建线程池 ExecutorService executorService = Executors.newCachedThreadPool(); // 2.向线程池中添加任务 executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); System.out.println("等待三秒继续"); Thread.sleep(3000); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); } // 执行结果: // 等待三秒继续 // pool-1-thread-2:锄禾日当午 // pool-1-thread-3:锄禾日当午 // pool-1-thread-1:锄禾日当午 // --- 三秒后 --- // pool-1-thread-3:锄禾日当午
二、定长线程池
长度是指定的数值,执行流程:1.判断线程池是否存在空闲线程;2.存在则使用;3.不存在空闲线程,且线程池未满,则创建线程,并放入线程池,然后使用;4.不存在空闲线程池,且线程池已满,则等待线程池存在空闲线程。
示例代码:
public static void main(String[] args) throws InterruptedException { // 1.创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(2); // 2.向线程池中添加任务 executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); } // 执行结果: // pool-1-thread-2:锄禾日当午 // pool-1-thread-1:锄禾日当午 // pool-1-thread-2:锄禾日当午
三、单线程线程池
效果与定长线程池创建时传入数值1效果一致,执行流程:1.判断线程池的线程是否空闲;2.空闲则使用,不空闲则等待到空闲再使用。
示例代码:
public static void main(String[] args) throws InterruptedException { // 1.创建线程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); // 2.向线程池中添加任务 executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); executorService.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":锄禾日当午"); } }); } // 执行结果: // pool-1-thread-1:锄禾日当午 // pool-1-thread-1:锄禾日当午 // pool-1-thread-1:锄禾日当午
四、 周期性任务定长线程池
周期任务,定长线程池,执行流程:与定长线程池一致,只是多了定时执行功能。周期性任务执行时:定时执行,当某个时机触发时,自动执行某任务。
示例代码:
public static void main(String[] args) throws InterruptedException { /** * 定时执行 * 参数1: runnable类型的任务 * 参数2: 定时时长 * 参数3: 时间单位,TimeUnit类型,传入字段表示 */ // 1.创建线程池 ScheduledExecutorService service = Executors.newScheduledThreadPool(2); // 2.向线程池中添加任务 service.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ": 锄禾日当午"); } },1, TimeUnit.SECONDS); /** * 周期执行 * 参数1 : runnable类型的任务 * 参数2 : 定时时长(第一次执行的延迟时长) * 参数3 : 周期时长 * 参数4 : 时间单位 */ // 1.创建线程池 ScheduledExecutorService service = Executors.newScheduledThreadPool(2); // 2.向线程池中添加任务 service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ": 锄禾日当午"); } },1,3, TimeUnit.SECONDS); }