一、线程的简介
Process与Thread
1、程序是指令和数据的有序集合,进程的一次执行过程,是一个动态的概念,是系统资源分配的单位
2、一个进程可以包含若干个线程,当一个进程中至少有一个线程,
3、线程是CPU调度和执行的单位
1、线程就是独立执行的路径
2、main()称之为主线程,为系统的入口,用于执行整个程序
3、在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的操作,4、先后顺序是不能人为的干预的。
5、对于同一份资源操作时,会存在资源抢夺的情况,需要加入并发控制
6、线程会带来额外的开销,如cpu调度时间,并发控制开销
7、每个线程在自己的工作内存交互,内存控制不当,会引起数据的不一致
run方法和start方法的对比
二、线程的实现(重点)
创建方法一:继承Thread
步骤:
1、自定义线程类继承Thread类
2、重写run()方法,编写线程执行体
3、创建线程对象,调用start()方法启动线程
//创建线程的方法一:继承Thread类,重写run()方法,调用start()方法开启线程//注意,线程开启不一定运行,由CPU调度public class TestThread1 extends Thread {//线程入口@Override//run方法属于子线程public void run() {//run方法体(线程体)for (int i = 0; i < 200; i++) {System.out.print("我在看代码------" + i);}}//main属于主线程public static void main(String[] args) {//创建线程对象TestThread1 testThread1 = new TestThread1();//调用start方法和调用run方法的区别//调用start(),线程是同时进行运行的//如果testThread1.run(),则先运行run在运行main()testThread1.start();for (int i = 0; i < 1000; i++) {System.out.print("我在学习多线程------" + i);}}}
//需要引用commons-io架包import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.URL;public class TestThread2 extends Thread {private String url;private String name;//构造器public TestThread2(String url, String name) {this.url = url;this.name = name;}@Overridepublic void run() {WebDownloader webDownloader = new WebDownloader();webDownloader.downloader(url, name);System.out.print("下载的文件名称为:" + name);}public static void main(String[] args) {String urltemp="https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF";TestThread2 testThread2 = new TestThread2(urltemp,"TESTNAME1");testThread2.start();}//下载器class WebDownloader{//下载方法public void downloader(String url,String name){try {FileUtils.copyURLToFile(new URL(url), new File(name));} catch (IOException e) {e.printStackTrace();System.out.print("IO异常,downloader方法出现问题");}}}}
创建方法二:定义MyRunnable类实现Runnable接口
步骤
1、定义MyRunnable类实现Runnable接口
2、实现run()方法,编写线程执行
3、创建线程对象,调用start()方法启动线程
推荐使用线程对象、
对比第一种方式,推荐这种,因为java单继承具有局限性,其实Thread也是实现类Runnable接口,
步骤
继承Thread类
| 继承Thread类 | 实现Runnable接口 |
|---|---|
| 子类继承Thread类具备多线程能能力 | 实现接口Runnable具有多线程能力 |
| 启动线程:子类对象。start() | 启动线程:传入目标对象+Thread对象。start() |
| 不建议使用,避免OOP单继承局限性 | 推荐使用,避免局限性,同一对象可被多个线程使用 |
public class TestThread3 implements Runnable {//创建线程的方式2:实现runnable,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法@Overridepublic void run() {for (int i = 0; i < 200; i++)System.out.print("我在看代码----" + i);}public static void main(String[] args) {//创建runnable接口的实现类对象TestThread3 testThread3=new TestThread3();//创建线程对象,通过线程对象来开启线程//Thread thread = new Thread(testThread3);//thread.start();Thread t1 = new Thread(testThread3);t1.start()//开启线程for (int i = 0; i < 300; i++)System.out.println("我在学习多线程" + i);}}
创建方法三:实现Callable接口(了解即可)
匿名内部类创建线程
匿名内部类方式实现线程的创建
作用:简化代码
把子类继承父类,重写父类方法,创建子类对象合成一步完成
把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:
new 父类/接口() {
重写父类方法
};
public static void main(String[] args) {//线程的父类是Thread//new MyThread().start();new Thread(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+ "-->"+i);}}}.start();//线程的接口Runnable r = new Runnable(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+ "-->"+"程序员");}}};new Thread(r).start();//接口的线程简化new Thread(new Runnable(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+ "-->"+"林枫");}}}).start();}
静态代理
静态代理,,模式终结
要求:
1、真实对象和代理对象要实现同一个接口
2、代理对象必要代理真实角色
静态代理实际上就是线程的实现原理
好处:
1、代理对象可以帮助真实对象做很多事情
2、真实对象可以专注做好自己的事情
package com.company;//静态代理,,模式终结//真是对象和代理对象又要实现同一个接口//代理对象必要要代理真实角色//好处://1、代理对象可以帮助真实对象做很多事情//2、真实对象可以专注做好自己的事情public class StaticProxy{public static void main(String[] args) {You you=new You();new Thread(new Runnable() {@Overridepublic void run() {System.out.println("我爱你");}}).start();WeddingCompany weddingCompany = new WeddingCompany(you);weddingCompany.HapplyMarry();}}//定义一个接口,让真实对象实现interface Marry {void HapplyMarry();}class You implements Marry{@Overridepublic void HapplyMarry(){System.out.println("张三要结婚");}}//代理角色,帮助你结婚class WeddingCompany implements Marry{//代理——真实目标private Marry target;public WeddingCompany(Marry target) {this.target = target;}@Overridepublic void HapplyMarry() {before();this.target.HapplyMarry();//这是真实对象after();}private void after(){System.out.println("结婚之后,收取尾款");}private void before(){System.out.println("结婚之前,布置现场");}}
Lamda表达式




public class TestLamda {public static void main(String[] args) {//使用匿名内部类Ilove love=new Ilove(){public void love(){System.out.println("i love you");}};love.love();}}interface Ilove {public void love();}
//使用lamda表达式代替public class TestLamda {public static void main(String[] args) {//使用匿名内部类ILove ilove = () -> {System.out.println("i love you");};ilove.love();}}interface ILove {void love();}
三、线程的状态
| NEW | 初始状态 | 尚未启动的线程处于此状态 |
|---|---|---|
| RUNNABLE | 运行状态 | 在JAVA虚拟机中执行的线程处于此状态,Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running) |
| BLOCKED | 阻塞 | 被阻塞等待监视器的线程处于此状态 |
| WAITING | 等待 | 在等待另一个线程执行动作到达指定等待时间的线程处于此状态 |
| TIMED WAITING | 超时状态 | 正在等待另一个线程执行动作到达指定等待时间 |
| TERMINATED | 终止 | 表示该线程已经执行完毕,已退出的线程处于此状态 |

几种方法的比较
- Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
- Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
- thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。
- obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
- obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。
停止线程
注意:
1、不推荐使用JDK提供的stop()、destroy()方法。已经弃用
2、推荐线程自己停下来
3、建议使用一个标志位进行终止变量,当flag=false,则终止线程运行import java.awt.*;public class TestStop implements Runnable {//1、设置一个标志位boolean flag=true;int i = 0;@Overridepublic void run() {while (flag) {System.out.println(i++);}}//2、设置一个公开的方法停止线程,转换标志位public void stopWhile() {this.flag = false;}public static void main(String[] args) {TestStop testStop = new TestStop();new Thread(testStop).start();for (int i = 0; i < 2; i++) {System.out.println("main"+i);if (i == 1) {//调用stopWhile方法切换标志位,使得线程停止testStop.stopWhile();System.out.println("线程停止了");}}}}
线程休眠
Sleep(time)方法,指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入倒计时就绪状态
- sleep可以模拟网络延时效果,倒计时等
- 每个对象都有一个锁,sleep不会释放锁
作用:
1、模拟网络延时
2、模拟倒计时
import java.text.SimpleDateFormat;import java.util.Date;import java.util.logging.SimpleFormatter;public class TestSleep2 {public static void main(String[] args) {//获取当前系统时间Date startTime = new Date(System.currentTimeMillis());while (true) {try {//模拟延时Thread.sleep(1000);System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));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;}}}}
线程的礼让
- 礼让线程,让当前的正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,需要根据CPU实际的调度
线程的强制执行
Jion()方法合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
可以想象成插队
线程状态的观测
Thread.State
线程状态,线程可以处于以下状态之一:
NEW
| NEW | 尚未启动的线程处于此状态 |
|---|---|
| RUNNABLE | 在JAVA虚拟机中执行的线程处于此状态 |
| BLOCKED | 被阻塞等待监视器的线程处于此状态 |
| WAITING | 在等待另一个线程执行动作到达指定等待时间的线程处于此状态 |
| TIMED WAITING | 正在等待另一个线程执行动作到达指定等待时间 |
| TERMINATED | 已退出的线程处于此状态 |
一个线程可以在给定的时间点处于一个状态,这些状态是不反映任何操作系统线程状态的虚拟机状态
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);//只要线程不终止while (state != Thread.State.TERMINATED) {Thread.sleep(100);state = thread.getState();//跟新线程状态System.out.println(state);}}}
线程的优先级
JAVA提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示。范围从1-10
| Thread. MIN_PRIORITY | 1 |
|---|---|
| Thread. MAX_PRIORITY = 10 | 10 |
| Thread. NORM_PRIORITY | 5 |
1、优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,取决于CPU调度
2、优先级的更改建议在start()之前
使用以下方法可以改变或获取优先级
getPriority().stePriority(int XXX);
优先级最大是10,最小是1,默认程序的优先级是5
//测试线程的优先级public class TestPriority implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());}public static void main(String[] args) {TestPriority testPriority = new TestPriority();Thread t1 = new Thread(testPriority,"t1");Thread t2 = new Thread(testPriority,"t2");Thread t3 = new Thread(testPriority,"t3");Thread t4 = new Thread(testPriority,"t4");Thread t5 = new Thread(testPriority,"t5");//设置优先级t2.setPriority(1);t3.setPriority(Thread.MAX_PRIORITY);t4.setPriority(6);t1.start();t2.start();t3.start();t4.start();t5.start();}}
守护线程
daemon,守护线程,
线程分为,用户线程和守护线程
1、虚拟机必须确保用户线程执行完毕
2、虚拟不用等待守护线程执行完毕
3、如后台记录操作日志,监控内存,垃圾回收等待等
四、线程的同步(重点)
龟兔赛跑
package com.company;public class Race implements Runnable {private static String winner;@Overridepublic void run(){//预设跑道长度是100米for (int i = 0; i <= 100; i++) {//判断比赛是否结束boolean flag = gameOver(i);if (flag){System.out.println(i);break;}System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"米");//模拟兔子睡觉if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}private boolean gameOver(int steps) {if (winner != null) {return true;}else if (steps >= 100) {winner = Thread.currentThread().getName();System.out.println(winner+"赢得比赛");return true;}return false;}public static void main(String[] args) {Race race = new Race();//new Thread(race,"乌龟").start();//乌龟的线程实例对象new Thread(race, "兔子").start();//兔子的线程实例对象}}
并发问题
不安全的买票
package com.company;public class SafeBank implements Runnable{private int ticketNums = 10;Boolean flag = true;//外部停止条件@Overridepublic void run() {while (flag){try {buy();} catch (InterruptedException e) {e.printStackTrace();}}}public void buy() throws InterruptedException {//判断是否售完票if (ticketNums == 0) {flag = false;return;}//模拟延时Thread.sleep(10);//输出抢到票的结果System.out.println(Thread.currentThread().getName()+"-->拿到了第" + ticketNums-- + "张票");}public static void main(String[] args) {SafeBank safeBank = new SafeBank();new Thread(safeBank,"张三").start();new Thread(safeBank, "黄牛").start();}}package com.company;//输出结果是有人拿到了同一张票public class TestThead2 implements Runnable {private int ticketNums = 10;@Overridepublic void run() {while (true) {//判断是否售完票if (ticketNums <= 0) {break;}//模拟延时try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//输出抢到票的结果System.out.println(Thread.currentThread().getName()+"-->拿到了第" + ticketNums-- + "张票");}}public static void main(String[] args) {TestThead2 testThead2 = new TestThead2();new Thread(testThead2,"张三").start();new Thread(testThead2, "黄牛").start();}}
不安全的数组
import java.util.ArrayList;import java.util.List;//不安全的数组public class UnsafeList {public static void main(String[] args) {final List<String> list = new ArrayList<String>();for (int i = 0; i < 2; i++) {new Thread(new Runnable() {@Overridepublic void run() {list.add(Thread.currentThread().getName());}}).start();}System.out.print(list.size());}}
不安全的银行取款
public class UnsafeBank {public static void main(String[] args) {//账户Accout accout = new Accout(100, "the fund");Drawing you=new Drawing(accout,50,"you");Drawing grilFrriend = new Drawing(accout, 100, "GF");you.start();grilFrriend.start();}}class Accout{int money;//余额String name;//卡名public Accout(int money, String name) {this.money = money;this.name = name;}}class Drawing extends Thread{Accout accout;//账户int drawingMoney;//取了多少钱int nowMoney;//现在手上有多少钱public Drawing(Accout accout, int drawingMoney, String name) {super(name);this.accout = accout;this.drawingMoney = drawingMoney;}//取钱@Overridepublic void run() {//判断有没有钱if (accout.money - drawingMoney < 0) {System.out.print(Thread.currentThread().getName() + "钱不够,取不了");return;}//卡内余额=余额-你取出来的钱accout.money = accout.money - drawingMoney;//你手里的钱nowMoney = nowMoney + drawingMoney;System.out.println(accout.name + "余额:" + accout.money);//Thread.currenthread().getName() 等价于 this.getName()System.out.println(this.getName()+"手里的钱");//Thread.currenthread().getName() 等价于 this.getName()System.out.println(this.getName());}}
队列和锁
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以只需要针对方法提出一套机制,这套机制就是synchroized关键字,包括两种方法:
synchronized关键字修饰和synchronized块
public synchronized void method(int args){}
//syhchronized方法控制对象的访问,每个对象对应一把锁,每个syhchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程就会阻塞,方法一旦执行,就独占该锁,知道方法返回才会释放锁,后面的被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法声明为synchroized将会影响效率,synchronized方法只锁定当前this对象,
安全的购票方式
package com.company;public class SafeBank implements Runnable{private int ticketNums = 10;Boolean flag = true;//外部停止条件@Overridepublic void run() {while (flag){try {buy();} catch (InterruptedException e) {e.printStackTrace();}}}public synchronized void buy() throws InterruptedException {//判断是否售完票if (ticketNums == 0) {flag = false;return;}//模拟延时Thread.sleep(10);//输出抢到票的结果System.out.println(Thread.currentThread().getName()+"-->拿到了第" + ticketNums-- + "张票");}public static void main(String[] args) {SafeBank safeBank = new SafeBank();new Thread(safeBank,"张三").start();new Thread(safeBank, "黄牛").start();}}
同步块
同步块:sychronized(Objr){}
Obj称之为同步监视器
obj可以是任意对象了,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法中同步监视器是this,就是对象本身,或者是claa(反射中讲解)
同步监视器的执行过程:
1、第一个线程访问,锁定同步监视器,执行其中的代码
2、第二个线程访问,发现同步监视器被锁定,无法访问
3、第一个线程访问结束,解锁同步监视器
4、第二个线程访问,发现同步监视器没有锁,然后锁定并继续访问
五、线程通信
六、高级主题
线程池
背景:经常创建喝销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中吗,使用时直接获取,使用完毕放回池中,可以避免频繁创建和销毁,实现重复利用,
好处:
- 提高响应速度,减少创建线程的时间
- 降低资源消耗
便于线程管理
JDK5.0,提供了线程相关的API:ExecutorService和Executors
- ExrcutorService:真正的线程池的接口,常见的子类ThreadPoolExecutor
- void



