多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作。
有一个容易混淆的概念叫做进程。
进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。
线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源。
一个线程的生命周期
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口.(其实准确来讲,应该有三种,还有一种是实现Callable接口,并与Future、线程池结合使用
(1)继续Thread类
class myThread extends Thread{
private int tid;
//char[] array = {'A','B'};
public myThread(int tid){
this.tid = tid;
}
@Override
public void run(){
try{
for(int i =0; i< 10; ++i){
Thread.sleep(1000);
System.out.println(String.format("%d:%d",tid,i));
//System.out.println((String.format("%d:%c",tid,array[i])));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
(2)
实现
Runable接口
class MultiThreadTests implents Runnable {
public static void myThreadsTest(){
for (int i = 0; i< 2; i++){
@Override
public void run() {
try{
for (int i =0; i<2; i++){
Thread.sleep(1000);
System.out.println(String.format("T2:%d",i));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public static void main(String[] argd){
myThreadsTest();
}
}
或者是
public class MultiThreadTests {
public static void myThreadsTest(){
for (int i = 0; i< 2; i++){
new Thread(new Runnable() {
@Override
public void run() {
try{
for (int i =0; i<2; i++){
Thread.sleep(1000);
System.out.println(String.format("T2:%d",i));
}
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
}
public static void main(String[] argd){
myThreadsTest();
}
}
多线程中有许多的方法:
(a)synchronized的使用:可以用于代码块,可以直接用于方法
代码段:
static Object object = new Object();
//锁测试
public static void synchronizedTest1(){
synchronized (object){
try{
for (int i = 0; i<5; i++){
Thread.sleep(1000);
System.out.println(String.format("T1%d",i));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
用于方法:
public class Thread1 implements Runnable {
public synchronized void run() {
..do something
}
}
(b)BlockingQueue 同步队列
阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管道,特别适用于先进先出策略的一些应用场景。
BlockingQueue在队列的基础上添加了多线程协作的功能:
除了传统的queue功能(表格左边的两列)之外,还提供了阻塞接口put和take,带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞,直到有空间时被唤醒;take在队 列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。
常见的阻塞队列有:
- ArrayListBlockingQueue
- LinkedListBlockingQueue
- DelayQueue
- SynchronousQueue
ConcurrentHashMap
高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap
class Customer implements Runnable{
private BlockingQueue<String> q;
public Customer(BlockingQueue<String> q) {
this.q = q;
}
@Override
public void run(){
try{
while (true){
System.out.println(Thread.currentThread().getName() + ":" + q.take());
}
} catch (Exception e){
e.printStackTrace();
}
}
}
class Producer implements Runnable{
private BlockingQueue<String> q;
public Producer(BlockingQueue<String> q ){
this.q = q;
}
@Override
public void run(){
try{
for(int i = 0; i<100;++i){
Thread.sleep(10);
q.put(String.valueOf(i));
}
}catch (Exception e){
e.printStackTrace();
}
}
}
(c)ThreadLocal:
保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。
主要方法是get()和set(),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。
private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
static String AB = "AB";
public static void ThreadLocalTest(){
for (int i=0; i< AB.length()-1; ++i) {
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 2; ++i) {
stringThreadLocal.set(String.valueOf(AB.charAt(i)));
Thread.sleep(1000);
System.out.println("TheadLocal:" + stringThreadLocal.get());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
(d)原子类:AtomicInteger:
atomicInteger,或者使用自己保证原子的操作,则等同于synchronized
//原子型变量与基本类型变量的在多线程执行操作时的区别
private static int counter = 0;
private static AtomicInteger atomicInteger = new AtomicInteger(0);
//基本类型变量在多线程中是线程不安全的,就是两个线程同时访问一个变量的时候只能一个线程实现操作
private static void testWithoutAtomic(){ //静态方法不能访问非静态的方法,但是非静态的方法是能够访问静态的变量的
for (int i = 0; i <10; i++){
new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(3000);
for (int j = 0; j<10; j++){
counter ++;
System.out.println(counter);
}
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
}
//原子型数据
private static void testWithAtomic(){
for (int i = 0; i<10; ++i){
new Thread(new Runnable() {
@Override
public void run() {
try{
for (int j = 0; j<10; ++j){
System.out.println(atomicInteger.incrementAndGet()); //原子型的数据自增,线程安全的
}
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
}
(e)ThreadPoolExecutor:
Executor 可以创建3种类型的ThreadPoolExecutor线程池:
FixedThreadPool:创建固定长度的线程池,每次提交任务创建一个线程,直到达到线程池的最大数量,线程池的大小不再变化。这个线程池可以创建固定线程数的线程池。特点就是可以重用固定数量线程的线程池。
SingleThreadExecutor:SingleThreadExecutor是使用单个worker线程的Executor。特点是使用单个工作线程执行任务。
CachedThreadPool:CachedThreadPool是一个”无限“容量的线程池,它会根据需要创建新线程。特点是可以根据需要来创建新的线程执行任务,没有特定的corePool
//线程池
public static void ExecutorTest(){
//ExecutorService service = Executors.newSingleThreadExecutor();
ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(new Runnable(){
@Override
public void run() {
try{
for(int i = 0; i<10; ++i){
Thread.sleep(1000);
System.out.println("Executor1:" + i);
}
}catch (Exception e){
e.printStackTrace();
}
}
});
service.submit(new Runnable() {
@Override
public void run() {
try{
for (int i = 0; i<10; ++i){
Thread.sleep(1000);
System.out.println("Executor2:" + i);
}
}catch (Exception e){
e.printStackTrace();
}
}
});
service.shutdown(); //任务执行完之后关闭
//轮询当前线程是否结束
while(!service.isTerminated()){
try{
Thread.sleep(1000);
System.out.println("Waited for termination");
}catch (Exception e){
e.printStackTrace();
}
}}
ExecutorService类内部是通过ThreadPoolExecutor实现的,掌握该类有助于理解线程池的管理,本质上,他们都是ThreadPoolExecutor类的各种实现版本。
(f)Future: 用于等待异步的结果阻塞的等待结果或者是捕获当前线程中的异常
过去,需要异步线程的任务执行结果,要求主线程和任务执行线程之间进行同步和数据传递。
Future简化了任务的异步执行,作为异步操作的一个抽象。调用get()方法可以获取异步的执行结果,如果任务没有执行完,会等待,直到任务完成或被取消,cancel()可以取消。
//Future 等待异步的结果 阻塞的等待结果或者是捕获线程中的异常
private static void testFuture(){
ExecutorService service = Executors.newSingleThreadExecutor();
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception{
Thread.sleep(1000);
//return 1;
throw new IllegalArgumentException("异常");
}
});
service.shutdown();
try{
System.out.println(future.get()); //等待线程执行完成数据的操作之后再获取这个值
//System.out.println(future.get(100,TimeUnit.MILLISECONDS)); //要求100ms执行完要不然报错
}catch (Exception e){
e.printStackTrace();
}
}