多线程

多线程

基本概念

并发与并行

并发:两个或多个事件在同一时间段发生
并行:两个或多个事件在同一时刻发生(同时发生)

在这里插入图片描述

程序、进程、线程

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

线程分类

java中的线程分为两类:

  1. 守护线程(如垃圾回收线程,异常处理线程)
  2. 用户线程(如主线程)
  • Java创建的线程默认是用户线程。

两者的差别是,当进程中还有用户线程在运行时,进程不终止;当进程中只有守护线程在运行时,进程终止。

线程的生命周期

在这里插入图片描述

创建多线程

继承Thread类创建线程

一、步骤
1.写一个类直接继承Thread类
2.重写run方法.该run()方法的方法体就代表了线程需要完成的任务
3.创建Thread子类的实例
4.调用线程对象的start()方法来启动该线程

public class MyThread extends Thread {
    @Override
    public void run() {
      
    }
}
public class ThreadExample {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

实现Runnable接口创建线程

一、步骤
1.定义Runnable的实现类,重写run()方法
2.创建Runnable实现类的实例,并以此作为Thread的target来创建对象,该对象才是真正的线程对象

public class MyRunnable implements Runnable{
    @Override
    public void run() {
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("这个是实现Runnable");
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        // 第一步
        Runnable runable = new MyRunnable();
        //第二步
        Thread thread = new Thread(runable);
        // 第三步
        thread.start();
    }
}

使用Runnable的优点:

  1. 适合多个相同的程序代码的线程去处理同一个资源
  2. 可以避免java中的单继承的限制
  3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
  4. 线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类

使用线程池创建(使用java.util.concurrent.Executor接口)

步骤

