Java 多线程技术概述
介绍多线程之前要介绍线程,介绍线程则离不开进程。
首先 ,
进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;
线程:就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个进程。
多线程:一个进程中不只有一个线程。
一、线程与进程
进程:通俗来解释就是一个程序,一个 App,打开任务管理器可以看到当前电脑中正在运行的 进程 。
线程:一个 进程 中一般包含多个线程,打开任务管理器也可以看到当前电脑中正在运行的 线程 每个各自执行自己的任务来实现 进程 的运行,当一个 进程 中的最后一个 线程 结束时,整个 进程 就结束了。
线程的 6 种状态:
NEW(未启动的线程)
RUNNABLE(执行的线程)
BLOCKED(被阻塞的线程)
WAITING(无限期等待的线程)
TIMED_WAITING(有限期等待的线程)
TERMINATED(已结束的线程)
二、Java 中线程创建的三种方式
1. Thread 类 : 通过创建一个类来继承 Thread 类,在这个类中重写 run()方法,通过这个类创建的对象就是一个线程。
class MyThread extends Thread{ @Override public void run() { //执行的Java语句 }}
复制代码
public static void main(String[] args) { MyThread t = new MyThread(); //线程启动 t.start();}
复制代码
2. Runnable 接口 :通过创建一个类来实现 Runnable 接口,在这个类中重写 run()方法,通过这个类创建的对象是一个线程任务,我们将这个任务传给一个 Thread 对象即可执行这个线程任务。(推荐使用这种方式,传入一个 Runnable 任务即可执行线程,跟使用线程池有关)
class MyRunnable implements Runnable{ @Override public void run() { //执行的Java语句 }}
复制代码
public static void main(String[] args) { MyRunnable r = new MyRunnable1(); Thread t = new Thread(r); t.start();}
复制代码
3. Callable 接口 (很少用的方式):创建方式与 Runnable 相似。创建此类线程会产生一个返回值,如果主程序要获取这个返回值,则主程序会在 Callable 线程运行结束后再运行,不获取的话,则两个线程并行。
三、线程中一些常用的方法
Thread 的常用方法
1.String getName() //获取该线程的名字 2.void setName(String name) //设置该线程的名字 3.void start() //线程开始 4.static Thread currentThread() //获取当前线程对象 5.static void sleep(long millis) //让当前线程休眠,进入阻塞状态,传入的参数单位为毫秒 6.void setDaemon(boolean on) //将线程设置为守护线程或者用户线程 7.void interrupt() //中断此线程 8.int getPriority() //返回此线程的优先级 9.void setPriority(int newPriority) //更改此线程的优先级 10.Thread(Runnable target) //(构造方法)传入一个Runnable任务 11.Thread(String name) //(构造方法)为线程取一个名字
复制代码
1、interrupt 方法和 stop 方法
线程在运行的过程中两种中断线程的方法,一个是 stop ()方法,一个是 interrupt ()方法。
Sun 公司在 JDK1.2 版本的时候将 stop ()方法改为过时了,因为这种中断线程的方式是在外部强制的,这可能会导致在中断过程数据丢失,所以是不安全的。
使用 interrupt ()方法则是一种安全的方式,当在线程外部调用 interrupt ()方法时,JVM 会给运行的线程传递一个异常对象,当线程接收到异常对象时,线程就会终止。
public static void main(String[] args) { Thread t = new Thread(new MyRunnable()); t.start(); t.interrupt();}
复制代码
2、守护线程和用户线程的设置
void setDaemon(boolean on)
复制代码
使用 setDaemon ()方法可以将一个线程设置为守护线程或者用户线程(参数为 TRUE 时,是守护线程,参数为 FALSE 时为用户线程),一个线程被创建时默认是用户线程。
当用户线程和守护线程一起运行,当用户线程结束时,则守护线程就结束。
四、线程安全
线程异步:即多条线程同时执行一个任务时,这种情况下往往是出现数据错乱的情况,例如两个人同时对一个银行账户进行取钱,账户中有 10000 元,两个人同时取走 5000 元,结果账户中还剩余 5000 元。所以这种线程异步的情况是非常不安全的。
线程同步:即多条线程同时执行一个任务时,,当一个线程开始执行任务线程后,为这个任务加锁,其他线程等待次线程执行完任务线程后再进行抢夺执行任务的时间片。
1、实现线程安全的方法(以售票窗口买票为例)
1.1、synchronized 方法(显式锁)
同步代码块
当一个售票窗口在卖票时,其他窗口等待。
语法:synchronized (加锁对象){ //Java语句 }
复制代码
class Runnable implements Runnable{ private int count = 10; private Object o = new Object(); public void run() { while(true){ synchronized (o){ if(count>0){ System.out.println(Thread.currentThread().getName()+"买票中"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println("余票:"+count); }else{ break; } } } }}
复制代码
同步方法
当时用同步方法时,当前加锁的对象默认为当前对象 this
class Runnable implements Runnable{ private int count = 10; public void run() { while(true){ boolean flag = sale(); if(!flag){ break; } } }
public synchronized boolean sale(){ //判断票数是否大于0,是返回true,否返回false if(count>0){ System.out.println(Thread.currentThread().getName()+"买票中"); try { //此处休眠0.5秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //票卖出,票数减一 count--; System.out.println("余票:"+count); return true; }else{ return false; } }}
复制代码
1.2、Lock 方法(隐式锁)
使用 Lock 方法需要创建 Lock 对象,并在需要加锁是手动加锁,在需要解锁时手动解锁
class Runnable implements Runnable{ private int count = 10; private Lock l = new ReentrantLock(); public void run() { while(true){ l.lock(); if(count>0){ System.out.println(Thread.currentThread().getName()+"买票中"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } count--; System.out.println("余票:"+count); l.unlock(); }else{ l.unlock(); break; } } }}
复制代码
1.3、显式锁和隐式锁的区别
(1)Sync:Java 中的关键字,是由 JVM 来维护的。是 JVM 层面的锁。
(2)Lock:是 JDK5 以后才出现的具体的类。使用 lock 是调用对应的 API。是 API 层面的锁。
(3)在使用 sync 关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取和释放锁了。那是因为当 sync 代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync 是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
(4)在使用 lock 的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock 方法。需要配合 tyr/finaly 语句块来完成。
(5)Sync 是不可中断的。除非抛出异常或者正常运行完成
(6)Lock 可以中断的。
(7)Sync:非公平锁
(8)lock:两者都可以的。默认是非公平锁。ReentrantLock(boolean fair),true 是公平锁,false 是不公平锁
五、死锁
概述
A 和 B 两人分别进入试衣间 1 和试衣间 2,A 想等 B 出来后去试衣间 2,自己则在试衣间 1 中等待,B 想等 A 出来后去试衣间 1,自己则在试衣间 2 中等待,最终 2 个人都在等对方出来,但是对方都不出来,导致一直僵持。
public class DeadLockTest { public static void main(String[] args) { A a = new A(); B b = new B(); //子线程中需要和主线程中同样的A和B对象 R r = new R(a,b); Thread t = new Thread(r); t.start(); b.say(a); }}
class B{ public synchronized void say(A a){ System.out.println("BBBBB"); a.reply(); } public synchronized void reply(){ System.out.println("bbbbb"); }}
class A{ public synchronized void say(B b){ System.out.println("AAAAA"); b.reply(); } public synchronized void reply(){ System.out.println("aaaaa"); }}
class R implements Runnable{ private A a; private B b;
public R(A a, B b) { this.a = a; this.b = b; } public void run() { a.say(b); }}
复制代码
六、生产者与消费者
当厨师在做菜时,服务员休息状态,当厨师做完菜后,厨师休息,服务员端菜出去,等服务员端空盘子回来后,服务员继续休息,叫厨师继续做菜。依次循环
public class Test { public static void main(String[] args) { Food f = new Food(); Cook c = new Cook(f); Waiter w = new Waiter(f); //厨师线程 new Thread(c).start(); //服务员线程 new Thread(w).start(); }}
class Cook implements Runnable{ private Food f; public Cook(Food f) { this.f = f; }
@Override public void run() { for(int i = 0;i<10;i++){ f.cook(i); try { //此处休眠0.5秒 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }}
class Waiter implements Runnable{ private Food f; private boolean flag = true; public Waiter(Food f) { this.f = f; }
@Override public void run() { for(int i = 0;i<10;i++){ f.get(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }}
class Food{ private String name; private String tasty; private boolean flag = true;
public Food() { }
public Food(String name, String tasty) { this.name = name; this.tasty = tasty; }
public boolean isFlag() { return flag; }
public void setFlag(boolean flag) { this.flag = flag; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getTasty() { return tasty; }
public void setTasty(String tasty) { this.tasty = tasty; }
//厨师做菜 public synchronized void cook(int i){ if(flag){ if(i % 2 == 0){ this.setName("番茄炒蛋"); this.setTasty("咸"); }else{ this.setName("糖醋排骨"); this.setTasty("酸甜"); } System.out.println("厨师做菜:"+this.getName()+",味道:"+this.getTasty()); } flag = false; //唤醒其他线程 this.notifyAll(); try { //厨师线程休眠 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
}
//服务员端菜 public synchronized void get(){ if(!flag){ System.out.println("服务员出菜:"+this.getName()+",味道:"+this.getTasty()); } flag = true; //唤醒其他线程 this.notifyAll(); try { //服务员线程休眠 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }}
复制代码
七、线程池(了解)
概述
当程序中需要执行许多的内容很少的线程时,线程创建所花费的时间就会多于每次线程运行的所耗费的时间,就是导致程序的效率大大降低。使用线程池的方法就可以为程序节省下创建线程的时间。
线程池的种类
ExecutorService service = Executors.创建方法void execute(Runnable command) //指挥线程池执行任务
复制代码
1.缓存线程池
任务线程传入时自动分配线程,线程不够时自动创建新线程
Executors.newCachedThreadPool() //创建缓存线程池
复制代码
2.定长线程池
指定线程池线程的个数,任务线程传入时自动分配线程,线程不够时剩余任务线程排队等待线程池中的线程执行完毕
Executors.newFixedThreadPool(int nThreads) //创建定长线程池,传入线程池中线程的个数
复制代码
3.单线程线程池
线程池中只有一个线程,任务线程传入时自动分配线程,一个任务执行时剩余任务线程排队等待线程池中的线程执行完毕
Executors.newSingleThreadExecutor() //创建单线程线程池
复制代码
4.周期定长线程池
指定线程池线程的个数,任务线程传入时自动分配线程,可以设定任务线程第一次执行的延时时间和之后每次执行的间隔时间
Executors.newScheduledThreadPool(int corePoolSize) //创建周期定长线程池,传入线程池中线程的个数 service.schedule(Runnable command, long delay, TimeUnit unit) //线程定时执行,传入任务、时长和时长单位 service.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)//周期性定时执行,传入任务,初次执行延时时长,周期间隔时长和时长单位
复制代码
推荐
字节跳动总结的设计模式 PDF 火了,完整版开放分享
刷Github时发现了一本阿里大神的算法笔记!标星70.5K
如果能听懂这个网约车实战,哪怕接私活你都可以月入40K
为什么阿里巴巴的程序员成长速度这么快,看完他们的内部资料我懂了
程序员达到50W年薪所需要具备的知识体系。
关于【暴力递归算法】你所不知道的思路
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
关注公众号 『 Java 斗帝 』,不定期分享原创知识。
同时可以期待后续文章 ing🚀
评论