多线程
2021-06-07 03:04
标签:time write 提前 文件创建 下载器 f11 mss priority 生产者消费者模式
Java.Thread类
process(进程)和Thread(线程)
程序:指令和数据的有效集合,本身没有任何运行含有,是一个静态的概念
进程:执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位
线程:通常一个进程可以包含多个线程(由调度器安排调度,不可人为干预)一个进程至少有一个线程,线程是CPU调度和执行的单位
1. 线程的创建
- Thread Class 继承Thread类 *
- Runnable接口 实现Runnable接口*
- Callable接口 实现Callable接口
1. 通过继承Thread
-
自定义线程类并继承Thread类
-
重写run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
启动方式:子类对象.start()
注:run()方法只有主线程一条执行路径,start()方法有多条执行路径,线程交替执行
package com.zhou3.Thread;
public class TestThreadDemo01 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i
2. 通过实现Runnable
-
定义线程类实现Runnable接口
-
实现run()方法,编写线程执行体
-
创建Thread线程对象,通过start()方法启动线程
启动方式:传入目标对象+Thread对象.start()
推荐使用此方式:可以避免单继承,灵活方便,方便一个对象被多个线程使用
package com.zhou3.Thread;
/*
多个线程同时操作同一个对象
没有对单一对象进行操作控制,存在并发问题
*/
public class TestThread implements Runnable{
private int ticketNumber = 10;
@Override
public void run() {
while (true){
if(ticketNumber
package com.zhou3.Thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/*
练习多线程,同步下载漂亮的图片
*/
public class TestThreadDemo02 implements Runnable{
//类属性
private String url;
private String name;
//有参构造函数
public TestThreadDemo02(String url,String name){
this.name = name;
this.url = url;
}
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println(name + "已经下载");
}
}
//下载器
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方法出现问题");
}
}
//main线程实现 多线程对象执行
public static void main(String[] args) {
TestThreadDemo02 t1 = new TestThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201110%2F20%2F104212ds59m9rtqspt6tzt.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908610&t=8c2e3d5e4418b17dcdccf84593a7cfed","1.jpg");
TestThreadDemo02 t2 = new TestThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908800&t=e7ef5c1ee4be471886018bc7b88f5aee","2.jpg");
TestThreadDemo02 t3 = new TestThreadDemo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F1%2F1680x1050%2F1349289433496.jpg&refer=http%3A%2F%2Fb.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908797&t=fbd6b61bf2cec0bfed9d6ef9e23ce412","3.jpg");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
3. 通过实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future
result1 = ser.submit(t1); - 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
package com.zhou3.Thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/*
使用Callable接口实现多线程
好处:1. 可以定义返回值 2. 可以抛出异常
*/
//多线程类
public class CallableDemo01 implements Callable {
//线程类属性
private String url;
private String fileName;
//线程类构造器
public CallableDemo01(String url,String fileName){
this.url = url;
this.fileName = fileName;
}
//多线程操作体
@Override
public Boolean call() throws Exception {
webDownloader webDownloader1 = new webDownloader();
webDownloader1.Downloader(url,fileName);
System.out.println(fileName + "已经下载完成");
return true;
}
}
//下载器类
class webDownloader{
public void Downloader(String url,String fileName){
try {
FileUtils.copyURLToFile(new URL(url),new File(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,URL或文件创建时出错");
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableDemo01 c1 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201110%2F20%2F104212ds59m9rtqspt6tzt.jpg&refer=http%3A%2F%2Fattach.bbs.miui.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908610&t=8c2e3d5e4418b17dcdccf84593a7cfed","1.jpg");
CallableDemo01 c2 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908800&t=e7ef5c1ee4be471886018bc7b88f5aee","2.jpg");
CallableDemo01 c3 = new CallableDemo01("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fb.zol-img.com.cn%2Fdesk%2Fbizhi%2Fimage%2F1%2F1680x1050%2F1349289433496.jpg&refer=http%3A%2F%2Fb.zol-img.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1618908797&t=fbd6b61bf2cec0bfed9d6ef9e23ce412","3.jpg");
//创建执行服务
//创建线程池
ExecutorService ser = Executors.newFixedThreadPool(3);
Future result1 = ser.submit(c1);
Future result2 = ser.submit(c2);
Future result3 = ser.submit(c3);
//获取结果
boolean rc1 = result1.get();
boolean rc2 = result1.get();
boolean rc3 = result1.get();
//关闭服务
ser.shutdownNow();
}
}
2. Lambda表达式
λ是希腊字母表中排行第十一位的字母,英语名称位Lambda
1. Lambda表达式的使用限制
Lambda表达式的出现是为了简化繁琐的表达式,可以用于匿名内部类定义过多。属于函数式编程
//格式
(paramss) -> expression[表达式]
(paramss) -> statement[语句]
(paramss) -> {statements}
函数式接口:
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable{
public abstract void run();
}
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
2. Lambda表达式的简化过程
package com.zhou3.Thread.Lambda;
/*
推导Lambda表达式
*/
public class LambdaDemo01 {
//2.静态内部类
static class Like2 implements iLike{
@Override
public void Lambda() {
System.out.println("I Like Lambda2");
}
}
public static void main(String[] args) {
iLike like = new Like();
like.Lambda();
like = new Like2();
like.Lambda();
//3.局部内部类
class Like3 implements iLike{
@Override
public void Lambda() {
System.out.println("I Like Lambda3");
}
}
like = new Like3();
like.Lambda();
//4.匿名内部类
like = new iLike() {
@Override
public void Lambda() {
System.out.println("I Like Lambda4");
}
};
like.Lambda();
//5.Lambda表达式,是对匿名内部类的再简化
//Lambda表达式就是对程序一步步的简化得来的,由于函数式接口只有一个方法,所以匿名内部类的接口和方法部分就理所当然的被去掉了
like = () -> {
System.out.println("I Like Lambda5");
};
like.Lambda();
}
}
//定义一个函数式接口
interface iLike{
void Lambda();
}
//1.接口实现类
class Like implements iLike{
@Override
public void Lambda() {
System.out.println("I Like Lambda1");
}
}
3. 为什么要使用Lambda表达式呢!
- 避免匿名内部类定义过多
- 让代码看起来简洁
- 去掉了一堆没有意义的代码
3. 线程的五大状态
调度:操作系统给线程分配时间片(就绪状态进入运行状态的方式)
- 创建状态(new Thread时,线程进入创建状态)
- 就绪状态(当线程调用start()方法时,线程进入就绪状态)
- 阻塞状态(当调用sleep,wait,同步锁时,线程进入阻塞状态)
- 运行状态(此时线程内的代码体才开始真正的执行)
- 死亡状态(线程中断或结束,此时线程不可被重启)
1. 线程的操作方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 让当前正在执行的线程体休眠指定的毫秒数 |
void join() | 等待该线程终止 |
static void yield() | 暂停正在执行的线程对象,状态退回到就绪状态,重写等待时间片分配 |
boolean isAlive() | 测试线程是否处于活动状态 |
2. 线程停止
- 不推荐使用JDK提供的stop()、destroy()方法(已经废弃)
- 推荐让线程自己停下来
- 建议使用一个标志位终止变量等flag=false时,线程终止运行
//在主线程中通过公开方法修改标志位来停止线程
package com.zhou3.Thread.ThreadState;
public class StopTest implements Runnable{
//1. 设置全局标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while(flag){
System.out.println("Thread time ==" + i++);
}
}
//2. 设置一个线程停止的公开方法
public void threadStop1(){
this.flag = false;
}
public static void main(String[] args) {
StopTest stopTest1 = new StopTest();
new Thread(stopTest1).start();
for (int i = 0; i
3. 线程休眠
- sleep(时间)方法可以指定当前线程阻塞的毫秒数(1000ms =1s)
- sleep存在异常InterruptedException
- sleep的时间达到后,该线程就重新进入就绪状态等待时间片的分配
- sleep可以模拟网络延时,和倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
模拟网络延时可以放大问题发生的可能性
通过线程休眠实现每秒打印异常当前时间
package com.zhou3.Thread.ThreadState;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CountDownTest {
public static void main(String[] args) {
Date date1 = new Date(System.currentTimeMillis());//获取当前系统时间
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//格式化时间并输出
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date1));
//刷新当前时间
date1 = new Date(System.currentTimeMillis());
}
}
}
4. 线程礼让
- 通过一种不阻塞线程的方式,让线程暂停
- 将线程从运行状态转为就绪状态(重写等待CPU分配时间片)
5. 线程强制执行(插队)
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
package com.zhou3.Thread.ThreadState;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i
6. 线程状态观测
通过Thread.getState()方法来观测线程状态
package com.zhou3.Thread.ThreadState;
public class TestStateObservation {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i
4. 线程优先级
线程优先级高不一定先执行,只是先执行的概率会大大提高(给的资源多)
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
-
线程优先级的表示
用数字来表示,范围1~10
Thread.MIN_PRIORITY = 1; Thread.MAX_PRIORITY = 10; Thread.NORM_PRIORITY = 5;
-
改变线程优先级的方法
getPriority() setPriority()
package com.zhou3.Thread.ThreadState; public class TestPriority { public static void main(String[] args) { MyPriority myPriority1 = new MyPriority(); Thread thread1 = new Thread(myPriority1,"1"); Thread thread2 = new Thread(myPriority1,"2"); Thread thread3 = new Thread(myPriority1,"3"); Thread thread4 = new Thread(myPriority1,"4"); Thread thread5 = new Thread(myPriority1,"5"); thread1.start(); thread2.setPriority(1); thread2.start(); thread3.setPriority(10); thread3.start(); thread4.setPriority(8); thread4.start(); thread5.setPriority(3); thread5.start(); //主线程优先级,默认优先级 System.out.println(Thread.currentThread().getName()+"优先级:"+Thread.currentThread().getPriority()); } } class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"优先级:"+Thread.currentThread().getPriority()); } }
5. 守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须保证用户线程执行完毕(main)
- 虚拟机不用等待守护线程执行完毕(gc垃圾回收)
6. 线程同步
并发:同一个对象被多个线程同时操作
现实生活中:我们遇到这种问题(食堂排队打饭)时,解决办法就是排队一个一个来
在处理多线程问题时,遇到这种并发情况,我们就需要线程同步。
线程同步:是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一次才开始使用
1. 队列和锁
这是线程同步的形成条件
每一个对象都有一把锁,可以实现线程安全
锁机制:synchronized排他锁,可以独占资源,而其他线程必须等待锁的释放
存在的问题:
- 性能降低
- 如果优先级高的线程等待一个优先级低的线程释放锁,会引起优先级倒置的问题
2. 同步方法
- synchronize关键字(包含synchronize方法和synchronize快)两种用法
- synchronize方法控制对“对象”的访问,每个对象都有一把锁,synchronize方法必须获得调用该方法的对象的锁才能执行,否则线程会被阻塞
默认锁this
锁的对象需要是Object类型的
java.util.concurrent //并发包、这是一个有关并发编程的包
7. 死锁
相互等待对方锁定的资源:自己抱着一个锁去等另外一个锁。想要同时持有两个锁,但是都不想给
死锁的避免(四个必要条件)
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放
- 不剥夺条件:进程已获得的资源在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
上面这四个必要条件,只要想办法破解其中任意一个或多个就可以避免死锁的发生
8. Lock锁
- JDK5.0开始,Java提供了更强大的线程同步机制----显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
- java.util.concorrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
- ReentrantLock类实现了Lock,它有与synchronized相同的并发型和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁和释放锁
注意:锁要写在并非线程类中
注意:要区分线程循环和方法循环的区别
9. Lock锁和synchronized关键字的对比
-
Lock是显式锁,synchronized是隐式锁
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且有更好的拓展性
-
优先顺序
Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步代码块(方法体之外)
自我总结:
Lock可以显式的对不安全代码进行加锁和解锁,适合在run方法中写代码的情况
package com.zhou3.Thread.Unsafe;
import java.util.concurrent.locks.ReentrantLock;
/*
lock锁测试
*/
public class TestLock {
public static void main(String[] args){
UnSafe safe1 = new UnSafe();
new Thread(safe1,"hong").start();
new Thread(safe1,"ming").start();
new Thread(safe1,"A").start();
}
}
class UnSafe implements Runnable {
private int ticketNumber = 10;
private final ReentrantLock lock =new ReentrantLock();
@Override
public void run(){
while (true){
try {
lock.lock();
if(ticketNumber
synchronized关键字是隐形的对this加锁,可以使得本类一次只能一个线程调用。关键字的位置很重要,适合在多线程类中写方法,在run中调用方法
package com.zhou3.Thread.Demo01;
/*
多个线程同时操作同一个对象
*/
public class TestThread {
public static void main(String[] args) {
BuyTicket buyTicket1 = new BuyTicket();
new Thread(buyTicket1,"小美").start();
new Thread(buyTicket1,"黄牛").start();
new Thread(buyTicket1,"懒羊羊").start();
// new Thread(new BuyTicket(),"小美").start();
// new Thread(new BuyTicket(),"大美").start();
// new Thread(new BuyTicket(),"小明").start();
}
}
class BuyTicket implements Runnable{
boolean flag = true;
private int ticketNumber = 10;
@Override
public void run() {
while (flag){
// if(ticketNumber
10. 线程协作
生产者消费者模式(问题)
Java中提供的几个解决线程通信的问题
方法名 | 作用 |
---|---|
wait() | 线程会一直等待,直到其他线程通知,同sleep不同会释放锁 |
wait(long timeout) | 等待指定毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度 |
注:以上方法都是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException。
1. 管程法
通过缓冲区过度数据
Producer(生产者) 缓冲区 Consumer(消费者)
package com.zhou3.Thread.ThreadCommunication;
/*
测试生产者消费者模型,管程法
生产者,消费者,产品,缓冲区
*/
public class TestPC {
public static void main(String[] args){
SynContainer container1 = new SynContainer();
Producer producer1 = new Producer(container1);
Consumer consumer1 = new Consumer(container1);
new Thread(producer1,"工具员").start();
new Thread(consumer1,"黑心狗").start();
}
}
class Producer implements Runnable{
SynContainer synContainer1;
//构造器
public Producer(SynContainer synContainer1){
this.synContainer1 = synContainer1;
}
@Override
public void run(){
for (int i = 0; i
2. 信号灯法
注意:一个文件中只能有一个public类
相当于是只有一个位置的缓冲区
注意标志位每次调用方法都要更改
package com.zhou3.Thread.ThreadCommunication;
/*
使用信号灯法,实现传小纸条聊天
我,对象,内容
*/
public class TestLight {
public static void main(String[] args) {
Massage massage = new Massage();
new Thread(new My(massage),"my").start();
new Thread(new Zx(massage),"zx").start();
System.out.println("我们会好好的");
}
}
class My implements Runnable{
Massage massage = new Massage();
//我要用这个蓝色的小纸条
public My(Massage massage){
this.massage = massage;
}
//我这次打算写100条“我喜欢你”到小纸条上
@Override
public void run() {
for (int i = 0; i
11. 线程池
- 经常创建和销毁线程,会耗费大量资源,对性能影响很大
- 可以提前创建好多个线程,放入线程池,使用时直接获取,用完再放回去
- 好处
- 提高响应速度(减少了创建新线程的时间)
- 降低资源销毁(重复利用线程池中的线程,不需要每次都创建)
- 便于线程的管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池的使用
-
从JDK5.0起提供了线程池相关的API:ExecutorService和Executors
-
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
-
void execute(Runnable command)//执行任务,没有返回值,用来执行Runnable
-
Future submit(Callable task)//有返回值,一般用来执行Callable -
void shutdown()//关闭线程池
-
-
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
package com.zhou3.Thread.TestPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPoolRunnable {
public static void main(String[] args) {
//创建线程池
MyThread myThread1 = new MyThread();
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(myThread1);
service.execute(myThread1);
service.execute(myThread1);
service.execute(myThread1);
service.shutdownNow();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
多线程
标签:time write 提前 文件创建 下载器 f11 mss priority 生产者消费者模式
原文地址:https://www.cnblogs.com/zhoushuaiyi/p/14591925.html
上一篇:c++ 虚函数