  1. 需要创建实现runnable或者callable接口方式的对象
  2. 创建executorservice线程池
  3. 将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
  4. 关闭线程池
package com.example.paoduantui.Thread;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class NumberThread implements Runnable{


    @Override
    public void run() {
        for(int i = 0;i<=100;i++){
            if (i % 2 ==0 )
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<100; i++){
            if(i%2==1){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args){

        //创建固定线程个数为十个的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //new一个Runnable接口的对象
        NumberThread number = new NumberThread();
        NumberThread1 number1 = new NumberThread1();

        //执行线程,最多十个
        executorService.execute(number1);
        executorService.execute(number);//适合适用于Runnable

        //executorService.submit();//适合使用于Callable
        //关闭线程池
        executorService.shutdown();
    }

}

线程的基本控制方法

在这里插入图片描述

同步的三种实现方式

java线程的同步问题可以通过三种方式实现:
首先创建四个线程


public class Test01 {
	public static void main(String[] args) {
		//创建接口实现类实例化对象
		Runnable r1 = new TicketRunnableImpl();
		//创建线程
		Thread t1 = new Thread(r1, "窗口一");
		Thread t2 = new Thread(r1, "窗口二");
		Thread t3 = new Thread(r1, "窗口三");
		Thread t4 = new Thread(r1, "窗口四");
		//启动线程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

实现方式一:使用synchronized代码块


public class TicketRunnableImpl implements Runnable{
	private int ticketNum = 1000;
	@Override
	public void run() {
		while (ticketNum > 0) {
			//同步代码块
			synchronized (this) {
				//判断
				if (ticketNum > 0) {
					ticketNum--;
					System.out.println(Thread.currentThread().getName() + "售出一张票,剩余:" + ticketNum);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}
}

实现方式二:使用对象锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class TicketRunnableImpl implements Runnable{
	private int ticketNum = 1000;
	//创建锁对象
	Lock lock = new ReentrantLock();
	@Override
	public void run() {
		while (ticketNum > 0) {
			//上锁
			lock.lock();
			//判断
			if (ticketNum > 0) {
				ticketNum--;
				System.out.println(Thread.currentThread().getName() + "售出一张票,剩余:" + ticketNum);
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//解锁
			lock.unlock();
		}
	}
}

实现方式三:使用同步方法


public class TicketRunnableImpl implements Runnable{
	private int ticketNum = 1000;
	@Override
	public void run() {
		while (ticketNum > 0) {
			sellTickets();
		}
	}
	//同步方法
	public synchronized void sellTickets() {
		//判断
		if (ticketNum > 0) {
			ticketNum--;
			System.out.println(Thread.currentThread().getName() + "售出一张票,剩余:" + ticketNum);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

优先使用顺序:
LOCK-》同步代码块-》同步方法

通信

在这里插入图片描述

sleep和wait的异同:
  • 相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
  • 不同点:
  1. 两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
  2. 调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
  3. 关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

线程工具

一、CountDownLatch
1.应用场景
在实际多线程并发开发过程中,我们会碰见很多等待子线程完毕后在继续执行的情况,(如多个子线程下载文件,所有子线程执行完毕后再重命名为文件名)。
2.使用方式
CountDownLatch的构造函数接受一个int类型的参数作为计数器,调用countDwon()方法,计数器减1,await()方法阻塞当前线程,直到计数器变为0;、
补充:
计数器为0的时候,调用awaite()方法不会阻塞主线程;
初始化后,不能修改计数器的值;
可以使用await(long time,TimeUnit unit)等待特定时间后,就不阻塞主线程;


public class Main {   
    //等待2个子线程执行完毕,计数器为2   
    static CountDownLatch countDownLatch = new CountDownLatch(2);   
   
    public static void main(String[] args) {   
        System.out.println("start subThread doing...");   
        //创建并开启2个子线程   
        SubThread subThread1 = new SubThread();   
        SubThread subThread2 = new SubThread();   
        subThread1.start();   
        subThread2.start();   
   
        try {   
            //阻塞主线程,等待子线程结束   
            countDownLatch.await();   
        } catch (InterruptedException e) {   
            e.printStackTrace();   
        }   
   
        System.out.println("subThread are finish...");   
    }   
   
    static class SubThread extends Thread {   
        @Override   
        public void run() {   
            //模拟执行任务   
            try {   
                sleep(3000);   
            } catch (InterruptedException e) {   
                e.printStackTrace();   
            }   
            //子线程执行完毕,减少计数器   
            System.out.println(getName() + " done...");   
            countDownLatch.countDown();   
        }   
    }   
}

运行结果:当Thread-1、Thread-0两个子线程执行完毕后,在运行main线程后续的逻辑


start subThread doing... 
Thread-1 done... 
Thread-0 done... 
subThread are finish...

二、CyclicBarrier
1.应用场景
如果当你遇见需要让一组线程达到同一个屏障(同步点)时被阻塞,直到最后一个线程达到屏障时,屏障才会打开的情况。
2.使用方式
CycliBarrier默认的构造方法CyclicBarrier(int parties),参数标识屏障拦截的线程个数,每个线程调用await()方法告诉SyclicBarrier我们已经达到屏障了,然后当前线程被阻塞。当所有子线程都达到屏障后,则继续执行子线程的后续逻辑。
补充:
CyclicBarrier还提供了一个更高级的函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程达到屏障时,优先执行barrierAction。
3.实例代码

public class Main { 
    //拦截2个子线程屏障 
    static CyclicBarrier cyclicBarrier = new CyclicBarrier(2); 
 
    public static void main(String[] args) { 
        System.out.println("start subThread doing..."); 
        SubThread subThread1 = new SubThread(); 
        SubThread subThread2 = new SubThread(); 
        subThread1.start(); 
        subThread2.start(); 
    } 
 
    static class SubThread extends Thread { 
        @Override 
        public void run() { 
            try { 
                System.out.println(getName() + " doing first things."); 
                //模拟子线程执行第一个任务 
                sleep(3000); 
                System.out.println(getName() + " done first things."); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
             
            try { 
                //完成第一个任务,告知达到屏障 
                cyclicBarrier.await(); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } catch (BrokenBarrierException e) { 
                e.printStackTrace(); 
            } 
 
 
            //所有子线程都完成第一个任务后,继续运行每个子线程的下一个任务 
            System.out.println(getName() + " doing other things."); 
        } 
    } 
} 

运行结果:当子线程都执行完第一个任务到达屏障后,执行下一个任务


start subThread doing... 
Thread-0 doing first things. 
Thread-1 doing first things. 
Thread-1 done first things. 
Thread-0 done first things. 
Thread-0 doing other things. 
Thread-1 doing other things.

三、Semaphore
1.应用场景
多线程访问公共资源的情况在开发过程中经常遇见,如数据库连接,可能开启几十个线程进行并发读取,但是考虑到数据库连接性能和消耗,我们必须控制10个线程哪个是连接数据库。Semaphore就是用来控制同时访问特定资源的线程数量。
2.使用方式
Semaphore的构造方法Semaphore(int permits),permits标识许可证数量。执行任务前,acquire()方法获取一个许可证;任务执行完成后调用relese()方法归还许可证。没有获得许可证的子线程就阻塞等待。
补充:
tryAcquire():尝试获取许可证;
intavaliablePermits():返回信号量中当前许可证的个数;
intgetQueueLength():返回正在等待获取许可证的线程个数;
booleanhasQueueThreads():是否有线程正在等待许可证;
reducePermits(int reduction):减少reduction个许可证;
getQueuedThreads():返回所有等待获取许可证的线程集合;
3.实例代码

public class Main { 
    //创建2个许可证 
    static Semaphore semaphore = new Semaphore(2); 
 
    public static void main(String[] args) { 
        System.out.println("start subThread doing..."); 
        //同时开启4个子线程运行 
        for (int i = 0; i < 4; i++) { 
            SubThread subThread = new SubThread(); 
            subThread.start(); 
        } 
    } 
 
    static class SubThread extends Thread { 
        @Override 
        public void run() { 
            try { 
                //执行任务前获取许可证 
                semaphore.acquire(); 
                System.out.println(getName() + "doing things."); 
                sleep(3000); 
                //执行完任务释放许可证 
                semaphore.release(); 
                System.out.println(getName() + "finish things."); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
        } 
    } 
}

运行结果:同时只有2个线程运行,当某个线程运行完毕释放许可后,下一个线程才获取许可运行;

start subThread doing... 
Thread-0doing things. 
Thread-1doing things. 
Thread-1finish things. 
Thread-2doing things. 
Thread-0finish things. 
Thread-3doing things. 
Thread-2finish things. 
Thread-3finish things. 

线程组

Java中使用ThreadGroup来表示线程组,对一批线程进行管理,控制一个线程组相当于掌控者属于这个线程组内的所有线程,但线程创建时若没有显示指定线程所属的线程组,则会被分到默认的线程组内

当某个线程加入了某个线程组后,该线程的整个周期都会在该线程组中,线程周期内无法改变该线程所属的线程组

创建Thread类时可以指定当前线程所属的线程组

  • Thread(ThreadGroup group,Runnable runnable),该线程属于group线程组,以runnable中的run方法作为线程执行体
  • Thread(ThreadGroup group,String name) 以name命名的新线程,属于group线程组
  • Thread(ThreadGroup group,Runnable runnable,String name) 以name命名的新线程,该线程属于group线程组,以runnable中的run方法作为线程执行体

Thread提供了一个getThreadGroup()方法,返回当前线程所属的线程组对象,该对象的getName()方法获取线程组的名称
ThreadGroup类的两个构造器

  • ThreadGroup(String name) 以指定的name作为名字创建线程组
  • ThreadGroup(ThreadGroup parent,String name) 以指定的父线程组,指定的name作为名字创建线程组
    注意线程组一旦被创建,线程组的名字不可被更改

ThreadGroup类的几个操作线程组内所有线程的常用方法

  • activeCount() 返回线程组内活动的线程数目 int
  • isDaemon() 是否为后台线程组 boolean
  • setDaemon(boolean bool) 设置线程组为后台线程组----最后一个线程执行结束或者被销毁后,该后台线程组自动销毁
  • interrupt() 中断线程组内所有的线程

线程相关类

在这里插入图片描述

ThreadLocal类

ThreadLocal类代表一个线程局部变量,通过把数据放在ThreadLocal中就可以让每个线程创建一个该变量的副本,从而避免并发访问的线程安全问题。
ThreadLocal的功能就是为每个使用该变量的线程都提供一个变量值的副本,使每个线程可以独立地改变自己的副本,而不会和其他线程的副本冲突,从线程的角度看,每个线程都完全拥有该变量一样。
在这里插入图片描述
ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式;而ThreadLocal是为了隔离多个线程的数据共享,从根本上避免多个线程之间对共享资源的竞争。

如果多个线程之间需要共享资源,以达到线程之间的通信功能就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,则可以使用ThreadLocal。

Collection包装线程不安全的集合

ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap等都是线程不安全的,即当多个并发线程都向这些集合中存、取元素时,就可能破坏这些集合的数据完整性。
如果程序中有多个线程可能访问以上集合,那么就可能用Collections提供的静态方法把这些集合包装成线程安全集合。

在这里插入图片描述

线程安全的集合类

1、Concurrent开头集合

以Concurrent开头的集合类,如ConcurrentHashMap、ConcurrentSkipList、ConcurrentSkipListSet、ConcurrentLinkedQueue和ConcurrentLinkedDeque。

2、CopyOnWrite开头集合

以CopyOnWrite开头的集合类,如CopyOnWriteArrayList、CopyOnWriteArraySet。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值