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)
// 周期性定时执行,传入工作,首次执行延时时长,周期距离时长和时长单位
最初
欢送关注公众号:前程有光,支付一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!