本章内容来自尚学堂Java300集视频第二季多线程部分总结
程序:指令集 静态概念
进程:操作系统调度程序 动态概念
线程:在进程内多条执行路径
一个进程中的线程共享相同的内存单元/内存地址空间->可以访问相同的变量和对象,而且它们从同一堆中分配对象->通信、数据交换、同步操作
由于线程间的通信是同一地址空间上进行的,所以不需要额外的通信机制
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大开销。 | 线程可以看成是轻量级进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候会为咩哥进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线程共同完成的。 | 线程是进程的一部分 |
package com.zjn.thread.create;
/**
* 模拟龟兔赛跑
* 1.创建多线程,继承Thhread + 重写run(线程体)
* 2.使用线程:创建子类对象 + 调用对象.start()方法 线程启动
*/
public class Rabbit extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("兔子跑了"+i+"步");
}
}
}
class Tortoise extends Thread {
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("乌龟跑了"+i+"步");
}
}
}
package com.zjn.thread.create;
public class RabbitApp {
public static void main(String[] args) {
//创建子类对象
Rabbit rab = new Rabbit();
Tortoise tor = new Tortoise();
//调用start方法
rab.start();
tor.start();
}
}
控制台输出
兔子跑了0步
兔子跑了1步
兔子跑了2步
兔子跑了3步
兔子跑了4步
兔子跑了5步
乌龟跑了0步
兔子跑了6步
...
继承Thread类方式的缺点:如果我们的类已经从一个类继承,则无法再继承Thread类
优点:可以同时实现继承。实现Runnable接口方式要通用一些
使用Runnable 创建线程
避免单继承的局限性
便于共享资源
类实现Runnable接口+重写run() -->真实角色类
启动多线程 使用静态代理
1) 创建真实角色
2)创建代理角色
3) 调用start()方法
package com.zjn.thread.create;
public class Programmer implements Runnable{
@Override
public void run() {
for (int i=0;i<1000;i++){
System.out.println("一边敲代码");
}
}
}
package com.zjn.thread.create;
public class ProgrammerApp {
public static void main(String[] args) {
Programmer pro = new Programmer();
Thread proxy = new Thread(pro);
proxy.start();
for (int i=0;i<1000;i++){
System.out.println("一边聊天");
}
}
}
控制台输出:
...
一边聊天
一边聊天
一边聊天
一边聊天
一边聊天
一边敲代码
一边敲代码
一边敲代码
一边敲代码
...
package com.zjn.thread.create;
/**
* 静态代理 设计模式
* 1.真实角色
* 2.代理角色:持有真实角色的引用
* 3.二者实现相同接口
*/
public class StaticProxy {
public static void main(String[] args) {
//创建真实角色
You you = new You();
//创建代理角色+真实角色的引用
WeddingCompany company = new WeddingCompany();
//执行任务
company.marry();
}
}
interface Marry{
public abstract void marry();
}
//真实角色
class You implements Marry{
@Override
public void marry() {
System.out.println("you and 嫦娥结婚了");
}
}
//代理角色
class WeddingCompany implements Marry{
private Marry you;
public WeddingCompany(Marry you) {
this.you = you;
}
public WeddingCompany() {
}
private void before(){
System.out.println("布置猪窝");
}
private void after(){
System.out.println("闹玉兔");
}
@Override
public void marry() {
before();
you.marry();
after();
}
}
感觉静态代理模式是否也应用于了测试用例部分,比如Junit的设置前置与后置条件。
Callable和Future接口
Callable和Runnable有几点不同:
1.Callable规定的方法是call(),Runnable规定的方法是run()
2.call方法可以抛出异常,run方法不能抛出异常
3.Callable任务执行后可返回值,运行Callable任务可拿到一个Future对象,而Runnable任务是不能返回值的,Future表示异步计算结果,它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果
package com.zjn.thread.create;
import java.util.concurrent.*;
/**
* 使用callable创建线程
*/
public class Call {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程
ExecutorService ser = Executors.newFixedThreadPool(1);
Race tortoise = new Race("老乌龟",1000);
Race rabbit = new Race("小兔子",500);
//获取值
Future<Integer> result1 = ser.submit(tortoise);
Future<Integer> result2 = ser.submit(rabbit);
Thread.sleep(2000);//2秒
//停止线程体循环
tortoise.setFlag(false);
rabbit.setFlag(false);
int num1 = result1.get();
int num2 = result2.get();
System.out.println("乌龟跑了-->"+num1+"步");
System.out.println("兔子跑了-->"+num2+"步");
//停止服务
ser.shutdown();
}
}
class Race implements Callable<Integer>{
private String name;//名称
private long time;//延时时间
private boolean flag = true;
private int step = 0;//步
@Override
public Integer call() throws Exception {
while (flag){
Thread.sleep(time);//延时
step++;
}
return 1000;
}
...//构造方法以及setter、getter类
新生状态 就绪状态 运行状态 阻塞状态 死亡状态
1.自然终止:线程体正常执行完毕
2.外部干涉:
2)线程体使用该标识
4)外部根据条件调用该方法
1.join:合并线程
package com.zjn.thread.status;
/**
* join:合并线程
*/
public class JoinDemo1 extends Thread{
public static void main(String[] args) throws InterruptedException {
JoinDemo1 demo = new JoinDemo1();
Thread t = new Thread(demo);
t.start();
//cpu调度运行
for (int i=0;i<100;i++){
if(50==i){
t.join();//main阻塞
}
System.out.println("main... "+i);
}
}
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("join..."+i);
}
}
}
2.yield:暂停自己的线程
package com.zjn.thread.status;
public class YieldDemo01 extends Thread {
public static void main(String[] args) {
JoinDemo1 demo = new JoinDemo1();
Thread t = new Thread(demo);
t.start();
//cpu调度运行
for (int i=0;i<100;i++){
if(i%20==0){
//暂停本线程main
Thread.yield();
}
System.out.println("main... "+i);
}
}
@Override
public void run() {
for (int i=0;i<1000;i++) {
System.out.println("yield..."+i);
}
}
}
3.sleep:休眠,不释放锁。排他锁
1)与时间相关:倒计时
package com.zjn.thread.status;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 倒计时
* 1.倒数10个数,1秒内打印一个
* 2.倒计时
*/
public class SleepDemo01 {
public static void main(String[] args) throws InterruptedException {
Date endTime = new Date(System.currentTimeMillis()+10*1000);
long end = endTime.getTime();
while(true){
//输出
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
//构建下一秒时间
endTime = new Date(endTime.getTime()-1000);
//等待一秒
Thread.sleep(1000);
//10秒以内继续,否则退出
if(end-10000>endTime.getTime()){
break;
}
}
}
public static void test1() throws InterruptedException {
int num = 10;
while(true) {
System.out.println(num);
Thread.sleep(1000);//暂停
if(num<0){
break;
}
}
}
}
2)模拟网络延时
package com.zjn.thread.status;
/**
* Sleep模拟网络延时,线程不安全
*/
public class SleepDemo02 {
public static void main(String[] args) {
//真实角色
com.zjn.thread.create.Web12306 web = new com.zjn.thread.create.Web12306();
//代理
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黄牛乙");
Thread t3 = new Thread(web,"工程师");
t1.start();
t2.start();
t3.start();
}
}
class Web12306 implements Runnable {
private int num = 50;
@Override
public void run() {
while(true){
if (num < 0){
break; //跳出循环
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
}
}
}
package com.zjn.thread.info;
/**
* 优先级:概率,不是绝对的优先级,没有绝对先后顺序
* MAX_PRIORITY 10
* NORM_PRIORITY 5 默认
* MIN_PRIORITY 1
*/
public class InfoDemo02 {
public static void main(String[] args) throws InterruptedException {
MyThread it = new MyThread();
Thread p1 = new Thread(it,"挨踢1");
MyThread it2 = new MyThread();
Thread p2=new Thread(it2,"挨踢2");
p1.setPriority(Thread.MIN_PRIORITY);//设置优先级
p2.setPriority(Thread.MAX_PRIORITY);
p1.start();
p2.start();
Thread.sleep(1000);
it.stop();
it2.stop();
}
}
package com.zjn.thread.info;
/**
* Thread currentThread() :当前线程
* setName() :设置名称
* getName() :获取名称
* isAlive()
*/
public class InfoDemo01 {
public static void main(String[] args) throws InterruptedException {
MyThread it = new MyThread();
Thread proxy = new Thread(it,"挨踢");
proxy.setName("test");
System.out.println(proxy.getName());
System.out.println(Thread.currentThread().getName());//main
proxy.start();
System.out.println("启动后的状态"+proxy.isAlive());
Thread.sleep(200);
it.stop();
Thread.sleep(100);
System.out.println("停止后的状态"+proxy.isAlive());
}
}
package com.zjn.thread.info;
public class MyThread implements Runnable {
private boolean flag = true;
private int num = 0;
@Override
public void run() {
while(flag) {
System.out.println(Thread.currentThread().getName()+"-->"+ num++);
}
}
public void stop(){
this.flag = !this.flag;
}
}
同步:并发,多个线程访问同一份资源 确保资源安全 -> 线程安全
Synchronize -> 同步
一、同步块
synchronized(引用类型|类|class){
}
死锁:过多的同步会造成死锁
package com.zjn.thread.syn;
public class SynDemo01 {
public static void main(String[] args) {
//真实角色
com.zjn.thread.create.Web12306 web = new com.zjn.thread.create.Web12306();
//代理
Thread t1 = new Thread(web,"路人甲");
Thread t2 = new Thread(web,"黄牛乙");
Thread t3 = new Thread(web,"工程师");
t1.start();
t2.start();
t3.start();
}
}
class Web12306 implements Runnable {
private int num = 50;
private boolean flag = true;
@Override
public void run() {
while (flag) {
test2();
}
}
//锁定范围不正确
public void test4(){
//a,b,c
synchronized (this) {
if (num < 0) {
flag=false; //跳出循环
return;
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
//线程安全 锁定正确
public void test2(){
if (num < 0) {
flag=false; //跳出循环
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
//线程不安全
public synchronized void test1(){
if (num <= 0) {
flag=false; //跳出循环
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
//线程安全 锁定正确
public void test5(){
//a,b,c
synchronized ((Integer)num) {
if (num < 0) {
flag=false; //跳出循环
return;
}
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了" + num--);
}
}
1.懒汉式
2.饿汉式
package com.zjn.thread.syn;
/**
* 单例设计模式:外部使用时,确保一个类只有一个对象
*/
public class SynDemo02 {
public static void main(String[] args) {
// //单线程中地址空间一样
// Jvm jvm1 = Jvm.getInstance();
// Jvm jvm2 = Jvm.getInstance();
// System.out.println(jvm1);
// System.out.println(jvm2);
// //多线程地址空间不同
// JvmThread thread1 = new JvmThread(100);
// JvmThread thread2 = new JvmThread(500);
// thread1.start();
// thread2.start();
//加入同步,地址空间一样
JvmThread thread1 = new JvmThread(100);
JvmThread thread2 = new JvmThread(500);
thread1.start();
thread2.start();
}
}
class JvmThread extends Thread{
private long time;
public JvmThread() {
}
public JvmThread(long time) {
this.time = time;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm.getInstance(time));
}
}
/**
* 单例设计模式
* 确保一个类只有一个对象
* 懒汉式-使用时才创建对象
* 1。构造器私有化,避免外部直接创建对象
* 2。声明一个私有的静态变量
* 3.创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象
*/
class Jvm {
//声明一个私有的静态变量,静态的同一份资源
private static Jvm instance = null;
//构造器私有化,避免外部直接创建对象
private Jvm(){
}
//double checking双重检查提高已经存在对象的访问效率
public static Jvm getInstance(long time) {
if (null==instance){
//其他线程进入的时候如果发现已经有对象就不用重新创建,直接获取
synchronized (Jvm.class){
if (null == instance) {
try {
Thread.sleep(time);//延时,放大错误
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
}
}
return instance;
}
//synchronized锁类的字节码信息
public static Jvm getInstance3(long time) {
//a b --> 效率不高,进来都需要等待,存在对象也需要等待
synchronized (Jvm.class){
if (null == instance) {
try {
Thread.sleep(time);//延时,放大错误
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}
}
public static synchronized Jvm getInstance2(long time) {
if (null == instance) {
try {
Thread.sleep(time);//延时,放大错误
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}
//创建一个对外的公共的静态方法访问该变量,如果变量没有对象,创建该对象
public static Jvm getInstance1(long time) {
if (null == instance) {
try {
Thread.sleep(time);//延时,放大错误
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Jvm();
}
return instance;
}
}
package com.zjn.thread.syn;
/**
* 单例创建的方式
* 1。懒汉式
* 1)构造器私有化
* 2)声明私有的静态属性
* 3)对外提供访问属性的静态方法,确保该对象存在
*/
public class MyJvm {
private static MyJvm instance;
private MyJvm(){
}
public static MyJvm getInstance(){
if(null==instance){ // 提高效率
synchronized (MyJvm.class){
if(null==instance){ //安全检查
instance = new MyJvm();
}
}
}
return instance;
}
}
/**
* 饿汉式-线程安全
* 1)构造器私有化
* 2)声明私有的静态属性,同时创建该对象
* 3)对外提供访问属性的静态方法
*/
class MyJvm2 {
//类加载时创建
private static MyJvm2 instance = new MyJvm2();
private MyJvm2(){
}
public static MyJvm2 getInstance(){
return instance;
}
}
/**
* 提高效率
* 类在使用的时候加载,JVMholder延缓了加载时间
*/
class MyJvm3 {
private static class JVMholder{
//类加载时创建
private static MyJvm3 instance = new MyJvm3();
}
private MyJvm3(){
}
public static MyJvm3 getInstance(){
return JVMholder.instance;
}
}
package com.zjn.thread.syn;
/**
* 过多的同步方法可能造成死锁
*/
public class SynDemo03 {
public static void main(String[] args) {
Object g = new Object();
Object m = new Object();
//两个线程访问同样资源
Test t1 = new Test(g,m);
Test2 t2 = new Test2(g,m);
Thread proxy = new Thread(t1);
Thread proxy2 = new Thread(t2);
proxy.start();
proxy2.start();
class Test implements Runnable{
Object goods;
Object money;
public Test(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true){
test();
}
}
public void test(){
synchronized (goods){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (money){
}
}
System.out.println("一手给钱");
}
}
class Test2 implements Runnable{
Object goods;
Object money;
public Test2(Object goods, Object money) {
this.goods = goods;
this.money = money;
}
@Override
public void run() {
while (true){
test();
}
}
public void test(){
synchronized (money){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (goods){
}
}
System.out.println("一手给货");
}
}
Producer-consumer problem生产者-消费者问题,也称为有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程-即所谓生产者和消费者——在实际运行时会发生的问题,生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆放弃数据)。等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区中添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常常用的方法有信号灯法、管程等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。
package com.zjn.thread.pro;
public class App {
public static void main(String[] args) {
//共同的资源
Movie m = new Movie();
//多线程
Player p = new Player(m);
Watcher w = new Watcher(m);
new Thread(p).start();
new Thread(w).start();
}
}
package com.zjn.thread.pro;
/**
* 一个场景,共同的资源
* 生产者消费者模式 信号灯法
* wait():等待,释放锁。sleep不释放锁
* notify()/notifyAll():唤醒
* 与synchronized一起使用
*/
public class Movie {
private String pic;
//信号灯
//flag=true生产者生产,消费者等待,生产完成后通知消费
//flag=flag生产者等待,消费者消费,消费完成后通知生产
private boolean flag = true;
/**
* 播放
* @param pic
*/
public synchronized void play(String pic){
if(!flag){//生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始生产
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了"+pic);
//生产完毕
this.pic = pic;
//通知消费
this.notify();
//生产者停下
this.flag = false;
}
public synchronized void watch(){
if (flag){//消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始消费
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了"+pic);
//消费完毕
//通知生产
this.notifyAll();
this.flag = true;
}
}
package com.zjn.thread.pro;
/**
* 生产者
*/
public class Player implements Runnable{
private Movie m;
public Player(Movie m) {
this.m = m;
}
@Override
public void run() {
for(int i=0;i<20;i++){
if(i%2==0){
m.play("左青龙");
}else{
m.play("右白虎");
}
}
}
}
package com.zjn.thread.pro;
/**
* 消费者
*/
public class Watcher implements Runnable{
private Movie m;
public Watcher(Movie m) {
this.m = m;
}
@Override
public void run() {
for(int i=0;i<20;i++){
m.watch();
}
}
}
Timer定时器类
TimerTask任务类
通过java timer timertask:(spring的任务调度就是通过他们来实现的)
在这种实现方式中,Timer类实现的是类似闹钟的功能,也就是定时或是每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其他线程的。而TimerTask类是一个抽象类,该类实现了Runnable接口,所以按照前面的介绍,该类具备多线程的能力。
在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行
在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动一个TimerTask实现。
package com.zjn.thread.schedule;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* schedule(TimerTask task, Date time)
* schedule(TimerTask task, Date time, long period)
*/
public class TimeDemo01 {
public static void main(String[] args) {
Timer timer = new Timer();
//每隔200毫秒运行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("so easy...");
}
},new Date(System.currentTimeMillis()+1000),200);
}
}
# 创建线程
x = threading.Thread(target=thread_function, args=(1,))
# 启动线程
x.start()
要告诉一个线程等待另一个线程结束,请调用.join()
x.join()
线程的运行顺序由操作系统确定,可能很难预测。它可能(并且可能会)因运行而异
threads = list()
for index in range(3):
x = threading.Thread(target=thread_function, args=(index,))
threads.append(x)
x.start()
for index, thread in enumerate(threads):
thread.join()
作为上下文管理器,使用该with语句来管理池的创建和销毁
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
executor.map(thread_function, range(3))
当两个或多个线程访问共享的数据或资源时,可能会发生争用情况。
一次只允许一个线程进入代码的read-modify-write部分。最常见的方法是Lock在Python中调用。在其他一些语言中,相同的想法称为mutex。Mutex来自互斥,这正是a的Lock功能。
由with语句锁定和释放:
class FakeDatabase:
def __init__(self):
self.value = 0
self._lock = threading.Lock()
def locked_update(self, name):
with self._lock:
local_copy = self.value
local_copy += 1
time.sleep(0.1)
self.value = local_copy