1. 线程简介
多线程——多任务
操作系统中运行的程序就是进程(progress)。
一个进程可以有多个线程(Thread)。
很多多线程都是模拟出来的,在一个cpu下,在同一个时间点,只能运行一个代码,因为切换的很快,所以有多线程同时执行的错觉。
对于同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
2. 创建线程
Thread类(也实现了Runnable接口)
Runnable接口
Thread类和Runnable接口。
2.1 继承Thread类、重写run方法
创建线程对象,调用start方法启动线程。
public class CreateThread extends Thread{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("###I am seeing code!"+i);
}
}
public static void main(String[] args) {
CreateThread test = new CreateThread();
test.start();//start方法,使两条线程交替执行
// test.run();
for (int i = 0; i < 2000; i++) {
System.out.println("I am studying Threads!"+i );
}
}
}
实战——利用多线程下载图片
/**
* 练习Thread,实现多线程下载图片
*/
public class Demo1 extends Thread{
private String url;
private String name;
public Demo1(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名"+name);
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1("http://n.sinaimg.cn/translate/63/w950h713/20191128/5ca6-iixntzz9340219.jpg","1.jpg");
Demo1 demo2 = new Demo1("http://n.sinaimg.cn/translate/63/w950h713/20191128/5ca6-iixntzz9340219.jpg","2.jpg");
Demo1 demo3 = new Demo1("http://n.sinaimg.cn/translate/63/w950h713/20191128/5ca6-iixntzz9340219.jpg","3.jpg");
demo1.start();
demo2.start();
demo3.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.println("IO异常,downloader方法出现问题");
}
}
}
线程是并发执行的
即图片下载完成的时间是随机的
2.2实现Runnable接口–java单继承方式,推荐使用
-
创建一个线程是声明一个类实现
Runnable
接口的其他方式。该类实现run
方法。那类的一个实例可以分配,作为一个参数传递Thread
时创建,并开始。 -
下面的代码将创建一个线程并开始运行:
PrimeRun p = new PrimeRun(143); new Thread(p).start();
实现:
public static void main(String[] args) {
CreateThread2 test = new CreateThread2();
new Thread(test).start();//需要作为一个参数放到Thread的构造器中 start方法,使两条线程交替执行
// test.run();
for (int i = 0; i < 20; i++) {
System.out.println("I am studying Threads!"+i );
}
}
//线程执行体
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("###I am seeing code!"+i);
}
}
实战二——购买火车票(多个线程操作同一个对象)
//多个线程同时操作一个对象
//买火车票——对象车票
public class Demo3 implements Runnable{
//票数
int numberTickets = 10;
//线程执行代码
public void run() {
while (true){
if (numberTickets<=0){
break;
}
//模拟延时
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了票"+numberTickets--);
}
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
//三个人同时抢票
new Thread(demo3,"小明").start();
new Thread(demo3,"小惠").start();
new Thread(demo3,"黄牛党").start();
}
}
结果——出现bug,线程冲突
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L3vsphcT-1605426609107)(C:\Users\26292\AppData\Roaming\Typora\typora-user-images\image-20201113171403241.png)]
实战三——龟兔赛跑
/**
* 模拟龟兔赛跑
*/
public class Demo4 implements Runnable{
//胜利者
private static String winner;
//线程实现主体
public void run() {
//乌龟和兔子的速度不同
if (Thread.currentThread().getName().equals("兔子")){
for (int i=0;i<=100;i=i+5){
//判断比赛是否结束
boolean flag = gameOver(i);
System.out.println(Thread.currentThread().getName()+"跑了"+(i+1)+"步");
if (i>90){
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (flag){
break;
}
}
}
if (Thread.currentThread().getName().equals("乌龟")){
for (int i = 0; i < 100; i++) {
//判断比赛是否结束
boolean flag = gameOver(i+1);
System.out.println(Thread.currentThread().getName()+"跑了"+(i+1)+"步");
if (flag){
break;
}
}
}
}
//判断游戏是否结束
public boolean gameOver(int step){
//判断是否由胜利者
if (winner!=null){
return true;
}else {
if (step>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Demo4 race = new Demo4();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
2.3实现Callable接口(了解即可)
@FunctionalInterface
public interface Callable<V>
返回结果的一个任务,并可能抛出异常。用户定义一个不带参数调用 call
的Callable
接口类似于Runnable
,,是专为其实例的类可能被另一个线程执行。然而,Runnable
,不返回结果并不能抛出异常。
Executors
类包含的实用方法与其他常见的形式转换为Callable
类。
public class CreateThread3 implements Callable<Boolean> {
//线程的执行体
public Boolean call() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+ "得到$"+i);
}
return true;
}
public static void main(String[] args) {
CreateThread3 c1 = new CreateThread3();
CreateThread3 c2 = new CreateThread3();
CreateThread3 c3 = new CreateThread3();
//1.创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//2.提交执行--相当于开启线程
Future<Boolean> r1 = ser.submit(c1);
Future<Boolean> r2 = ser.submit(c2);
// Future<Boolean> r3 = ser.submit(c3);
//3.获取结果
try {
boolean rs1 = r1.get();
boolean rs2 = r2.get();
// boolean rs3 = r3.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//4.关闭服务
ser.shutdownNow();
}
}
3 静态代理
真实角色:你
代理者:婚庆公司
接口:结婚
3.1 实例
/**
* 静态代理模式
*
* 真实对象
* 共同接口
* 代理对象
* 真实对象和代理对象实现同一个接口
* 代理对象要代理真实对象
*
* 好处
* 代理对象可以做很多真实对象做不了的事情
* 真实对象专注做自己的事情
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).HappyMarry();// new Thread(Runnable).start();
}
}
//共同接口
interface Marry{
void HappyMarry();
}
//真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("我愿意!");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry client;
public WeddingCompany(Marry client) {
this.client = client;
}
@Override
public void HappyMarry() {
//代理人
System.out.println("准备好会场");
client.HappyMarry();
System.out.println("收尾款");
}
}
3.2 线程中的应用
类Thread也实现了Runnable接口
class Thread implements Runnable
new Thread(Runnable).start();
4 Lamda 表达式
4.1Lamda表达式的优点
new Thread(()->System.out.println("多线程学习!")).start();
- 避免匿名内部类定义过多
- 可以使代码更简洁
- 去掉了一堆没有意义的代码
4.2 函数式接口
一个接口里只有唯一一个抽象方法
例如Runnable,Callable
@FunctionalInterface
public interface Runnable
Runnable
接口应该被任何类的实例的目的是通过一个线程执行的实施。类必须定义一个称为 run
此接口旨在为那些希望在它们活动时执行代码的对象提供一个通用的协议。例如,Runnable
通过类Thread
实施。激活的意思是说一个线程已启动并且尚未停止。
此外,Runnable
一类是主动的而不是子类Thread
提供手段。一个类实现Runnable
可以运行没有子Thread
实例化一个Thread
实例并将自身作为目标。在大多数情况下,这Runnable
接口应该是如果你只打算重写run()
方法并没有其他Thread
方法。这是重要的因为方法重写,继承类不应该继承除非程序员打算修改或增强类的基本行为。
4.3 应用
public class TestLambda {
//3.静态内部类
static class Like2 implements Ilike{
@Override
public void lambda() {
System.out.println("I like Lambda!静态类");
}
}
public static void main(String[] args) {
//4.局部内部类
class Like3 implements Ilike{
@Override
public void lambda() {
System.out.println("I like Lambda!局部内部类");
}
}
Ilike i = new Like();
i.lambda();
i = new Like2();
i.lambda();
i = new Like3();
i.lambda();
//5.匿名内部类
i = new Ilike() {
@Override
public void lambda() {
System.out.println("I like Lambda!匿名内部类");
}
};
i.lambda();
//6.用Lambda表达式
i = ()->{
System.out.println("I like Lambda!Lambda实现");
};
i.lambda();
}
}
//1.定义接口
interface Ilike{
void lambda();
}
//2.实现类
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("I like Lambda!");
}
}
5 线程的状态
public static enum Thread.State
extends Enum<Thread.State>
线程状态。一个线程可以有以下规定:
NEW
线程尚未开始。(刚创建)RUNNABLE
处于这种状态中的java虚拟机执行的线程。 (start)BLOCKED
线程阻塞等待监控锁在这个国家。WAITING
处于这种状态的线程被无限期地等待另一个线程来执行特定的动作。TIMED_WAITING
处于这种状态的线程正在等待另一个线程上执行一个动作指定的等待时间。(sleep)TERMINATED
处于这种状态的线程退出。 (线程结束)
一个线程可以在一个给定的时间点上只有一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
- 创建状态:new
- 就绪状态:
- 阻塞状态:sleep
- 运行状态
- 死亡状态
5.1.如何停止线程
不推荐使用stop和destroy方法
public class Test01 implements Runnable{
//1.线程正常停止
//2.推荐使用一个标志位
//3.尽量不使用stop和destroy方法
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) {
Test01 test01 = new Test01();
new Thread(test01).start();
for (int i = 0; i < 900000000; i++) {
if (i == 88880000){
System.out.println("main"+(++i));
//调用stop方法,切换标志位,使线程停止
test01.stop();
}
}
}
}
5.2.线程休眠
sleep方法
-
线程休眠
-
sleep时间到达后线程进入就绪状态
-
每个对象都有一个锁,sleep并不会释放锁
-
模拟网络延时–放大问题的发生性
-
模拟倒计时
5.2.1购票问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cirs7j1H-1605426609112)(C:\Users\26292\AppData\Roaming\Typora\typora-user-images\image-20201114215132529.png)]
5.2.2 打印倒计时
public class Test3 implements Runnable{
int number = 10;//剩余时间
@Override
public void run() {
for (int i=number;i>=0;i--){
if (i>0){
System.out.println("加油!还剩"+i+"秒");
}else {
System.out.println("时间到了");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test3 test3 = new Test3();
new Thread(test3).start();
}
}
5.3 线程礼让
yield方法:让当前正在执行的线程暂停,但不阻塞。
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功,看cpu心情
class TestYield{
public static void main(String[] args) {
Test4 test4 = new Test4();
new Thread(test4).start();
new Thread(test4).start();
}
}
public class Test4 implements Runnable{
int index = 0;
@Override
public void run() {
System.out.println(index+"线程开始执行");
Thread.yield();
System.out.println(index+"线程执行结束");
index++;
}
public static void main(String[] args) {
}
}
5.4 线程强制执行
join方法
- join合并线程,等待此线程执行完成后,再执行其他线程,其他线程阻塞。
- 可以类比与插队
- 区别于yield
public class Test5 implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程vip来了"+(++i));
}
}
public static void main(String[] args) throws InterruptedException {
Test5 test5 = new Test5();
//启动线程
Thread thread = new Thread(test5);
Thread thread2 = new Thread(test5);
thread.start();
//主线程
for (int i = 0; i < 100; i++) {
if (i==20){
thread.join();//非静态方法,需要对象调用
}
System.out.println("main"+i);
}
}
}
6 线程优先级(priority)
设置和获取优先级
public class Test1 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"----->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority,"小明");
Thread thread2 = new Thread(myPriority,"小花");
Thread thread3 = new Thread(myPriority,"小张");
Thread thread4 = new Thread(myPriority,"小李");
Thread thread5 = new Thread(myPriority);
Thread thread6 = new Thread(myPriority);
//先设置优先级,在启动,否则设置无效
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(5);
thread3.start();
thread4.setPriority(Thread.MAX_PRIORITY);
thread4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"----->"+Thread.currentThread().getPriority());
}
}
优先级高的不一定先执行,只是获得调度的概率变大了!!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-64yjbjBG-1605426609113)(C:\Users\26292\AppData\Roaming\Typora\typora-user-images\image-20201115153806571.png)]
性能倒置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H3ZJWe9D-1605426609114)(C:\Users\26292\AppData\Roaming\Typora\typora-user-images\image-20201115153935813.png)]
7 守护线程
线程分为用户线程和守护线程,虚拟机必须确保用户线程执行完毕。
虚拟机不用等待守护线程执行完毕。
public class Test1 {
public static void main(String[] args) {
God god = new God();//守护线程
People people = new People();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认为false,都是用户线程
Thread thread1 = new Thread(people);
thread.start();
thread1.start();
}
}
//上帝--一直存在
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你!");
}
}
}
//人类--有寿命长度
class People implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你第"+(i+1)+"天都开心的活着。");
}
System.out.println("-==========================goodbye!world!=====================-");
}
}
附录. Thread类的关键方法
currentThread()方法
public static Thread currentThread()
返回当前正在执行的线程对象的引用。
结果
当前执行线程。
sleep()方法
public static void sleep(long millis)
throws InterruptedException
当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性。线程不失去任何监视器的所有权。
-
参数
millis
-睡在毫秒的时间长度 -
异常
IllegalArgumentException
-如果millis
值为负InterruptedException
-如果任何线程中断了当前线程。当前线程的空中断状态被清除时,抛出该异常。