- 简介概念
- 线程的实现(重点)
- 线程状态
- 线程同步(重点)
- 线程通信
- 高级相关
线程
例如:
一、线程是什么?
电脑有多个应用,可以看成有多个进程,进程可以看做事程序(指令+数据)的一次执行过程(程序进入内存,通过CPU处理)。每个进程又有多个线程(进程的一个个任务,由CPU调度和执行)。main函数也是一个主线程。
二、线程的使用
1.线程的实现
Thread Runnable Callable
线程基本是通过继承 Thread 类 和 实现 Runnable 接口 Callable(了解即可)
-
继承 Thread 实现多线程:
代码如下(示例):
package priv.practice.thread;
public class CreateThread extends Thread{
//1.继承 Thread 类 2.重写run() 方法 3.调用 start() 方法
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++){
System.out.println(" extends Thread get multiThread!");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
CreateThread createThread=new CreateThread();
createThread.start();//打印5次
for(int i=0;i<500;i++){//主线程打印500次
System.out.println(" main method!"+i);
}
}
}
运行结果如下:
extends Thread get multiThread!
extends Thread get multiThread!
main method!0
extends Thread get multiThread!
extends Thread get multiThread!
extends Thread get multiThread!
main method!1
main method!2
main method!3
......
显然,两个线程是交替执行的。
-
通过 实现 Runnable 接口,重写 run方法,再使用new Thread(MyThread).run() 实现多线程,
class MyThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<900;i++){
System.out.println(i+" Runnable interface!");
}
}
}
在main方法中进行如下应用:
// TODO Auto-generated method stub
Thread thread = new Thread(new MyThread());
thread.start();
for(int i=0;i<900;i++){
System.out.println(i+" main thread !");
}
可见,MyThread 类 在基础 接口后,若想启动 start方法,要把自己new出来的对象作为参数传到Thread类中(这不是装饰器模式,实际上是代理模式),再经过Thread启动run方法。
public class TicketsSale implements Runnable{ private int ticketsNum = 10; public static void main(String[] args) { // TODO Auto-generated method stub TicketsSale tickets = new TicketsSale(); new Thread( tickets,"tom").start(); new Thread( tickets,"jack").start(); new Thread( tickets,"ds").start(); } @Override public void run() { // TODO Auto-generated method stub while(true){ if(ticketsNum<=0){ break; } System.out.println(Thread.currentThread().getName()+" -->拿到了第"+(ticketsNum--)+"张票被获取"); //延时 try { Thread.sleep(200); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
会出现多个人拿到同一张票。
并发 (complicated) e.g. 火车票出售
-
Callable 接口 (了解)
实现 Callable接口,实现其中的 call();方法,该方法有返回值。需要抛出异常。该种方式需要开启与 关闭服务,
2.静态代理模式
真实对象与代理对象 都要实现同一个接口,代理对象要代理真实的角色,就要将真实对象作为构造函数的参数传入,之后代理对象就可以调用原对象的方法,也可以独立做一些其他的额外的事情,做真实对象做不了的事情。
package priv.pattern.proxy;
//代理模式-静态代理
interface Marry {
void HappyMarry();
}
class Person implements Marry {
@Override
public void HappyMarry() {
// TODO Auto-generated method stub
System.out.println("this person is happy!");
}
}
class WeddingCompany implements Marry {
//WeddingCompany 婚庆公司
//婚庆公司 构造方法传入 Marry 类型对象,返回该类型对象,写入私有Marry
private Marry targetMarry;
public WeddingCompany (Marry targetMarry){
this.targetMarry=targetMarry;
}
@Override
public void HappyMarry() {
before();
// 调用 Person(Marry)类中的方法
this.targetMarry.HappyMarry();
after();
}
private void after() {
System.out.println("after marry mad!");
}
private void before(){
System.out.println("before marry happy!");
}
}
public class StaticProxy {
public static void main(String[] args) {
// TODO Auto-generated method stub
WeddingCompany company = new WeddingCompany(new Person());
company.HappyMarry();
}
}
通过多线程技术进行改造,如下:
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("replace by lamada");
}
}).start();
--------------------------------------------------------------------------
WeddingCompany company = new WeddingCompany(new Person());
company.HappyMarry();
显然,线程的实现就是使用了静态代理方式,Thread是代理类,Runnable是真实类(接口),代理类通过重写了真实接口的方法才可以启动。
-
lamda表达式
实现lamda表达式要满足接口只有一个而且接口只能有一个方法,参考静态内部类-->局部内部类 -->匿名内部类 的演化,实现lamda表达式。(Runnable接口内部仅有一个run方法,它就是函数式接口,在多线程中实现 lamda表达式非常重要且常用)。
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("replace by lamada");
}
}).start();//lamda style
new Thread( () ->System.out.println("replace by lamada");).start();
3. 线程的状态
sleep(每个对象都有一把锁,sleep不会释放锁)
join(插队)
yield(不一定成功) wait notify
线程同步(重点)
多个线程操作同一个资源(并发),既多个线程操作同一个资源。(每个对象都有一把锁,sleep不会释放锁)
通常使用线程队列(排队)+锁 的方式实现 同步(synchronized) 解决线程不安全的问题.。
但是这种方式会带来性能问题,多线程竞争会导致较多的 上下文切换 与 调度延时,优先级高的线程等待优先级低的线程也会引起优先级倒置,导致性能问题。
........................
int ticketsNum = 10;
Object lock = new Object();
@Override
public void run() {
while(true){
//延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (lock) {................
以上代码是售票例子中关于同步的应用,显然,synchronized 修饰的代码块就是要同步的部分,同时在()中的lock对象就是同步监视器。它的作用就是当线程访问synchronized修饰的代码块时,如果发现lock(同步监视器)已锁定,就等待,反之,即可访问该代码块。
写这段代码块时,开始将票的总数(tickets)写到了run()方法中,这样新建一个线程,该线程获取同步代码块运行,tickets 的原始值 没开启一个新线程就更新一次。synchronized修饰方法此处参考 多线程懒汉式单例模式(以后填坑....)。
-
死锁
死锁就是同步代码块和同步方法的嵌套。双方争夺对方持有的资源(互斥使用)。
死锁产生应满足4个条件:
- 资源互斥使用
- 不可抢占资源
- 占有且等待
- 循环等待
为避免死锁,破坏以上2.3.4条件均可破坏以避免死锁(参考银行间算法)。
main 方法 ....... // 首先建立两个资源 A B Object A = new Object(); Object B = new Object(); //thread 01 先拿 A资源 再拿B资源 new Thread(()->{ synchronized (A) { System.out.println("thread 01 get A! "); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //thread 01 get B synchronized (B) { System.out.println("thread 01 get B! "); } } } ).start(); //thread 02 先拿 B资源 再拿A资源 new Thread(()->{ synchronized (B) { System.out.println("thread 01 get B! "); try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //thread 01 get B synchronized (A) { System.out.println("thread 01 get A! "); } } } ).start(); ...
-
多线程通信
多个线程运行,任务不同,处理的资源一样。(与买票的多线程例子相比 ,多个线程任务相同(买票),处理的资源(车票)也相同)。 多线程通信实现对资源的处理,可以理解为一个线程进行输入,另一个线程进行输出。为了保证二者资源的一致性,资源类在外部建立后,可以通过使用构造方法传参的方式传入 输入/输出 线程中,线程再通过内部的run方法改变资源。为了线程安全问题,需要在输入输出的线程处理资源的代码块外都增加同步方法(synchronized)。需要注意的是由于输入输出的线程不是同一个线程,锁需要将 object 替换为 Resourse.class 或者 Resourse r 中 的 r。