Javaの《多线程》课堂学习笔记

一 一些概念

进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。(线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。Eg:音乐播放器有播放歌曲、显示歌词、显示界面。)

同步:排队执行 , 效率低但是安全。
异步:同时执行 , 效率高但是数据不安全。
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。

二 继承Thread

使用流程:
1.自定义一个类,继承Thread类,重写run方法。
2.main方法中,创建新编写的类的对象,调用start方法执行线程。

public class Deno {
    public static void main(String[] args) {
         MyThread m = new MyThread();
         m.start();
    }
}

public class MyThread extends Thread {
    // run方法就是线程要执行的任务方法
    public void run() {
    // 这里的代码就是一条新的执行路径
    // 这个执行路径的触发方式不是调用run方法,而是通过thread对象的start方法来启动任务       
    }
}

public class Deno {
    public static void main(String[] args) {
         MyThread m = new MyThread();
         m.start();
         for (int i = 0; i < 10; i++) {
            System.out.println("汗滴禾下土"+i);
         }
    }
}
public class MyThread extends Thread {

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("锄禾日当午"+i);
        }
    }
}

第一次运行结果:汗滴禾下土0 锄禾日当午0 汗滴禾下土1 锄禾日当午1 ··· 汗滴禾下土6 锄禾日当午6 锄禾日当午7 汗滴禾下土7 锄禾日当午8 汗滴禾下土8 锄禾日当午9 汗滴禾下土9

第二次运行结果:汗滴禾下土0 锄禾日当午0 汗滴禾下土1 锄禾日当午1 ··· 汗滴禾下土3 锄禾日当午3 锄禾日当午4 ··· 锄禾日当午9 汗滴禾下土4 ··· 汗滴禾下土9

原因:两个线程谁快谁慢不确定,所以每次允许结果都会不同。

三 实现Runnable

public class Deno {
    public static void main(String[] args) {
         // 1 创建一个任务对象
         MyRunnable r = nwe MyRunnable()// 2 创建一个线程,并为其分配一个任务
         Thread t = new Thread(r);
         // 3 执行这个线程
         t.start();
    }
}

public class MyRunnable implements Runnable{
   
    public void run() {   // 线程的任务  }
    
}
public class Deno {
    public static void main(String[] args) {
         // 1 创建一个任务对象
         MyRunnable r = nwe MyRunnable();
         // 2 创建一个线程,并为其分配一个任务
         Thread t = new Thread(r);
         // 3 执行这个线程
         t.start();
         for (int i = 0; i < 10; i++) {
            System.out.println("汗滴禾下土"+i);
         }
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        // 线程的任务
        for (int i = 0; i < 10; i++) {
            System.out.println("锄禾日当午"+i);
        }
    }
}

实现Runnable 比 继承 Thread 有以下优势

1,通过创建任务然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况;

2,可以避免单继承带来的局限性;

3,任务与线程本身是分离的,提高了程序的健壮性;

4,后续学习的线程池技术接受Runnable类型的任务,不接收Thread类型的线程。

四 Thread类

1 设置与获取线程名称

String getName();  // 返回此线程的名称。
public class Demo3 {
    public static void main(String[] args) {
        // 如何获取线程的名称
       System.out.println(
             Thread.currentThread().getName());
             
       // 不设置的有默认的名字
       new Thread(new MyRunnable()).start();
       new Thread(new MyRunnable()).start();
       new Thread(new MyRunnable()).start();
             
        // 设置线程名称的方式1
        Thread t = new Thread(new MyRunnable());
        t.setName("wwww");
        t.start();
        
        // 设置线程名称的方式2
       new Thread(new MyRunnable(),"锄禾日当午").start();      
    }
    
    
    static class MyRunnable implements Runnable{
        public void run() {         
               System.out.println(Thread.currentThread().getName());
        }
    }
}
输出结果(每次执行顺序会不同):
main Thread-0 锄禾日当午 wwww Thread-2 Thread-1

2 线程休眠sleep

static void sleep(long millis):导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。

Thread.sleep(1000);     //1000毫秒
public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        //线程的休眠
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            Thread.sleep(1000);     //1000毫秒
        }
    }
}

