【学习笔记】线程(一)之线程概念及创建
线程简介
-
边吃饭,边玩手机
-
开车打电话
-
现实中有很多这样的例子,看起来是多个任务在做,其实本质上我们的大脑在同一时间只做了一件事
多线程:
-
执行main方法时,如果在main方法中调用了其他方法,就会出去main方法,去执行其他方法,然后在回来,多线程就实现了多条路同时执行
普通方法调用和多线程
程序、进程和线程
-
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
-
进程是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位
-
通常一个进程中包含若干个线程,最少一个,线程是CPU调度和执行的单位
注意:很多多线程是模拟出来的,真正的多线程是有多个CPU,即多核,如服务器。如果是模拟出来的多线程,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以有同时执行的错觉。
核心概念:
-
线程就是独立的执行路径
-
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
-
main() 称之为主线程,为系统的入口,用于执行整个程序
-
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
-
对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制
-
线程会带来额外的开销,如cpu调度时间,并发控制开销
-
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建
-
继承 Thread 类
-
自定义线程继承Thread 类
-
重写run() 方法
-
创建线程对象,调用start() 方法创建线程
-
package com.thread;
public class Demo01 extends Thread{
@Override
public void run() { //重写run方法
for (int i = 0; i < 200; i++) {
System.out.println("我是线程----"+ i);
}
}
public static void main(String[] args) {
//创建线程
Demo01 demo01 = new Demo01();
//调用start方法
demo01.start();
for (int i = 0; i < 2000; i++) {
System.out.println("我是主线程-----"+ i);
}
}
}
运行结果是这样的:先执行了主线程,执行到797,然后执行了创建的线程到199,执行完后,又去执行主线程到完毕
所以可以看出:线程是不一定立即执行的,由CPU 调度执行
案例:下载网络图片
需要导入commons-io 包
package com.thread.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class ThreadTest02 extends Thread{
private String url;
private String name;
public ThreadTest02(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownload webDownload = new WebDownload();
webDownload.download(url,name);
System.out.println("下载了文件名为" + name);
}
public static void main(String[] args) {
ThreadTest02 t1 = new ThreadTest02("https://wx2.sinaimg.cn/mw1024/006tJpbVly1h4m2yozkrsj30gx0gw3zn.jpg","biaoqing1.jpg");
ThreadTest02 t2 = new ThreadTest02("https://wx4.sinaimg.cn/mw1024/006tJpbVly1h4m2ypjyakj30j60jidgr.jpg","biaoqing2.jpg");
ThreadTest02 t3 = new ThreadTest02("https://wx3.sinaimg.cn/mw1024/006tJpbVly1h4m2ypel8rj309w09w0sn.jpg","biaoqing3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下载器类
class WebDownload{
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,download方法出现问题");
}
}
}
开启了三个线程,我们开启的顺序是 t1 t2 t3,但是下载的顺序却是
所以再次印证了线程开启不一定立即执行,CPU 调度执行
-
实现Runnable 接口
-
定义MyRunnable类实现Runnable接口
-
实现run() 方法,编写线程体
-
创建线程对象,调用start() 方法启动线程
-
package com.thread.demo01;
public class ThreadTest03 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我是线程----" + i);
}
}
public static void main(String[] args) {
ThreadTest03 t1 = new ThreadTest03();
new Thread(t1).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程----" + i);
}
}
}
结果与继承Thread 类的方式相同
两者区别:
-
继承Thread类
-
子类继承Thread类具备多线程能力
-
启动线程:子类对象.start()
-
不建议使用:避免OOP单继承局限性
-
-
实现Runnable接口
-
实现接口Runnable 接口具有多线程能力
-
启动线程:传入目标对象 + Thread 对象.start()
-
推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
-
下面用买票的例子,来展现一个对象被多个线程使用,并且暴露出了线程不安全的问题
package com.thread.demo01;
public class ThreadTest04 implements Runnable{
private int tickNum = 10;
@Override
public void run() {
while (true){
if (tickNum<=0){
break;
}
//延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----->拿到了第"+ tickNum-- + "张票");
}
}
public static void main(String[] args) {
ThreadTest04 ticks = new ThreadTest04();
new Thread(ticks,"张三").start();
new Thread(ticks,"李四").start();
new Thread(ticks,"王五").start();
}
}
从运行结果可以发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱
比如王五和李四同时拿到了第一张票
案例:龟兔赛跑
package com.thread.demo01;
public class Race implements Runnable{
private static String winner; //胜利者
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (Thread.currentThread().getName().equals("兔子") && i % 10 ==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName() + "--->跑了"+ i +"米");
}
}
//判断比赛是否结束
public boolean gameOver(int steps){
if (winner != null){ //已经有胜利者了
return true;
}else if(steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is" + winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
-
实现callable接口
-
实现Callable接口,需要返回值类型
-
重写call方法,需要抛出异常
-
创建目标对象
-
创建执行服务:ExecutorServiece ser = Excetor.newFixedThreadPool(3);
-
提交执行结果:Future< Boolean > r1 = ser.submit(ti);
-
获取结果:boolean rs1 = r1.get();
-
关闭服务:ser.shutdownNow();
-
利用callable 下载图片
package com.thread.demo02;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable implements Callable {
private String url;
private String name;
public TestCallable(String url,String name){
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception {
UrlDownload urlDownload = new UrlDownload();
urlDownload.download(url,name);
System.out.println("下载了名为:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://wx2.sinaimg.cn/mw1024/006tJpbVly1h4m2yozkrsj30gx0gw3zn.jpg","表情1.jpg");
TestCallable t2 = new TestCallable("https://wx4.sinaimg.cn/mw1024/006tJpbVly1h4m2ypjyakj30j60jidgr.jpg","表情2.jpg");
TestCallable t3 = new TestCallable("https://wx3.sinaimg.cn/mw1024/006tJpbVly1h4m2ypel8rj309w09w0sn.jpg","表情3.jpg");
//创建执行服务
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 UrlDownload{
public void download(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}