JAVA多线程入门笔记1
线程、进程、多线程
普通方法调用只有主线程一条执行路径,而多线程是多条执行路径,主线程和子线程并行交替执行。
一个进程可以有多个线程。
Process(进程)与Thread(线程)
说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
- 线程就是独立的执行路径.
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程.
- main()称之为主线程,为系统的入口,用于执行整个程序.
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的.
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制.
- 线程会带来额外的开销,如cpu调度时间,并发控制开销.
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致.
线程创建
三种创建:
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
继承Thread类
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 2000; i++) {
System.out.println("我在看代码"+i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start()方法开启线程
testThread1.start();
//main线程,主方法
for (int i = 0; i < 2000; i++) {
System.out.println("我在学习多线程"+i);
}
}
//线程不一定立即执行,看CPU安排调度
}
案例:图片下载
//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread {
private String url;//图片下载地址
private String name;//下载图片名
public TestThread2(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) {
//图片地址自己随便在网上找几个
TestThread2 t1 = new TestThread2("http://i0.hdslb.com/bfs/bangumi/image/36a46b1dd11081a5b6a1b65b9cf48eeec1ec2bd2.png@2320w_664h.png","p1.png");
TestThread2 t2 = new TestThread2("http://i0.hdslb.com/bfs/bangumi/image/cd84ca2237b47d7b5b9bc9dd61e3a21ccb08e32e.png@90w_90h.webp","p2.webp");
TestThread2 t3 = new TestThread2("http://i0.hdslb.com/bfs/album/8c26f6b4387b10823e6215b6505cf939bf9317aa.jpg@240w_320h_1e_1c.webp","p3.webp");
t1.start();
t2.start();
t3.start();
}
}
//下载器
class webDownload{
//下载方法
public void downLoad(String url,String name){
try {
//注意导入commons-io的jar包
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoad方法出现问题");
}
}
}
实现Runnable接口
定义MyRunnable类实现Runnable接口
实现run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
推荐使用Runnable对象,为了避免JAVA单继承的局限性,方便同一个对象被多个线程使用
//创建线程方法二:实现Runnable接口,重写run方法,执行线程时丢入Runnable接口实现类,最后调用start方法
public class TestThread3 implements Runnable {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("run:"+i);
}
}
public static void main(String[] args) {
//创建runnable接口实现类的对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
// Thread thread = new Thread(testThread3);
// thread.start();
new Thread(testThread3).start();
for (int i = 0; i < 2000; i++) {
System.out.println("mian:"+i);
}
}
}
案例:图片下载
//练习Thread,实现多线程同步下载图片
public class TestThread2 implements Runnable{
private String url;//图片下载地址
private String name;//下载图片名
public TestThread2(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) {
TestThread2 t1 = new TestThread2("http://i0.hdslb.com/bfs/bangumi/image/36a46b1dd11081a5b6a1b65b9cf48eeec1ec2bd2.png@2320w_664h.png","p1.png");
TestThread2 t2 = new TestThread2("http://i0.hdslb.com/bfs/bangumi/image/cd84ca2237b47d7b5b9bc9dd61e3a21ccb08e32e.png@90w_90h.webp","p2.webp");
TestThread2 t3 = new TestThread2("http://i0.hdslb.com/bfs/album/8c26f6b4387b10823e6215b6505cf939bf9317aa.jpg@240w_320h_1e_1c.webp","p3.webp");
//方法一
// t1.start();
// t2.start();
// t3.start();
//方法二
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
//下载器
class webDownload{
//下载方法
public void downLoad(String url,String name){
try {
//注意导入commons-io的jar包
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoad方法出现问题");
}
}
}
初识并发问题
多个线程操作同一个资源的情况下,线程不安全,会发生数据紊乱。
//多线程同时操作
//模拟抢票
public class TestThread4 implements Runnable{
//抢票
private int tickets = 20;
@Override
public void run() {
while (true){
if (tickets<=0){
break;
}
//模拟延时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+tickets--+"张票");
}
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
//开启多个线程
new Thread(testThread4,"aaa").start();
new Thread(testThread4,"bbb").start();
new Thread(testThread4,"ccc").start();
//运行后发现,同一张票可能被不同线程抢到,出现数据紊乱
}
}
实现Callable接口
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行: Future result1 = ser.submit(t1);
6.获取结果: boolean r1 = result1.get();
7.关闭服务: ser.shutdownNow();
//创建线程方法三:实现callable接口
//1、callable可以定义返回值
//2、callable可以抛出异常
public class TestCallable implements Callable<Boolean> {
private String url;//图片下载地址
private String name;//下载图片名
public TestCallable(String url,String name){
this.url=url;
this.name = name;
}
//下载图片线程的执行体
@Override
public Boolean call() {
webDownload webDownload = new webDownload();
webDownload.downLoad(url,name);
System.out.println("下载文件:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("http://i0.hdslb.com/bfs/bangumi/image/36a46b1dd11081a5b6a1b65b9cf48eeec1ec2bd2.png@2320w_664h.png","p1.png");
TestCallable t2 = new TestCallable("http://i0.hdslb.com/bfs/bangumi/image/cd84ca2237b47d7b5b9bc9dd61e3a21ccb08e32e.png@90w_90h.webp","p2.webp");
TestCallable t3 = new TestCallable("http://i0.hdslb.com/bfs/album/8c26f6b4387b10823e6215b6505cf939bf9317aa.jpg@240w_320h_1e_1c.webp","p3.webp");
//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:
Future<Boolean> result1 = ser.submit(t1);
Future<Boolean> result2 = ser.submit(t2);
Future<Boolean> result3 = ser.submit(t3);
//获取结果:
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
//关闭服务:
ser.shutdownNow();
}
}
//下载器
class webDownload{
//下载方法
public void downLoad(String url,String name){
try {
//注意导入commons-io的jar包
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downLoad方法出现问题");
}
}
}
静态代理模式
//接口Marry
public interface Marry {
//都要完成的动作
void HappyMarry();
}
//真是角色对象,You类
public class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("结婚了");
}
}
//代理对象,帮助你结婚,WeddingCompany类
public class WeddingCompany implements Marry {
//接收代理的真实对象
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//调用真实对象的HappyMarry函数
after();
}
private void after() {
System.out.println("结婚之后,收取服务费");
}
private void before() {
System.out.println("结婚之前,布置现场");
}
}
//静态代理模式总结
//真实对象和代理对象都实现同一个接口
//代理对象代理真实对象
//优点
//代理对象可以做很多真实对象做不了的事情
//真实对象专注做自己的事情
public class Staticproxy{
public static void main(String[] args){
//You you = new You();
// WeddingCompany weddingCompany = new WeddingCompany(you);
// weddingCompany.HappyMarry();
new WeddingCompany(new You()).HappyMarry();
//实现Runnable接口创建线程的方法实际是一种静态代理模式
//Thread类实现了Runnable接口,相当于代理对象
//自己编写的类也实现了Runnable接口,相当于真实对象
//本例中的HappyMarry方法相当于线程中的start方法
new Thread(()->System.out.println("111")).start();
}
}
帮助理解的参考文章
Lambda表达式(补充)
- 避免匿名内部类定义过多
- 去掉没有意义的代码,只留下核心的逻辑
- Functional interface(函数式接口):任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,可以通过lambda表示式来创建该接口的对象。
public class TestLambda1 {
//3、静态内部类
static class Student2 implements Stud{
@Override
public void say(int a) {
System.out.println("lambda2静态内部类"+a);
}
}
public static void main(String[] args) {
Stud student = new Student1();
student.say(1);
student = new Student2();
student.say(2);
//4、局部内部类
class Student3 implements Stud{
@Override
public void say(int a) {
System.out.println("lambda3局部内部类"+a);
}
}
student = new Student3();
student.say(3);
//5、匿名内部类、没有类的名称,必须借助接口或者父类
student = new Stud() {
@Override
public void say(int a) {
System.out.println("Lambda4匿名内部类"+a);
}
};
student.say(4);
//6、通过lambda表达式简化
student = (int a)->{
System.out.println("lambda5-lambda表达式"+a);
};
student.say(5);
//当代码简单时还可以简化
student= a->System.out.println("lambda5-lambda表达式"+a);
student.say(10);
}
}
//1、定义一个函数式接口
interface Stud{
public abstract void say(int a);
}
//2、实现类
class Student1 implements Stud{
@Override
public void say(int a) {
System.out.println("lambda1实现类"+a);
}
}
使用时注意:
- lambda表达式只能在只有一行代码时简化为一行。多行代码必须由{}包裹。
- 前提接口必须为函数式接口。
- 当有多个参数时,必须加上括号,可以去掉参数类型。
详情www.kuangstudy.com