输出结果:每隔1s输出0、1、2、3···9

线程阻塞:所有消耗时间的操作。不止指线程休眠。如文件读取、接受用户输入。

3 线程中断

void interrupt(); // 中断此线程。 
public class Demo5 {
    public static void main(String[] args) {
      // 一个线程是一个独立的执行路径,它是否结束应该由其自身决定
       Thread t1 = new Thread(new MyRunnable());
       t1.start();
       for (int i = 0; i < 5; i++) {
       Sout(Thread.currentThread().getName()+":"+i); // main+i
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) { 
               e.printStackTrace();
            }
        }
        // 给线程t1添加中断标记
       t1.interrupt();
    }

    static class MyRunnable implements Runnable{
        @Override
        public void run() {
          for (int i = 0; i < 10; i++) {
          Sout(Thread.currentThread().getName()+":"+i); 
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) { //线程中断异常
                //e.printStackTrace();
            System.out.println("发现了中断标记,线程自杀");
                return;
              }
            }
        }
    }
}
输出结果:Thread-0:0 main:0 Thread-0:1 main:1 Thread-0:2 main:2 Thread-0:3 main:3 
Thread-0:4 main:4 Thread-0:5 发现了中断标记,线程自杀

无return的输出结果:main:0 Thread-0:0 main:1 Thread-0:1 main:2 Thread-0:2 
main:3 Thread-0:3 main:4 Thread-0:4 Thread-0:5 发现了中断标记,线程自杀 
Thread-0:6 Thread-0:7 Thread-0:8 Thread-0:9

五 线程不安全

1,现象与原因

public class Demo {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //总票数
        private int count = 5;
        @Override
        public void run() {
            while (count > 0){
                // 卖票
                System.out.print("正在准备卖票 ");
                // 加入休眠sleep
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count --;
                System.out.println("卖票结束,余票:"+count);
            }
        }
    }
}

输出结果:
正在准备卖票 正在准备卖票 正在准备卖票 卖票结束,余票:4
正在准备卖票 卖票结束,余票:3
正在准备卖票 卖票结束,余票:2
正在准备卖票 卖票结束,余票:1
正在准备卖票 卖票结束,余票:-1
卖票结束,余票:0
卖票结束,余票:-2

出现问题:余票负数;(count更大或者多次运行会出现)多个同一“余票i”。

原因:线程A、B、C抢票。仅剩1张票时,A在休眠时,B、C因为count>1再次进入run()程序。A休眠后抢到最后一张票,B、C要走完全部程序。

2,解决方案

同步代码块和同步方法都属于隐式锁

(1)同步代码块

格式:synchronized(锁对象){ }

多个线程观察Object o是否已经打上了锁的标记。打了,表示有线程正在执行,其他线程要等待。一旦这一程序结束,标记解开,所有线程开始抢,打标记。

class MyRunnable implements Runnable {
	//锁对象,多个线程执行此任务时,成员属性为同一个
	private Object o = new Object();
	@Override
	public void run() {
		//同步代码快
		synchronized (o) {
			if () {}
		}
	}
}

public class Demo8 {
    public static void main(String[] args) {
        Object o = new Object();
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        private int count = 10;
        private Object o = new Object();  // o放这,是同一把锁
        @Override
        public void run() {
              // o放这,不是同一把锁,锁不住,还是会出现负票的结果
             // Object o = new Object();  
                while (true) {
                    // synchronized(锁对象){   }
                    synchronized (o) {
                        if (count > 0) {
                            System.out.print("正在准备卖票 ");
                         // 加入休眠sleep
                            try {
                            Thread.sleep(1000);
                            } catch (InterruptedException e) {
                            e.printStackTrace();
                            }
                            count --;             
                            // 加上是哪个线程抢到的票          
                            Sout(Thread.currentThread().getName()+"卖票结束,余票"+count);
                        } else {
                            break;
                        }
                }
            }
        }
    }
}

