简介
看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件时间。
充分利用道路,加入多个车道
在操作系统中运行的程序就是进程。一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等。
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。进程则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。通常一个进程中可以包含若干个线程,一个进程中至少有一个线程(main线程)。线程是CPU调度和执行的单位。
很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
每个线程在自己的工作区间内交互,内存控制不当会造成数据不一致
线程创建
继承Thread类
线程不一定立即执行,CPU安排调度
执行顺序是主线程调用线程之后不一定立即执行,由CPU安排调度。安排调度后是两线程个随机交叉执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class TestThread01 extends Thread { @Override public void run () { for (int i = 0 ; i < 200 ; i++) { System.out.println("我在看代码------" + i); } } public static void main (String[] args) { TestThread01 testThread01 = new TestThread01(); testThread01.start(); for (int i = 0 ; i < 1000 ; i++) { System.out.println("我在学习多线程------" + i); } } }
1.创建lib项目,导入jar包
2.编写代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class TestThread02 extends Thread { private String url; private String name; public TestThread02 (String url, String name) { this .url = url; this .name = name; } @Override public void run () { WebDownloader1 webDownloader1 = new WebDownloader1(); webDownloader1.downloader(url, name); System.out.println("下载了文件名为" + name); } public static void main (String[] args) { TestThread02 t1 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png" , "1.png" ); TestThread02 t2 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png" , "2.png" ); TestThread02 t3 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png" , "3.png" ); t1.start(); t2.start(); t3.start(); } } class WebDownloader1 { public void downloader (String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现问题" ); } } }
实现Runnable接口
执行顺序是主线程调用线程之后不一定立即执行,由CPU安排调度。安排调度后是两个随机交叉执行
线程延迟增大了问题的发生概率
实现Callable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public class TestThreadCallable implements Callable <Boolean > { private String url; private String name; public TestThreadCallable (String url, String name) { this .url = url; this .name = name; } @Override public Boolean call () { WebDownloader1 webDownloader1 = new WebDownloader1(); webDownloader1.downloader(url, name); System.out.println("下载了文件名为" + name); return true ; } public static void main (String[] args) throws ExecutionException, InterruptedException { TestThreadCallable t1 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png" , "1.png" ); TestThreadCallable t2 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png" , "2.png" ); TestThreadCallable t3 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png" , "3.png" ); ExecutorService es = Executors.newFixedThreadPool(3 ); Future<Boolean> r1 = es.submit(t1); Future<Boolean> r2 = es.submit(t2); Future<Boolean> r3 = es.submit(t3); Boolean rs1 = r1.get(); Boolean rs2 = r2.get(); Boolean rs3 = r3.get(); es.shutdown(); } } class WebDownloader1 { public void downloader (String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现问题" ); } } }
线程方法
方法
说明
setPriority(int newPriority)
更改线程的优先级
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠
void join()
等待该线程终止
static void yield()
暂停当前正在执行的线程对象,并执行其他线程
void interrupt
中断线程(不使用这个方式)
boolean isAlive()
测试线程是否处于活动状态
线程停止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class TestStop implements Runnable { private boolean flag = true ; @Override public void run () { int i = 0 ; while (flag) { System.out.println("run.....Thread" + i++); } } 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 < 1000 ; i++) { System.out.println("main" +i); if (i == 900 ) { testStop.stop(); System.out.println("线程已经停止了" ); } } } }
线程休眠
sleep不会释放锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class TestSleep2 { public static void main (String[] args) throws InterruptedException { tenDown(); } public static void systemDate () { Date startTime = new Date(System.currentTimeMillis()); while (true ){ try { System.out.println(new SimpleDateFormat("HH:mm:ss" ).format(startTime)); Thread.sleep(1000 ); startTime = new Date(System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void tenDown () throws InterruptedException { int num = 10 ; while (true ){ Thread.sleep(1000 ); System.out.println(num--); if (num<=0 ){ break ; } } } }
线程礼让
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class TestYield { public static void main (String[] args) { MyYield yield = new MyYield(); new Thread(yield,"后裔" ).start(); new Thread(yield,"嫦娥" ).start(); } } class MyYield implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()+"线程开始执行" ); Thread.yield(); System.out.println(Thread.currentThread().getName()+"线程停止执行" ); } }
线程强制执行
线程状态
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期。
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run() ,此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程状态观测
线程优先级
优先级低只是意味着获得调度的概率低,并不会一定在优先级高之后被CPU调度。
守护线程
用户线程定义:平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
线程同步 多个线程操作同一个资源线程不安全
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
不安全的买票案例
同票:每个线程都有自己的工作空间,都与主存交互。将主存中的数据拷贝到工作区之后,还没或者正要进行将对数据的修改返回给主存时,此时的cpu调度了另一个线程对其进行同样的操作。导致拿到了同一张票。线程切换带来的原子性问题
负票:在票的临界处也就是最后一张票的时候,A线程被CPU调度执行进行买票,A线程延迟100毫秒。在这100毫秒的时间间隔里,线程B被CPU调度执行进行买票。此时的票数还是为1
没有return出去。然后B线程延迟,A线程延迟过后票数-1此时为0票。B线程延迟过后买票,票数-1此时0-1等于-1票。于是出现了负票。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class UnsafeBuyTicket { public static void main (String[] args) { BuyTicket ticket = new BuyTicket(); new Thread(ticket, "苦逼的我" ).start(); new Thread(ticket, "努力的你" ).start(); new Thread(ticket, "可恶的黄牛" ).start(); } } class BuyTicket implements Runnable { private int tickeNums = 10 ; boolean flag = true ; @Override public void run () { while (flag) { try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void buy () throws InterruptedException { if (tickeNums <= 0 ) { flag = false ; return ; } Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "拿到" + tickeNums-- + "票" ); } }
不安全的银行取钱 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 public class UnsafeBank { public static void main (String[] args) { Account account = new Account(100 , "积蓄" ); Drawing you = new Drawing(account, 50 , "你" ); Drawing girlFriend = new Drawing(account, 100 , "女朋友" ); you.start(); girlFriend.start(); } } class Account { int money; String name; public Account (int money, String name) { this .money = money; this .name = name; } } class Drawing extends Thread { Account account; int drawingMoney; int nowMoney; public Drawing (Account account,int drawingMoney,String name) { super (name); this .account = account; this .drawingMoney= drawingMoney; } @Override public void run () { if (account.money-drawingMoney<0 ){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了" ); return ; } try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } account.money = account.money-drawingMoney; nowMoney = nowMoney + drawingMoney; System.out.println(account.name+"余额为:" +account.money); System.out.println(this .getName()+"手里的钱:" +nowMoney); } }
案例:女朋友取款100,你取款50。银行卡余额100,却出现你两都取钱成功,而余额为负数的结果。
出现负数的解释同买票出现负数原因一样。在对余额进行操作时A线程延迟,此时B线程被CPU调度执行取钱,延迟。A线程取钱成功余额减去对应的数目(假设取钱50:100-50=50),B线程进行取钱成功余额减去对应的数目(假设取钱100:50-100=-50)。
线程不安全的集合
集合个数本来应该是10000的,出现了少了1个的原因是。当某个线程正在执行向集合添加元素的时候。同一时间CPU调度执行了另一个线程向集合添加元素,添加成功。此时刚才那个线程也向集合中添加元素,在list的同一位置,旧的元素值被新的覆盖。买票同票问题
同步方法
方法里面也有分只读和修改的代码块,这时候使用同步方法会浪费资源
改造不安全买票,使用synchronized
同步方法的同步监视器是默认是this
同步块
解决不安全的银行取钱案例。首先我们还是使用同步方法
说明我们并没有锁正确。原因是因为同步方法默认锁的是this对象,在这里也就是Drawing对象。而我们操控的是账户对象的值,所以导致并没有起作用。
使用同步块,对account对象进行锁定
还可以对当前账户的余额进行判断,从而优化我们的代码
解决线程不安全的集合。
同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可。 同步方法默认用this或者当前类class对象作为锁。 同步代码块可以选择以什么来加锁,比同步方法要更颗粒化,我们可以选择只同步会发生问题的部分代码而不是整个方法。
死锁
多个线程互相抱着对方需要的资源同时又请求对方的资源,然后形成僵持
程序一直在运行,僵持
如何解决死锁?很简单我们请求对方的资源的同时,放下手里的资源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class TestDeadLock { public static void main (String[] args) { MakeUp g1 = new MakeUp(0 , "灰姑凉" ); MakeUp g2 = new MakeUp(1 , "白雪公主" ); g1.start(); g2.start(); } } class Lipstick {} class Mirror {} class MakeUp extends Thread { static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice; String girlName; MakeUp(int choice, String girlName) { this .choice = choice; this .girlName = girlName; } @Override public void run () { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } private void makeup () throws InterruptedException { if (choice == 0 ) { synchronized (lipstick) { System.out.println(this .girlName + "获得口红的锁" ); Thread.sleep(1000 ); } synchronized (mirror) { System.out.println(this .girlName + "获得镜子的锁" ); Thread.sleep(1000 ); } } else { synchronized (mirror) { System.out.println(this .girlName + "获得镜子的锁" ); Thread.sleep(1000 ); } synchronized (lipstick) { System.out.println(this .girlName + "获得口红的锁" ); } } } }
Lock锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class TestLock { public static void main (String[] args) { TestLock2 testLock2 = new TestLock2(); new Thread(testLock2).start(); new Thread(testLock2).start(); new Thread(testLock2).start(); } } class TestLock2 implements Runnable { int ticketNums = 10 ; private final ReentrantLock lock = new ReentrantLock(); @Override public void run () { while (true ) { try { lock.lock(); if (ticketNums > 0 ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNums--); } else { break ; } } finally { lock.unlock(); } } } }
线程协作
方法名
作用
wait()
表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout)
指定等待的毫秒数
notify()
唤醒一个处于等待状态的线程
notifyAll()
唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度
管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 public class TestPC { public static void main (String[] args) { SynContainer container = new SynContainer(); new Product(container).start(); new Consumer(container).start(); } } class Product extends Thread { SynContainer container; public Product (SynContainer container) { this .container=container; } @Override public void run () { for (int i = 0 ; i < 100 ; i++) { container.push(new Chicken(i)); System.out.println("生产了" +1 +"只鸡" ); } } } class Consumer extends Thread { SynContainer container; public Consumer (SynContainer container) { this .container=container; } @Override public void run () { for (int i = 0 ; i < 100 ; i++) { Chicken chicken = container.pop(); System.out.println("消费了" +1 +"只鸡" ); } } } 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; } }
信号灯法
通过标志位来判断进行操作和等待操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 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; } }
线程池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestPool { public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(10 ); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.shutdown(); } } class MyThread implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()); } }