AI智能
改变未来

简述Java多线程(一)


JAVA多线程

程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程:是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位。

线程是CPU调度和执行的单位。

创建一个新的执行线程有两种方法:一是将一个类声明为

Thread

子类。这个子类应该重写

run

方法,编写线程执行体,创建线程对象,调用

start()

方法启动线程。二是实现

Runnable

接口;实现

run()

方法,编写线程执行体;创建线程,调用

start()

启动线程。

线程开启不一定立即执行,看CPU的调度

创建方法一举例:

public class Threaddemo1 extends Thread{@Overridepublic void run() {//run方法线程体for(int i = 0 ; i < 100 ; i++){System.out.println(\"thread\"+i);}}public static void main(String[] args) {Threaddemo1 thread = new Threaddemo1();thread.start();for(int i = 0 ; i < 100 ; i++){System.out.println(\"hello\"+i);}}}

输出结果是相互交替的,因为是CPU负责调度,每次结果都可能不一样,若把

start()

换成

run()

则顺序确定,性质就不同了。

创建方法二举例:

public class TestThread implements Runnable{@Overridepublic void run() {for(int i = 0 ; i < 100 ; i++){System.out.println(\"thread\"+i);}}public static void main(String[] args) {//创建runnable接口的实现类对象TestThread testThread = new TestThread();//创建线程对象Thread thread = new Thread(testThread);thread.start();for(int i = 0 ; i < 100 ; i++){System.out.println(\"hello\"+i);}}}

两种方法稍微有差异,但是以上两段代码输出等效

总结

继承Thread类:子类继承

Thread

类具备多线程能力;启动线程只需要

子类对象.start()

;不建议使用(避免OOP单继承局限性)

实现Runnable接口:启动线程需要传入目标对象+

Thread对象.Strat()

;推荐使用(避免单继承局限性,灵活方便,方便同一个对象被多个线程使用)

模拟抢票

@Overridepublic void run() {while(true){if(Nums <= 0)break;try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+\"--> get the No.\"+Nums--);}}

重写

run()

,该段实现每次间隔200毫秒,环境模拟抢票,

main()

如下

TestThread1 testThread1 = new TestThread1();new Thread(testThread1,\"xiaoming\").start();new Thread(testThread1,\"xiaohong\").start();new Thread(testThread1,\"huangniu\").start();

该demo出现了不安全情况,可能同时出现两个线程抢占一个资源,考虑信号量方面,没有实现互斥。

模拟龟兔赛跑

public class race implements Runnable{private static String winner;@Overridepublic void run() {for (int i = 0; i <= 100; i++) {boolean flag = gameOver(i);if (flag) {break;}System.out.println(Thread.currentThread().getName()+\"-->跑了\"+i+\"步\");}}//判断是否完成比赛private boolean gameOver(int steps){if(winner != null){return true;}if(steps == 100){winner = Thread.currentThread().getName();System.out.println(\"winner is \"+winner);return true;}return false;}public static void main(Strad8ing[] args) {race race1 = new race();new Thread(race1,\"兔子\").start();new Thread(race1,\"乌龟\").start();}}

在这个demo中发现,若不建立公共变量,是不会出现抢占资源的情况的。

实现Callable接口(简单了解)

可以定义返回值,可以抛出异常

  1. 需要定义返回值类型
  2. 重写call方法,若获取结果需抛出异常
  3. 创建目标对象
  4. 创建执行服务
  5. 提交执行
  6. 获取结果
  7. 关闭服务

举例一个demo,首先介绍定义的内部类

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 Exception,downloader has a problem\");}}}

该类实现的是将一个

url

文件下载下来

public class TestCallable implements Callable <Boolean>{private String url;private String name;public TestCallable(String url,String name){this.url = url;this.name = name;}@Overridepublic Boolean call() {WebDownloader webDownloader = new WebDownloader();webDownloader.downloader(url,name);System.out.println(\"download the file named \"+name);return true;}public static void main(String[] args) throws ExecutionException, InterruptedException {TestCallable s = new TestCallable(\"URL.jpg\",\"wallpaper1.jpg\");TestCallable s1 = new TestCallable(\"URL.jpg\",\"wallpaper2.jpg\");TestCallable s2 = new TestCallable(\"URL.jpg\",\"wallpaper3.jpg\");//创建执行服务,线程池ExecutorService ser = Executors.newFixedThreadPool(3);//提交执行Future<Boolean> rs1 = ser.submit(s);Future<Boolean> rs2 = ser.submit(s1);Future<Boolean> rs3 = ser.submit(s2);//获取结果Boolean rt1 = rs1.get();Boolean rt2 = rs2.get();Boolean rt3 = rs3.get();//关闭服务ser.shutdown();}}

以上只是简单的应用,将在下面的文章中介绍具体用法。

静态代理

真实对象和代理对象都要实现同一个接口

代理对象要代理真实角色

好处:代理对象可以做很多真实对象做不了的事情;真实对象专注于做自己的事情

interface Marry{ad8void HappyMarry();}//真实角色class You implements Marry{@Overridepublic void HappyMarry() {System.out.println(\"its very happy.\");}}//代理角色class WeddingCompany implements Marry{private Marry target;public WeddingCompany(Marry target){this.target = target;}@Overridepublic void HappyMarry() {before();this.target.HappyMarry();//真实对象结婚after();}private void before(){System.out.println(\"布置\");}private void after(){System.out.println(\"收款\");}}

以结婚的过程举例静态代理,其实Thread就是一个代理角色,真实角色则为Runnable

Lambda表达式

为了避免内部类定义过多

(params)-> expression(表达式); (params) -> statement(语句); (params) -> {statements}

函数式接口的定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么他就是函数式接口
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
like = ()->{System.out.println(\"I like lambda2\");};like.lambda();

like为接口定义的一个对象,此接口中有一个lambda方法。

interface Ilike{void lambda(int a);}

三种简化写法:

like = (a)->{System.out.println(\"I like lambda\"+a);};like = a -> {System.out.println(\"I like lambda\"+a);};like = a -> System.out.println(\"I like lambda\"+a);like.lambda(520);

Lambda表达式前提是接口为函数式接口;多个参数可以同时去掉参数类型,需要保持同步,需要加小括号;

简化过程其实可以理解为:静态内部类(类中定义static class,重写方法)->局部内部类(类中定义class,重写方法)->匿名内部类(直接new 接口,重写方法,不写内部类名)->Lambda表达式

线程状态

  • 创建状态
  • 就绪状态

start()

之后线程进入就绪状态,但不意味着立即调度执行

  • 阻塞状态
  • 运行状态
  • 死亡状态

线程中断或者结束,一旦进入死亡状态,就不能再次启动

Thread

常用方法:

方法 作用
setPriority(int newPriority)
更改此线程的优先级。
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
void join()
等待这个线程死亡。
static void yield()
对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。
void interrupt()
中断这个线程。
boolean isAlive()
测试这个线程是否活着。
  • 一般来讲线程最好正常停止(利用循环次数,不建议死循环)

  • 建议设置一个标志位

State

枚举类型(具有约束,例如定义四季枚举类型:春夏秋冬)

Thread.State state = thread.getState();

线程休眠_sleep

  • sleep

    指定当前线程阻塞的毫秒数

  • sleep

    存在异常

    InterruptedException
  • sleep

    时间达到后,线程进入就绪状态

  • 可以模拟网络延时,倒计时等
  • 每个对象都有一个锁,
    sleep

    不会释放锁

  • sleep()

    方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指

    this.currentThread()

    返回的线程。

try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}

需要抛出异常

模拟网络延时:可以放大问题的发生性(例如上方“模拟抢票”中,设置一定休眠时间,会发现多个线程抢占同一公共资源)

模拟倒计时:

public static void main(String[] args) throws InterruptedException {//Date startTime = new Date(System.currentTimeMillis()); //获取系统当前时间while(true){Thread.sleep(1000);Date startTime = new Date(System.currentTimeMillis());System.out.println(new SimpleDateFormat(\"HH:mm:ss\").format(startTime));}}

每隔1s输出一下当前时间(Date类的使用)。

线程礼让_yield

  • 让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功
@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+\"线程开始执行\");Thread.yield();System.out.println(Thread.currentThread().getName()+\"线程停止执行\");}

执行两个线程,结果如下:

0执行,礼让后给1。该结果可能不成功。

yield

执行后,会让CPU重新调度,重新调度的结果可能是新的也可能是旧的。

线程强制执行_join

合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞(本质插队)

public class testJoin implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println(\"special Thread comes.\"+i);}}public static void main(String[] args) throws InterruptedException {testJoin t = new testJoin();Thread t1 = new Thread(t);t1.start();for (int i = 0; i < 500; i++) {if(i==200){t1.join();}System.out.println(\"main\"+i);}}}

结果为,在循环的500中,前200遍历主线程和

t1

在并行,而在200时,只剩下

t1

执行,执行完毕后才把资源归还主线程。

后续将在下一节中介绍线程优先级,锁,同步,生产者消费者,线程池,管程等相关问题。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 简述Java多线程(一)