运行结果:
正在准备卖票 Thread-0卖票结束,余票:9
正在准备卖票 Thread-0卖票结束,余票:8
正在准备卖票 Thread-1卖票结束,余票:7
正在准备卖票 Thread-2卖票结束,余票:6
正在准备卖票 Thread-1卖票结束,余票:5
正在准备卖票 Thread-1卖票结束,余票:4
正在准备卖票 Thread-0卖票结束,余票:3
正在准备卖票 Thread-0卖票结束,余票:2
正在准备卖票 Thread-0卖票结束,余票:1
正在准备卖票 Thread-1卖票结束,余票:0

注:每次运许结果中,Thread-i中i不同,一直为0也可能(多线程)。下同。

(2)同步方法

线程同步:synchronized

class MyThread extends Thread {
	public synchronized void xxx() {}
	//此时锁对象为this
	public synchronized static void xxx() {}
	//此时锁对象为MyThread.class
}
public class Demo9 {
    public static void main(String[] args) {
        Object o = new Object();
        Runnable run = new Ticket();  // 同一把锁
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        private int count = 10;
        @Override
        public void run() {
            while (true) {
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }
        }
        // 加synchronized 
        public synchronized boolean sale(){
            if (count > 0) {
                System.out.print("正在准备卖票 ");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                Sout(Thread.currentThread().getName()+"卖票结束,余票:"+count);
                return true;   
            }
                return false;  // 票卖完返回false,run()不再执行
        }
    }
}

输出结果:
正在准备卖票 Thread-0卖票结束,余票:9
正在准备卖票 Thread-0卖票结束,余票:8
正在准备卖票 Thread-0卖票结束,余票:7
正在准备卖票 Thread-2卖票结束,余票:6
正在准备卖票 Thread-2卖票结束,余票:5
正在准备卖票 Thread-2卖票结束,余票:4
正在准备卖票 Thread-2卖票结束,余票:3
正在准备卖票 Thread-2卖票结束,余票:2
正在准备卖票 Thread-1卖票结束,余票:1
正在准备卖票 Thread-1卖票结束,余票:0

(3)显式锁

线程同步:显式锁 Lock 子类 ReentrantLock

class MyRunnable implements Runnable {
	private Lock l = new ReentrantLock();
	//默认false,表示非公平锁,传入true则代表公平锁,先到先得,可防止回首掏
	@Override
	public void run() {
		l.lock();	//启动锁
		if () {}
		l.unlock();	//关闭锁
	}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo10 {
    public static void main(String[] args) {
        Object o = new Object();
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        private int count = 10;
        // 参数为true表示公平锁    默认是false,不是公平锁
        private Lock l = new ReentrantLock(fair:true);
        @Override
        public void run() {
            while (true) {
                l.lock();   // 锁住
                    if (count > 0) {
                        System.out.print("正在准备卖票 ");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
 Sout(Thread.currentThread().getName()+"卖票结束,余票:"+count);
                    }else {
                        break;  
                    }
                    l.unlock();   // 锁放开
            }
        }
    }
}

显式锁与隐式锁的区别:https://www.cnblogs.com/kaigejava/p/12710602.html
非公平锁:大家一起抢。上面介绍的3个锁都是非公平锁。
公平锁:排队,哪个线程先来就先执行。在Lock的基础上加上判定条件实现。

六 线程死锁

生活例:A进a房,B进b房。A(想进b)等B出来,B(想进a)等A出来。僵住。

七 多线程通信

如:A线程下载音乐,B线程播放音乐。A线程下载完成后通知B线程播放音乐。

void wait(); 
//导致当前线程等待它被唤醒,通常是通知或中断。 

void wait(long timeoutMillis); 
//导致当前线程等待它被唤醒,通常是通知或中断 ,或者直到经过一定量的实时。 

void wait(long timeoutMillis, int nanos); 
//导致当前线程等待它被唤醒,通常是通知或中断 ,或者直到经过一定量的实时。 

notify(); 
//通常用于唤醒wait()状态的线程。

八 线程的六种状态

