线程
process(进程)和thread(线程)
通常在进程中至少有一个线程,没有线程的进程是没有存在的意义,线程是CPU调度和执行的单位
1、线程就是独立的执行路径;
2、在程序运行时,即使没有自己创建线程,后台也会有多个线程。如 main()主线程(用户线程)、gc线程(守护线程);
3、main()称为主线程,是程序的入口,用于执行整个程序
4、在一个进程中开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的
5、对同一份资源进行操作时,会存在资源抢占的问题,需加入并发控制
6、线程会带来额外的开销,如CPU调度时间、并发控制开销
7、每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
三种创建线程的方式
继承Thread类(重点)
线程开启不一定立即执行,由cpu调度执行
Thread类本身也是继承了Runnable接口,在创建Thread类时我们也需要重写run()方法
//练习thread,实现多线程实现同步下载图片
public class TestThread2 implements Runnable {
private String url;//地址
private String file;//文件名
public TestThread2(String url,String file){
this.url = url;
this.file = file;
}
@Override
public void run() {
WebDownloader downloader = new WebDownloader();
downloader.downloader(url,file);
System.out.println("下载好了文件名为" + file + "的文件");
}
public static void main(String[] args) {
String route = "http://www.121down.com/attachment/soft/2017/0421/172538_97896706.jpg";
TestThread2 testThread1 = new TestThread2(route,"download1.jpg");
TestThread2 testThread2 = new TestThread2("http://img.itmop.com/upload/2017-7/20177311317108200.jpg","download2.jpg");
TestThread2 testThread3 = new TestThread2("https://img.hnheijing.com/uploadfile/2017/0418/20170418094503642.jpg","download3.jpg");
new Thread(testThread1).start();
new Thread(testThread2).start();
new Thread(testThread3).start();
}
class WebDownloader{
public void downloader(String url,String file){
try {
FileUtils.copyURLToFile(new URL(url),new File(file));
} catch (IOException e) {
e.printStackTrace();
System.out.println("io异常downloader");
}
}
}
}
实现Runnable接口(重点)
Runnable接口内只有一个方法run()方法,我们在实现run方法时仍然需要创建Thread类,使用Thread类的start()方法帮助我们去执行run()方法
在Java中一个类只能继承一个父类,但是在实现时能够实现多个接口,所以我们通常使用实现接口的方式去使用线程,因为单一的继承具有局限性,实现接口能实现
//创建线程方式二:实现runnable接口
public class TestRun1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("练习..." + i);
}
}
public static void main(String[] args) {
TestRun1 testRun1 = new TestRun1();
new Thread(testRun1).start();
for (int i = 0; i < 1500; i++) {
System.out.println("多线程跑起来..." + i);
}
}
}
实现callable接口(了解)
静态代理模式
总结:真实对象和代理对象都要实现同一个接口,代理对象要将实际对象作为属性
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany company = new WeddingCompany(new You());
company.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("结婚");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("结婚之后收尾款");
}
private void before() {
System.out.println("婚前,布置现场");
}
}
lambda表达式
- 理解Functional Interface(函数式接口)是lambda表达式的关键(函数式编程)
- 函数式接口的定义
- 任何接口,如果只包含一个抽象方法,那么它就是函数式接口
- 对于函数式接口,可以通过lambda表达式来创建该接口对象
//推导lambda表达式
public class LambdaTest {
//2、静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//3、局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();
//4、匿名内部类 没有类的名称,必须借助接口或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
//5、用lambda简化
like = ()-> System.out.println("i like lambda5");
like.lambda();
}
}
//定义一个函数式接口
interface ILike{
void lambda();
}
//1、实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
为什么要使用lambda表达式
- 避免匿名内部类定义过多
- 代码看起来更简洁
- 去掉没有意义的代码,只留下核心逻辑
public class LambdaTest2 {
public static void main(String[] args) {
ILove love = (int a) -> {System.out.println("i hate you -->" + a);};
love.love(11);
//1、简化去掉形参参数类型
love = (a)-> {System.out.println("i hate you -->" + a);};
//2、简化去掉括号
love = a-> {System.out.println("i hate you -->" + a);};
//3、去掉花括号(表达式只有一行时)
love = a -> System.out.println("i hate you -->" + a);
}
}
interface ILove{
void love(int a);
}
}
- 总结
- lambda表达式只有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块进行包裹。
- 前提是接口必须为函数式接口
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
线程停止(标志)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pxfdhV6E-1641525496492)(C:\Users\liyeqi\AppData\Roaming\Typora\typora-user-images\image-20210624134956472.png)]
- 建议线程正常停止—>利用次数不建议死循环
- 建议使用标志位—> 设置一个标志位
- 不要使用stop和destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable{
//1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run....Thread" + i++);
}
}
//2.设置一个公开的线程停止的方法停止线程,转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1500; i++) {
System.out.println("main" + i );
if (i == 1000){
//调用stop方法,切换标志位,让线程停止
testStop.stop();
System.out.println("该线程停止了...");
}
}
}
}
线程休眠(sleep)
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时
- 每一个对象都有一个锁,sleep不会释放锁
//模拟倒计时
public class TestSleep2 {
public static void main(String[] args) throws InterruptedException {
//TestSleep2.tenDown();
Date dateStart = new Date(System.currentTimeMillis());//获取系统当前时间
while (true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(dateStart));
dateStart = new Date(System.currentTimeMillis());//更新当前时间
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0){
break;
}
}
}
}
线程礼让(yield)
- 礼让线程,让当前执行的线程暂停,但不阻塞
- 将线程从运行状态转为阻塞状态
- 让CPU重新调度,礼让不一定成功
//测试礼让线程
//礼让不一定成功
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"小明").start();
new Thread(myYield,"小红").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合并线程,待此线程执行完毕之后,再执行其他线程,其他线程阻塞
- 可以想象成插队
//测试join方法public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("线程vip来了"); } } public static void main(String[] args) throws InterruptedException { //启动线程 TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); for (int i = 0; i < 1000; i++) { if (i == 200){ thread.join(); } System.out.println("main..." + i); } }}
观察线程状态
//观察测试线程状态public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { for (int i = 0; i < 5; 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);//Run //只要线程不终止,就一直输出下去 while(state != Thread.State.TERMINATED){ Thread.sleep(100); state = thread.getState(); System.out.println(state); } }}
线程的优先级
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有程序,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级按照数字来表示,范围从1~10(priority)
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY =10
- Thread.NORM_PRIORITY =5
使用以下方式获获取或者改变优先级
- .getPriority() .setPriority()
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这些看cpu的调度
优先级的调度建议在start()之前调度
public class TestPriority { public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); t1.start(); t2.setPriority(Thread.MIN_PRIORITY); t2.start(); t3.setPriority(4); t3.start(); t4.setPriority(Thread.MAX_PRIORITY); t4.start(); t5.setPriority(-1); t5.start(); }}class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); }}
守护线程
- 线程分为用户线和守护线
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
//测试守护线程(用户线程结束,不用管守护线程是否结束即可停止)public class TestDeamon { public static void main(String[] args) { Parents parents = new Parents(); YouA you = new YouA(); Thread thread = new Thread(parents); thread.setDaemon(true);//默认false,表示用户线程,正常的线程都是用户线程 thread.start();//父母守护线程启动 new Thread(you).start(); }}class YouA implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("开心的活着"); } System.out.println("=======goodbye! world!========== "); }}class Parents implements Runnable{ @Override public void run() { while(true){ System.out.println("父母守护着我"); } }}
线程同步机制
- 并发 同一个对象被多个对象同时操作
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这些对象,这时候我们需要线程同步,线程同步其实是一种线程等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
形成条件:队列+锁
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题
一个线程持有锁会导致其他所有需要此锁的线程挂起
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致引起优先级倒置,引起性能问题
保证安全:synchronized方法和synchronized代码块
synchronized方法控制"对象"的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个方法申明为synchronized将会影响效率
同步方法
- synchronized关键字放在要加锁的方法前
同步块
-
同步块:synchronized(Obj){}
锁的对象就是变化的量,需要增删改的对象
-
Obj称之为同步监听器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
-
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
//测试juc安全类型的集合public class TestJUC { public static void main(String[] args) throws InterruptedException { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{list.add(Thread.currentThread().getName());}).start(); } Thread.sleep(2000); System.out.println(list.size()); }}
死锁
- 多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一同步块同时拥有"两个以上对象的锁"时,就可能会发生死锁的问题
死锁避免方法
产生死锁的四个必要条件:
-
互斥条件:一个资源每次只能被一个进程使用。
-
请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放
-
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
我们只要想办法破坏以上四种方法任意一个或者多个条件就能避免发生死锁
Lock锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WjAOojZt-1641525496498)(C:\Users\liyeqi\AppData\Roaming\Typora\typora-user-images\image-20210629144743207.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ptlaSLU-1641525496502)(C:\Users\liyeqi\AppData\Roaming\Typora\typora-user-images\image-20210629145549575.png)]
//测试lock锁public class TestLock { public static void main(String[] args) { TestLock2 lock2 = new TestLock2(); new Thread(lock2).start(); new Thread(lock2).start(); new Thread(lock2).start(); }}class TestLock2 implements Runnable{ int ticketNum = 10; //定义lock锁 private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ lock.lock();//加锁 try{ if (ticketNum > 0){ try { Thread.sleep(1000); System.out.println(ticketNum--); } catch (InterruptedException e) { e.printStackTrace(); } }else { break; } }finally { //解锁 lock.unlock(); } } }}
线程协作
线程通信
-
java提供了几种方法解决线程之间通信得问题
方法名 作用 wait() 表示线程一直等待,直到其他线程通知,与sleep不同,wait会释放锁 wait(long timeout) 指定等待得毫秒数 notify() 唤醒一个处于等待状态的线程 notifyAll() 唤醒同一对象上所有调用wait()方法的线程,优先级别高的线程优先度高 注意:均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则抛出异常
管程法
//测试生产者消费者模型-->利用缓冲区解决//生产者、消费者、产品、缓冲区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{ SynContainer container; public Productor(SynContainer container){ this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { container.push(new Chicken(i)); System.out.println("生产了" + i + "只鸡"); } }}class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container){ this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了" + container.pop().id +"只鸡"); } }}//产品class Chicken { int id; public Chicken(int id){ this.id = id; }}//缓冲区class SynContainer{ //需要一个容器大小 Chicken[] chickens = new Chicken[10]; 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; }}
信号灯法
//测试生产者消费者问题2:信号灯法public class TestPC2 { public static void main(String[] args) { Tv tv = new Tv(); new Player(tv).start(); new Watcher(tv).start(); }}//生产者生产--->演员class Player extends Thread{ Tv tv; public Player(Tv tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i%2 == 0){ this.tv.play("<快乐大本营播放中>"); }else{ this.tv.play("抖音记录美好生活"); } } }}//消费者消费--->观众class Watcher extends Thread{ Tv tv; public Watcher(Tv tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } }}//产品--->节目class Tv{ //演员表演,观众等待 //观众观看,演员等待 String voice; boolean flag = true; //表演 public synchronized void play(String voice){ if (!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了" + voice); this.notifyAll(); this.voice = voice; this.flag = !this.flag; } //观看 public synchronized void watch(){ if (flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看了:" + voice); //通知演员表演 this.notifyAll(); this.flag = !this.flag; }}
线程池
背景:经常创建和销毁、使用量特别大得资源,比如并发情况下得线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maxinumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
jdk5.0起提供了线程池相关Api:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池得工厂类,用于创建并返回不同类型的线程池
public class TestPool { public static void main(String[] args) { //创建服务,创建线程池 //newFixedThreadPool 参数为:线程池的大小 ExecutorService service = Executors.newFixedThreadPool(10); //执行 service.execute(new MyPool()); //关闭连接 service.shutdown(); }}class MyPool implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() ); }}