1、程序,进程和线程
-
程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
-
进程(Process):是程序的一次执行过程,他是一个动态的概念,是系统资源分配的单位
-
线程(Thread):是CPU调度和执行的单位
main()称之为主线程,为系统的入口,用于执行整个程序
2、创建线程的三中方式
-
继承Thread类:重写run()方法,编写线程执行体,创建线程对象,调用start()方法启动线程。但是
-
实现Runnable接口:重写run()方法,执行线程需要丢入runnable接口实现类,调用start方法。同时这种方法也避免了单继承的局限性,灵活方便,而且方便同一个对象被多个线程使用。
-
实现Callable接口(了解即可 ):重写call方法,抛出异常,需要返回值类型。使用时需要创建执行服务,然后提交执行,再获取结果,最后需要关闭服务等4个步骤。
3、静态代理(Thread类)
-
真实对象和代理对象都要实现同一接口,代理对象要代理真实角色,这样就可以使得真实对象专注于一件事情,而代理对象会把其他完善的事情做完
-
比如,我要结婚,然后找了一家婚庆公司,这样我就成了真实对象,婚庆公司就成了代理对象,而我仅仅需要结婚,其他事情就可以完全交给婚庆公司来完成了。
-
所以说多线程就使用了静态代理,Thread类就是代理对象,而它需要一个真实对象来作为参数,而且这个真实对象需要实现Runnable接口。
public class StaticProxy { public static void main(String[] args) { new WeddingCompary(new You()).marry(); } } interface Marry{ public void marry(); } //真实角色 class You implements Marry{ @Override public void marry() { System.out.println("我要结婚了,很开心!"); } } //代理角色 class WeddingCompary implements Marry{ private Marry target; public WeddingCompary(Marry target) { this.target = target; } @Override public void marry() { before(); this.target.marry(); after(); } private void after() { System.out.println("收拾残局,送客!"); } private void before() { System.out.println("布置现场,邀请嘉宾!"); } }
4、Lambda表达式
-
避免了匿名内部类定义过多,让代码看起来更简洁,去掉了一些没有意义的代码,只留下其核心逻辑,其实质属于函数式编程的概念
-
函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口,对于函数式接口,我们可以通过lambda表达式来创建接口的对象
-
lambda表达式的演化过程:由普通方式->静态内部类->局部内部类->匿名内部类->lambda表达式,如下:
public class LambdaTest { static class ILike2 implements Like{ @Override public void lambda() { System.out.println("lambda表达式2"); } } public static void main(String[] args) { //普通方式调用 Like like = new ILike(); like.lambda(); //静态内部类 like = new ILike2(); like.lambda(); //局部内部类 class ILike3 implements Like{ @Override public void lambda() { System.out.println("lambda表达式3"); } } like = new ILike3(); like.lambda(); //匿名内部类 like = new Like() { @Override public void lambda() { System.out.println("lambda表达式4"); } }; like.lambda(); //lambda表达式 like = ()-> System.out.println("lambda表达式5"); like.lambda(); } } //定义一个函数式接口 interface Like{ public abstract void lambda(); } //接口的实现类 class ILike implements Like{ @Override public void lambda() { System.out.println("lambda表达式"); } }
-
lambda表达式的简化:(前提必须是函数式接口)
-
如果有参数的话,可以省去参数类型,如果省去的话就要都去掉
-
如果有参数的话,可以省去包裹参数的括号;如果没有参数或者有多个参数的话不能省去括号
-
如果方法体里只有一句话的话可以省去花括号
-
5 、线程的状态
-
创建状态:通过new Thread()创建线程对象进入了新生状态,调用start方法进入就绪状态。
-
阻塞状态:当调用sleep、wait或同步锁时,线程进入阻塞状态,阻塞时间解除后,重新进入就绪状态,等待CPU调度执行
-
就绪状态:通过CPU调度进入运行状态
-
运行状态:在这个状态下,才真正执行线程中的代码
-
死亡状态:线程终端或者结束,进入死亡状态,就不能再次启动了
-
线程中常用的方法:
-
setpriority(int newPriority):更改线程的优先级
-
static void sleep(long millis):让当前正在执行的线程进入休眠
-
void join():等待该线程终止
-
static void yield():暂停当前线程对象,并执行其他线程
-
boolean isAlive():测试线程是否处于活动状态
-
-
停止线程
-
不推荐使用JDK提供的stop()、destroy()(过时)方法
-
推荐线程自己停下来
-
建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
-
-
线程休眠(sleep方法进入休眠)
每个对象都有一个锁,sleep不会释放锁
//使用sleep实现一个简单的计时器 public static void main(String[] args) { Date date = new Date(System.currentTimeMillis()); while (true){ try { String format = new SimpleDateFormat("HH:mm:ss").format(date); System.out.println(format); Thread.sleep(1000); date = new Date(System.currentTimeMillis()); //更新当前时间 } catch (InterruptedException e) { e.printStackTrace(); } } }
-
线程礼让
礼让线程,让当前正在执行的线程暂停,但不阻塞;将线程从运行状态转为就绪状态
//测试代码 public class TestYield { public static void main(String[] args) { MyYield yield = new MyYield(); new Thread(yield, "A").start(); new Thread(yield, "B").start(); } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName() + "线程执行结束"); } }
-
线程加入join:线程执行join方法后,会优先执行此线程,待此线程执行完毕后才执行其他线程
public class TestJoin implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } } public static void main(String[] args) { TestJoin testJoin = new TestJoin(); Thread vip = new Thread(testJoin, "vip"); vip.start(); for (int i = 0; i < 200; i++) { System.out.println("main:" + i); if(i == 90) { try { vip.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
-
追踪线程的状态
public class TestState { public static void main(String[] args) { Thread thread = new Thread(()->{ for (int i = 0; i < 3; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("================================"); }); Thread.State state = thread.getState(); System.out.println(state); thread.start(); state = thread.getState(); System.out.println(state); while (state != Thread.State.TERMINATED){ try { Thread.sleep(100); state = thread.getState(); System.out.println(state); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
线程优先级(1-10):优先级越高,执行的可能性就越大,但是不一定一定优先执行
使用getPriority()获取线程优先级,使用setPriority(int)设置线程优先级
public class TestPriority { public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority()); MyPriority priority = new MyPriority(); Thread thread1 = new Thread(priority, "1"); Thread thread2 = new Thread(priority, "2"); Thread thread3 = new Thread(priority, "3"); thread1.start(); thread2.setPriority(4); thread2.start(); thread3.setPriority(Thread.MAX_PRIORITY); thread3.start(); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority()); } }
-
守护线程:
线程分为用户线程和守护线程,默认创建的线程是用户线程,守护线程通过线程的setDaemon(true)方法来设置为守护线程。在jvm虚拟机中,虚拟机是不管守护线程的,只有用户线程存在时程序才会正常的运行下去
public class TestDaemon { public static void main(String[] args) { God god = new God(); Me me = new Me(); //设置该线程为守护线程 Thread thread = new Thread(god); thread.setDaemon(true); thread.start(); //启动用户线程 new Thread(me).start(); } } //守护线程 class God implements Runnable{ @Override public void run() { while (true){ System.out.println("苍天保佑你!"); } } } //用户线程 class Me implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("活着..."); } System.out.println("死去了"); } }
6、线程同步
-
当多个线程访问同一资源时,可能会引发一些安全问题,比如:火车站卖票问题,银行取钱问题等等,这些都可能伴随着安全问题,因此我们可以通过队列+所机制来解决该问题
//银行取钱可能会引发的线程不安全的代码 public class DrawMoney { public static void main(String[] args) { Account account = new Account(1000, "130925"); new Draw(account, 500, "我").start(); new Draw(account, 800, "女朋友").start(); } } class Account { private int money; private String name; public Account(int money, String name) { this.money = money; this.name = name; } public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } } //模拟在银行取款 class Draw extends Thread{ private Account account; private int drawMoney; public Draw(Account account, int drawMoney, String name){ super(name); this.account = account; this.drawMoney = drawMoney; } @Override public void run() { if((account.getMoney() - this.drawMoney) < 0){ System.out.println("余额不足," + this.getName() + "取钱失败!"); return; } //模拟延时,使得两个线程都能进行到这里 try { this.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney() - this.drawMoney); System.out.println(this.getName() + "取了" + this.drawMoney + "元"); System.out.println(account.getName() + "账户余额:" + account.getMoney() + "元"); } }
-
通过synchronized方法和synchronized块来解决该问题,但是这种锁机制也是有一定缺陷的,比如会影响性能,但是可以保证安全,正所谓鱼与熊掌不可兼得。
synchronized默认锁的是this对象,如果使用同步代码块的话,我们可以自定义锁的对象,一般情况下,我们锁的是共享的资源对象,如上案例中,我们就可以锁account对象
//还是用以上代码案例,我们只需要修改run方法中的代码即可,需要添加同步代码块,如下 @Override public void run() { synchronized (account){ if((account.getMoney() - this.drawMoney) < 0){ System.out.println("余额不足," + this.getName() + "取钱失败!"); return; } //模拟延时,使得两个线程都能进行到这里 try { this.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney() - this.drawMoney); System.out.println(this.getName() + "取了" + this.drawMoney + "元"); System.out.println(account.getName() + "账户余额:" + account.getMoney() + "元"); } }
7、死锁
-
多个线程互相抱着对方需要的资源,然后形成僵持。
-
产生死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
-
不剥夺条件:进城已获得的资源,在未使用完之前,不能强行剥夺
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
以上四个条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
-
public class DieLockTest { public static void main(String[] args) { new Thread(new MakeUp(0, "红太狼")).start(); new Thread(new MakeUp(7, "美羊羊")).start(); } } class MakeUp implements Runnable{ //定义镜子和口红 private static Mirror mirror = new Mirror(); private static Lipstick lipstick = new Lipstick(); private int choice; private String name; public MakeUp(int choice, String name){ this.choice = choice; this.name = name; } @Override public void run() { if(choice == 0){ synchronized (mirror){ System.out.println("获得镜子的锁"); try { Thread.sleep(1000); synchronized (lipstick){ System.out.println("获得口红的锁"); } } catch (InterruptedException e) { e.printStackTrace(); } } } else { synchronized (lipstick){ System.out.println("获得口红的锁"); try { Thread.sleep(1000); synchronized (mirror){ System.out.println("获得镜子的锁"); } } catch (InterruptedException e) { e.printStackTrace(); } } } } } class Mirror{ } class Lipstick{ }
8、Lock锁
-
从jdk5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当,它是juc包下的接口类(java.util.concurrent.locks.Lock),它是控制多个线程对共享资源进行访问的工具。ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实习爱你线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。
-
synchronized与Lock的对比:(ReentrantLock:可重用锁)
-
Lock是显示锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序:Lock > 同步代码块 > 同步方法
-
9、生产者消费者问题(线程通信)
-
这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间相互依赖,互为条件。在生产者消费者问题中,仅有synchronized是不够的,它只能保证线程同步,却不能解决线程通信。
-
JAVA提供了几个方法解决线程之间的通信问题
-
wait()方法:object类中的,与sleep不同,会释放锁
-
wait(long timeout):指定等待的毫秒数
-
notify():唤醒一个处于等待状态的线程
-
notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度
-
-
解决方法:
-
管程法: 生产者将生成好的数据放入缓冲区,消费者从缓冲区拿出数据
public class TestPC { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //定义生产者 class Productor extends Thread{ private SynContainer synContainer; public Productor(SynContainer synContainer){ this.synContainer = synContainer; } //生产者生产鸡 @Override public void run() { for (int i = 0; i < 100; i++) { synContainer.push(new Chicken(i)); System.out.println("生产者生产了第" + i + "只鸡"); } } } //定义消费者 class Consumer extends Thread{ private SynContainer synContainer; public Consumer(SynContainer synContainer){ this.synContainer = synContainer; } //消费者消费鸡 @Override public void run() { for (int i = 0; i < 100; i++) { // Chicken chicken = synContainer.pop(); System.out.println("消费者消费了第" + synContainer.pop().getId() + "只鸡"); } } } //定义产品 class Chicken{ private int id; public Chicken(int id) { this.id = id; } public int getId() { return id; } public void setId(int id) { this.id = id; } } //定义缓冲区 class SynContainer{ //需要一个容器大小 private Chicken[] chickens = new Chicken[10]; //容器计数器 private int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken){ //判断如果容器满了,则不能放入产品了,需要消费者先消费产品 if (count == chickens.length){ //通知消费者消费产品,自身进入阻塞状态 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满,则放入容器中 chickens[count] = chicken; count++; //通知消费者可以消费了 this.notifyAll(); } //消费者消费产品 public synchronized Chicken pop(){ //如果容器里没有鸡了,就要等待生产者生产鸡 if (count <= 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; Chicken chicken = chickens[count]; //通知生产者可以生产了 this.notifyAll(); return chicken; } }
-
信号灯法:
public class TestPC2 { public static void main(String[] args) { TV tv = new TV(); new Actor(tv).start(); new Watcher(tv).start(); } } //定义生产者==》演员 class Actor extends Thread{ private TV tv; public Actor(TV tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if(i % 2 == 0){ tv.play("哈哈哈" + i, "脱口秀" + i); } else { tv.play("不是所有牛奶都叫特仑苏", "广告" + i); } } } } //定义消费者==》观众 class Watcher extends Thread{ private TV tv; public Watcher(TV tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //定义产品==》节目 class TV{ private String voice; private String content; //标志位:TRUE:演员表演 // FALSE:观众观看 private boolean flag = true; public String getVoice() { return voice; } public void setVoice(String voice) { this.voice = voice; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } //演员表演 public synchronized void play(String voice, String content){ if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员发声:" + voice + ",表演了:" + content); //通知观众观看 this.notifyAll(); //更新表演的内容 this.voice = voice; this.content = content; //表演完成后设置标志位取反 this.flag = !this.flag; } //观众观看表演 public synchronized void watch(){ if(this.flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看了节目:" + this.voice + ",看到了:" + this.content); //通知演员表演 this.notifyAll(); //切换标志位 this.flag = !this.flag; } }
-
10、线程池
-
优点:
-
提高响应速度(大大减少了创建和销毁线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理
-
-
线程池的使用:jdk5.0起,提供了线程池相关API:ExecutorService和Executors
public class TestPool { public static void main(String[] args) { //创建线程池 ExecutorService service = Executors.newFixedThreadPool(10); //启动线程 service.execute(new Thread(new MThread())); service.execute(new Thread(new MThread())); service.execute(new Thread(new MThread())); //关闭链接 service.shutdown(); } } class MThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
该笔记是根据B站狂神老师的课程完成的