  • new 尚未启动的状态
  • Runnable 在java虚拟机中执行的线程处于此状态
  • Blocked 排队等待执行的状态
  • Waiting 等待被唤醒的状态
  • Timed Waiting 等待被唤醒/等待若干时间的状态
  • Terminated 退出的状态

九 带返回值的线程Callable

Java的第三种线程。用得少,仅了解。

Thread和Runnable线程是和主线程并发运行。Callable既可以和主线程并发运行,也可以允许主线程等Callable执行完毕,拿到Callable的返回值再运行。Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

1,接口定义

//Callable接口
public interface Callable<V> {
         V call() throws Exception;
}
//Runnable接口
public interface Runnable {
         public abstract void run();
}

2,使用方法

1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
     @Override
     public <T> call() throws Exception {
          return T;
     }
}

2. 创建FutureTask对象, 并传入第一步编写的Callable类对象
      FutureTask<Integer> future = new FutureTask<>(callable);
      
3. 通过Thread,启动线程
new Thread(future).start();

4. 获取线程返回的结果 FutureTask.get()
V get() :如果需要等待计算完成,然后检索其结果。  
V get(long timeout, TimeUnit unit) :
如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。  

3,案例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {

  public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(c);
        new Thread(task).start();
        Integer j = task.get();
        System.out.println("返回值为:"+j);
        for (int i = 0 ; i < 10 ; i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print(i);
        }


    }

    static class MyCallable implements Callable<Integer>{
        public Integer call() throws Exception{
            for (int i = 0 ; i < 10 ; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.print(i+" ");
            }
            return 100;
        }
    }

}
输出结果:
0 1 2 3 4 5 6 7 8 9 返回值为:100
0123456789

4,对比Runnable

相同点:都是接口、都可以编写多线程程序、都采用Thread.start()启动线程

不同点:Runnable没有返回值;Callable可以返回执行结果;Callable接口的call()允许抛出异常;Runnable的run()不能抛出

十 线程池

如果并发的线程数量很多,并且每个线程都执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建和销毁线程需要时间。

线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

线程池的好处:降低资源消耗、提高响应速度、提高线程的可管理性。

Java中的四种线程池:缓存线程池、定长线程池、单线程线程池、周期性任务定长线程池。

1,缓存线程池

无限制长度。
任务加入后的执行流程:
1判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用

ExecutorService service = Executors.newCachedThreadPool();

service.execute(new Runnable(){});

2,定长线程池

长度是指定的线程池。
加入任务后的执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程

// 传入数字代表有几个线程
ExecutorService service = Executors.newFixedThreadPool(2);

service.execute(new Runnable(){});

3,固定线程池

执行流程:
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用

ExecutorService service = Executors.newSingleThreadExecutor();

service.execute(new Runnable(){});

4,周期定长线程池

执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程

周期性任务执行时: 定时执行;当某个任务触发时 自动执行某任务

 ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

 service.schedule();
 // 或
 service.scheduleAtFixedRate();

public class Demo16 {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
 // 定时执行一次
 // 参数:定时执行的任务、时长数字、数字时长的时间单位(Timeunit的常量指定)
        scheduledExecutorService.schedule(new Runnable() {
             public void run() {Sout(Thread.currentThread().getName()+"锄");}
        },delay:5, TimeUnit.SECONDS);      //5秒钟后执行

        /*
        周期性执行任务
            参数1:任务
            参数2:延迟时长数字(第一次在执行上面时间以后)
            参数3:周期时长数字(每隔多久执行一次)
            参数4:时长数字的单位
        */
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
           public void run() {Sout(Thread.currentThread().getName()+"锄");}
        },initialDelay:5,period:1,TimeUnit.SECONDS);
        
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值