多线程 --- 小白学习笔记
多线程技术概述
线程与进程
进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少 有一个线程 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分 成若干个线程。
线程调度
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性), Java使用的为 抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使 用率更高。(比喻:线程就是岗位工作内容,进程就是工程项目岗位。如果全部人都围着一个工作去做那么大家都要去不停的换岗位去实现工程项目;但如果大家都分工合作,反而减少了岗位切换的次数,工作起来才会更快!)
同步与异步
同步: 排队执行,效率低但是安全。
(一个进程里n个线程排队执行,冲突就很少)
异步: 同时执行,效率高但是数据不安全。
(一个进程里n个线程同时执行,冲突容易发生)
并发与并行(面试重点)
并发: 指两个或多个事件在同一个时间段内发生。
(并发指的是在指定时间段内执行)
并行: 指两个或多个事件在同一时刻发生(同时发生)。
(并行指的是在那 一“刻” 时间同时执行)
下面时各种多线程的案例
Thread 实例
//Thread
public class Demo {
public static void main(String[] args) {
//下面两行时为了开启新任务(MyThread)
MyThread m = new MyThread();
m.start();
for(int i=0;i<10;i++){
System.out.println("叭叭叭叭叭叭"+i);
}
public class MyThread extends Thread{
//run方法就是线程要执行的任务方法
public void run(){
//这里的代码 就是一条新的执行路径
//这个执行路径的触发方式不是调用run方法,而是通过thread对象的start()来启动任务
for(int i=0;i<10;i++){
System.out.println("喵喵喵喵喵喵"+i);
}
}
}
//此方法是按线程抢栈的方式分配,有时能交替线程,有时会被其中一个方法优先读取完,结果也不稳定
Thread 的第二种用法(匿名内部类)
//Thread 的第二种用法(匿名内部类)
public class Demo2 {
public static void main(String[] args) {
//使用匿名内部类的方法实现线程的方式,还是继承了Thread
new Thread(){
public void run(){
for(int i=0;i<10;i++){
System.out.println("哒哒哒哒哒哒"+i);
}
}
}.start();
for (int i=0;i<10;i++){
System.out.println("嘎嘎嘎嘎嘎嘎"+i);
}
}
}
Runnable 实例
//Runnable
/*
*实现Runnable与继承Thread相比有如下优势
* 1. 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多线程同时执行相同任务的情况
* 2. 可以避免单继承所带来的局限性(java里面只有单继承但没有多继承,但是MyRunnable是实现类,java里面可以多实现)
* 3. 任务与线程本身是分离的,提高了程序的健壮性
* 4. 后续学习的线程池技术,接受Runnable类型的任务,但Thread类型的线程却不接受
*/
public class Demo {
//多线程技术练习
public static void main(String[] args) {
//1、创建一个任务对象
MyRunnable r = new MyRunnable();
//2、创建一个线程,并为其分配任务r
Thread t = new Thread(r);
//3、执行这个线程
t.start();
for(int i=0;i<10;i++){
System.out.println("小声点"+i);
}
}
}
//用于给线程进行执行的任务
//实现接口runnable,实现以后需要重写他的抽象方法run
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程任务
for(int i=0;i<10;i++){
System.out.println("汪汪汪"+i);
}
}
}
线程休眠演示
//线程休眠(也属于线程阻塞)
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<=20;i++){
System.out.println(i);
//每5s休眠一次
Thread.sleep(5000);
}
}
}
线程中断和守护线程演示
public class Demo5 {
//线程的中断
//一个线程是一个独立的执行路径,他是否应该结束是由其自身决定
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable())
//直接创建的线程为用户线程
//线程:分为守护线程和用户线程
//用户线程(直接创建的线程都是用户线程):当一个进程不包括任何存货的用户线程时,进行结束
//守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动中断
//设置守护线程t1
t1.setDaemon(true);
t1.start();
for(int i=0;i<6;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
//因为Runnable父接口没有声明对异常的抛出,所以子不能声明比父更大的异常
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t1.interrupt();
//添加一个任务(线程)
static class MyRunnable implements Runnable{
//实现方法
@Override
public void run() {
for(int i=0;i<12;i++){
//接收当前线程名字并打印 + 打印声明多少次
System.out.println(Thread.currentThread().getName()+":"+i);
//因为Runnable父接口没有声明对异常的抛出,所以子不能声明比父更大的异常
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("不产生异常,但是main方法走完后该线程自动中断");
//假如该方法不加入return则不会中断,不过会走入该 catch块
return;
}
}
}
}
}
线程不安全处理
方法一:
public class Demo6 {
//解决线程不安全
//解决方案一:
// sychronized(锁对象){
//
// }
//注意:任何对象都可以作为锁的存在
public static void main(String[] args) {
Runnable run = new Demo6.Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//设置总票数
private int count = 20;
//创建锁对象o,
private Object o = new Object();
@Override
public void run() {
while(true) {
//创建同步代码块并传入锁对象
synchronized (o) {
//如果票数大于0打印
if (count > 0) {
System.out.println(Thread.currentThread().getName()+"正在准备卖票");
try {
//每秒休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票" + count);
} else {
break;
}
}
}
}
}
}
方法二:
//同样的主方法
static class Ticket implements Runnable {
//设置总票数
private int count = 20;
@Override
public void run() {
//这里的锁可以比喻为试衣间外的大门锁,就是说每个试衣间本身有一把琐,然后在试衣间外部也有一栋大门锁,如果大门锁一关上其他试衣间也不能进去
synchronized (this){
}
while (true) {
sale();
boolean flag = sale();
//如果flag就跳出循环
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
//如果count > 0进入循环
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "正在准备卖票");
try {
//每秒休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票" + count);
return true;
}else{
return false;
}
}
}
方法三:
//线程安全三 --- 显示锁
//显示锁 Lock 子类 ReentranLock
//同样的主方法
static class Ticket implements Runnable {
//设置总票数
private int count = 20;
//创建显示锁,并取名为l
//这里ReentrantLock可以传参数(true或者false)不传的时候默认是false,false对应的就是不公平锁,true对应的是公平锁
private Lock l = new ReentrantLock(true);
@Override
public void run() {
while (true) {
//上锁
l.lock();
if(count>0){
//买票
System.out.println(Thread.currentThread().getName() + "正准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票剩余:"+count);
}else{
break;
}
//解锁
l.unlock();
}
}
}
隐式锁和显示锁的区别
参考网站:
https://www.cnblogs.com/kaigejava/p/12710602.html
sync和lock的区别
一:出身不同
从sync和lock的出身(原始的构成)来看看两者的不同。
Sync:Java中的关键字,是由JVM来维护的。是JVM层面的锁。
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁
sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。
而lock是通过调用对应的API方法来获取锁和释放锁的。
二:使用方式不同
Sync是隐式锁。Lock是显示锁
所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。
三:等待是否可中断
Sync是不可中断的。除非抛出异常或者正常运行完成
Lock可以中断的。中断方式:
1:调用设置超时方法tryLock(long timeout ,timeUnit unit)
2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
四:加锁的时候是否可以公平
Sync;非公平锁
lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
true:公平锁
false:非公平锁
五:锁绑定多个条件来condition
Sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
① 降低资源消耗。
② 提高响应速度。
③ 提高线程的可管理性
缓存线程池
缓存线程池休眠过后再执行线程的话不会执行新的线程,反而会执行空闲的线程而不是新建的线程
public class Demo10 {
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中假如新的任务 并新增传入新增线程1
service.execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"都挺好");
}
});
//向线程池中假如新的任务 并新增传入新增线程2
service.execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"都不好");
}
});
//向线程池中假如新的任务 并新增传入新增线程3
service.execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我很好");
}
});
//休眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//向线程池中假如新的任务 并新增传入新增线程3
service.execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我很好");
}
});
}
}
定长线程池
定长线程池在开头设置长度之后便不能扩容,如果线程数量大于定长线程池的长度后,多余的线程会重复使用线程池内的空余线程
public class Demo11 {
public static void main(String[] args) {
//创建定长线程池并设置线程长度为3
ExecutorService service = Executors.newFixedThreadPool(3);
//加入线程1
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":abcde");
try {//休眠2秒钟
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//加入线程2
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":xxxxx");
try {//休眠2秒钟
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//加入线程3
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":yyyyy");
try {//休眠2秒钟
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//因为定长线程池长度设置为3,设置3个线程后再加线程也不能扩容线程容量,后续的线程只能重复使用线程池内的空闲线程
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":zzzzz");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":zzzzz");
}
});
}
}
单线程线程池
public class Demo12 {
public static void main(String[] args) {
//创建单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//加入线程池1
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"CSNY");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"CSNY");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"CSNY");
}
});
//结论:单线程线程池只允许一个线程在里面,特点:无法扩容线程,不空闲则等待
}
}
周期定长线程池
public class Demo13 {
public static void main(String[] args) {
//创建周期定长线程池,定长2
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/*1.定时执行一次
*
* 参数一:定时执行任务
* 参数二:时长数字
* 参数三:时长数字单位,TimeUnit的常量指定
* */
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("国破山河在");
}//任务,5个单位后执行,单位为秒
},5, TimeUnit.SECONDS);
//创建周期性执行任务
/*
*周期星执行任务
* 参数一:任务
* 参数二:延期时长数字(第一次执行再什么时间以后)
* 参数三:周期时长数字(每隔多久执行一次)
* 参数四:时长数字单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("城春草木深");
}//任务,每5个单位后执行,时间间隔为1个单位后重复,单位为秒
},5,1,TimeUnit.SECONDS);
}
}