注意:大部分多线程是模拟的,真正的多线程是指有多个CPU,即多核,但是模拟出来的多线程,就是在一个CPU的前提下,在一个时间点执行一段代码,但是因为切换的很快,所以会有同步执行的错觉
线程创建方式一 :
第一步:继承Thread
类
第二步:重写run()
方法
第三步:创建继承了Thread
类的对象 , 调用start()
方法启动。
代码实现:
package com.kuang.demo01;
import java.awt.*;
public class TestThread1 extends Thread{ //继承Thread类
//自定义run方法的线程
@Override
public void run() {
//线程执行体
for (int i = 0; i < 200; i++) {
System.out.println("有人在写笔记-->"+i);
}
}
//主线程
public static void main(String[] args) {
//创建线程对象
TestThread1 testThread1 = new TestThread1();
testThread1.start(); //start() 方法用来 启动线程;
for (int i = 0; i < 3000; i++) {
System.out.println("有人在在睡觉sleep-->"+i);
}
}
}
上述代码运行结果太长,只取出其中部分运行结果:
有人在在睡觉sleep-->83
有人在在睡觉sleep-->84
有人在在睡觉sleep-->85
有人在在睡觉sleep-->86
有人在写笔记-->0
有人在在睡觉sleep-->87
有人在在睡觉sleep-->88
有人在在睡觉sleep-->89
有人在在睡觉sleep-->90
可以看到在运行注方法时,并没有按照顺序进行,因为:
TestThread1 testThread1 = new TestThread1();
testThread1.start();
上面的代码让TestThread1
类的对象可以通过start()
方法来开启多线程,系统会先跳过testThread1.start();
语句,因为线程启动还需要一段时间,转而执行下面的循环,在执行循环一段时间以后,线程差不多开启成功后开始执行,在执行一段时间后就又回到主线程,就这样不断来回切换,产生同步执行的错觉
注意:多线程只是对重写的run()
方法中的逻辑而言,多条线程同步执行重写后的run()
方法中的内容
创建线程方式二:
第一步:实现runnable
接口
第二步:重写run
方法
第三步:需要创建一个线程Thread
对象 ,然后把runbale
接口实现类的对象丢到构造参数里 ,调用start
方式启动
package com.kuang.demo01;
public class TestThread3 implements Runnable { //实现接口
@Override //重写方法
public void run() {
//线程执行体
for (int i = 0; i < 200; i++) {
System.out.println("有人在写笔记-->"+i);
}
}
public static void main(String[] args) {
//重点就是将Runbale接口实现类的对象丢入Thread构造器
TestThread3 testThread3 = new TestThread3();
new Thread(testThread3).start();
for (int i = 0; i < 3000; i++) {
System.out.println("有人在在睡觉sleep-->"+i);
}
}
}
代码运行结果为:
有人在写笔记-->77
有人在写笔记-->78
有人在在睡觉sleep-->1236
有人在写笔记-->79
有人在写笔记-->80
有人在写笔记-->81
有人在写笔记-->82
原理和创建方式一一样:在这里不再解释,这里要注意,一定要使用线程对象的start
方法,如果使用run
方法的话会先执行完线程执行体中的所有代码之后再执行主线程中的代码,在这里不再做演示
那么两种方法相比有什么优缺点?
第一种最好理解
第二种比较灵活,可以创建一个Runnable
接口实现类的对象之后放入不同的Thread
构造器构造器中来对任务执行多线程,在后面的龟兔赛跑的案例中可以用到
代码示例1:
我们可以使用多线程来在网上下载图片
代码示例:
package org.westos.java1;
import org.apache.commons.io.FileUtils; //这里导入了第三方包
import java.io.File;
import java.io.IOException;
import java.net.URL;
//多线程实现图片下载
public class MyTest5 extends Thread{ //使用第一种创建方式
private String url; //定义目标图片URL
private String filename; //定义目标文件
public MyTest5(String url, String filename){ //定义有参构造来确定要下载的图片的url和目标文件
this.url = url;
this.filename = filename;
}
@Override
public void run() {
//下载图片
webdownload load = new webdownload(); //下载器,自定义
load.downlodar(url, filename);
System.out.println(filename + "下载完成");
}
public static void main(String[] args) {
MyTest5 myTest1 = new MyTest5("https://img.alicdn.com/tfs/TB1uUvlXL1H3KVjSZFBXXbSMXXa-990-110.png", "file1");
MyTest5 myTest11 = new MyTest5("https://gw.alicdn.com/tfs/TB1KZFLaBKw3KVjSZFOXXarDVXa-240-240.png", "file2");
MyTest5 myTest12 = new MyTest5("https://img.alicdn.com/tfs/TB13XYhaW5s3KVjSZFNXXcD3FXa-990-400.jpg_1080x1800Q90s50.jpg", "file3");
myTest1.start();
System.out.println("执行了myTest1");
myTest11.start();
System.out.println("执行了myTest11");
myTest12.start();
System.out.println("执行了myTes12");
}
}
class webdownload{ //自定义下载器
public void downlodar(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name)); //使用第三方类的方法
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载出错了");
}
}
}
运行结果为:
执行了myTest1
执行了myTest11
执行了myTes12
file2下载完成
file1下载完成
file3下载完成
可以看到图片下载完成的顺序不一样,因为是多线程,三条线程一起执行,所以顺序可能不会按照代码中的顺序完成,至于代码:
执行了myTest1
执行了myTest11
执行了myTes12
为什么按顺序执行,这是因为主方法也是一个独立的线程,会按照线程中的顺序执行
代码示例2:
龟兔赛跑:
首先创建个赛道,然后要离终点越来越近
判断比赛是否结束
打印出胜利者
龟兔赛跑开始
故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
终于,乌龟赢得比赛
package com.kuang.demo01;
//龟兔赛跑
public class Race implements Runnable{ //使用第二种创建方式
//winner:只有一个胜利者
private static String winner;
@Override
public void run() {
//赛道
for (int step = 1; step <= 101; step++) {
if (Thread.currentThread().getName().equals("兔子") && step % 50==0){
try {
Thread.sleep(100); //兔子休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(step);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+ step +"步");
}
}
//判断比赛是否结束
private boolean gameOver(int step){
if (winner!=null){ //如果存在胜利者
return true;
}
if (step>=100){ //如果跑到了比赛结束
winner = Thread.currentThread().getName();
System.out.println("比赛结束");
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();
}
}
运行结果为:
乌龟跑了36步
兔子跑了49步
乌龟跑了37步
乌龟跑了38步
乌龟跑了39步
乌龟跑了40步
乌龟跑了41步
乌龟跑了42步
乌龟跑了43步
乌龟跑了44步
乌龟跑了45步
......
比赛结束
胜利者----->乌龟
可以看到在兔子跑了50
步后不再跑,在兔子线程休眠的时候,乌龟线程执行完毕,在winner
是乌龟后打印结果,程序结束。这就是龟兔赛跑的实现
代码示例3:
模拟抢票:
package com.kuang.demo01;
//初识并发问题:多个线程同时操作一个对象(runnable接口实现类)
//线程不安全
public class TestThread4 implements Runnable {
//票
private int ticketNums = 10;
@Override
public void run() {
test();
}
public void test(){
//执行体
while (true){
if (ticketNums<=0){
break;
}
//模拟网络延时,睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程名字
System.out.println(Thread.currentThread().getName()+"-->抢到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
TestThread4 t = new TestThread4();
//第二个参数,创建线程名字
new Thread(t,"小明").start();
new Thread(t,"老师").start();
new Thread(t,"黄牛党").start();
}
}
运行结果为:
黄牛党-->抢到了第10张票
老师-->抢到了第8张票
小明-->抢到了第9张票
黄牛党-->抢到了第7张票
小明-->抢到了第7张票
老师-->抢到了第6张票
黄牛党-->抢到了第4张票
小明-->抢到了第5张票
老师-->抢到了第3张票
小明-->抢到了第2张票
黄牛党-->抢到了第1张票
老师-->抢到了第2张票
可以在上述运行结果中发现,黄牛
和小明
都抢到了第7
张票,这是个错误,也就是线程不安全,多个线程对同一份资源进行操作需要使用并发控制,使用同步可以改正,同步在以后会讲到