线程的实现及高并发,进程

目录

线程背景:

线程特点:

线程的三种方法:

线程状态

守护线程(计数器)

进程背景:

进程特点:

线程的实现

1、继承Thread

2、实现Runnable

3、实现线程池

拓展:线程锁

1、悲观锁和乐观锁

2、两种常见的锁

1、读写锁的实现

2、Synchronized(锁)


线程背景:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

一个进程可以有很多线程,每条线程并行执行不同的任务。

在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。

进程与线程的关系

线程特点:

多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。

1)轻型实体

线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。

线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:

(1)线程状态。

(2)当线程不运行时,被保存的现场资源。

(3)一组执行堆栈。

(4)存放每个线程的局部变量主存区。

(5)访问同一个进程中的主存和其它资源。

用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数返回地址等的一组寄存器堆栈

2)独立调度和分派的基本单位。

在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。

3)可并发执行。

在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

4)共享进程资源。

在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存文件,所以线程之间互相通信不必调用内核

进程与线程的关系

线程的三种方法:

sleep():意思就是睡觉,当前线程暂停一段时间让给别的线程去运行
yield() :就是当前线程正在执行的时候停止下来进入等待队列,yield的意思是我让出一下CPU,由调度系统来调度
join()  :意思就是在自已当前线程加入你调用join的线程(),本线程等待。等调用的线程运行完了,自己再去执行。

package com.cy;
 
 
// 多线程与高并发:认识几个线程的方法
public class Sleep_Yield_Join {
    public static void main(String[] args) {
        //testSleep();
        //testYield();
        testJoin();
    }
 
    /*
    Sleep,意思就是睡觉,当前线程暂停一段时间让给别的线程去运行。
    Sleep是怎么复活的?由你的睡眠时间而定,等睡眠到规定的时间自动复活
     */
    static void testSleep() {
        new Thread( () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ex){
                    ex.printStackTrace();
                }
            }
 
        }).start();
    }
 
    /*
    Yield,就是当前线程正在执行的时候停止下来进入等待队列,在系统的调度
    算法里头,还是依然可能把你刚回去的这个线程拿回来继续执行;
    当然,更大的可能性是把原来等待的那些拿出一个来执行,所以yield的意思是我让出一下CPU,
    后面你们能不能抢到那我不管
     */
    static void testYield() {
        new Thread( () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("A" + i);
                if (i%10 == 0) Thread.yield();
            }
        }).start();
 
        new Thread( () -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("-----------B" + i);
                if (i%10 == 0) Thread.yield();
            }
        }).start();
    }
 
    /*
    join,意思就是在自已当前线程加入你调用join的线程(),本线程等待。等调用的线程运行
    完了,自己再去执行。t1和t2两个线程。在t1的某个点上调用了t2.join,它会跑到t2去运行,
    t1等待t2运行完毕继续t1运行(自己join自己没有意义)
     */
    static void testJoin() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        });
 
        Thread t2 = new Thread(() -> {
            try {
                t1.join(); // 等待t1线程完成,再继续执行
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
 
            for (int i = 0; i < 50; i++) {
                System.out.println("B" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        });
 
        t1.start();
        t2.start();
    }
}

线程状态

线程的状态

附图表示了线程在它的生命周期内的任何时刻所能处的状态以及引起状态改变的方法。这图并不是完整的有限状态图,但基本概括了线程中比较感兴趣和普遍的方面。以下讨论有关线程生命周期以此为据。

●新线程态(New Thread)

产生一个Thread对象就生成一个新线程。当线程处于"新线程"状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。例如,一个线程调用了new方法之后,并在调用start方法之前的处于新线程状态,可以调用start和stop方法。

●可运行态(Runnable)

线程的生命状态与周期

线程的生命状态与周期

start()方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run()方法。在这时线程处于可运行态。该状态不称为运行态是因为这时的线程并不总是一直占用处理机。特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可运行态的线程占用处理 机。Java通过调度来实现多线程处理机的共享。注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。

阻塞/非运行态(Not Runnable)

当以下事件发生时,线程进入非运行态。

①suspend()方法被调用;

②sleep()方法被调用;

③线程使用wait()来等待条件变量

④线程处于I/O请求的等待。

●死亡态(Dead)

当run()方法返回,或别的线程调用stop()方法,线程进入死亡态。通常Applet使用它的stop()方法来终止它产生的所有线程。

线程的本操作:

派生:线程在进程内派生出来,它即可由进程派生,也可由线程派生。

阻塞(Block):如果一个线程在执行过程中需要等待某个事件发生,则被阻塞。

激活(unblock):如果阻塞线程的事件发生,则该线程被激活并进入就绪队列。

调度(schedule):选择一个就绪线程进入执行状态

结束(Finish):如果一个线程执行结束,它的寄存器上下文以及堆栈内容等将被释放。 

守护线程(计数器)

 守护线程是特殊的线程,一般用于在后台为其他线程提供服务.

Java中,isDaemon():判断一个线程是否为守护线程.

Java中,setDaemon():设置一个线程为守护线程.

C# 守护线程

类1:守护线程类

/**
* 本线程设置了一个超时时间
* 该线程开始运行后,经过指定超时时间,
* 该线程会抛出一个未检查异常通知调用该线程的程序超时
* 在超时结束前可以调用该类的cancel方法取消计时
* @author solonote
*/
 
public class TimeoutThread extends Thread{
/**
* 计时器超时时间
*/
private long timeout;
/**
* 计时是否被取消
*/
private boolean isCanceled = false;
/**
* 当计时器超时时抛出的异常
*/
private TimeoutException timeoutException;
/**
* 构造器
* @param timeout 指定超时的时间
*/
public TimeoutThread(long timeout,TimeoutException timeoutErr) {
super();
this.timeout = timeout;
this.timeoutException = timeoutErr;
//设置本线程为守护线程
this.setDaemon(true);
}
/**
* 取消计时
*/
public synchronized void cancel()
{
isCanceled = true;
}
/**
* 启动超时计时器
*/
public void run()
{
try {
Thread.sleep(timeout);
if(!isCanceled)
throw timeoutException;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

进程背景:

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程

进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

进程特点:

动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。

并发性:任何进程都可以同其他进程一起并发执行

独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;

异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的                  速度向前推进

结构特征:进程由程序、数据和进程控制块三部分组成。多个不同的进程可以包含相同的程序:                     一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,                   程序不能发生改变。

线程的实现

1、继承Thread

package cn.tedu.tickets;

/*本类用于改造多线程售票案例,解决数据安全问题*/
public class TestThreadV2 {
    public static void main(String[] args) {
        //5.创建多个线程对象并以多线程的方式运行
        TickectT2 t1 = new TickectT2();
        TickectT2 t2 = new TickectT2();
        TickectT2 t3 = new TickectT2();
        TickectT2 t4 = new TickectT2();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

//1.自定义多线程类
class TickectT2 extends Thread {
    //3.新增成员变量用来保存票数
    static int tickets = 100;
    //static Object o = new Object();

    //2.添加重写的run()来完成业务
    @Override
    public void run() {
        //3.创建循环结构用来卖票
        while (true) {
            //Ctrl+Alt+L调整代码缩进
            //7.添加同步代码块,解决数据安全问题
            //synchronized (new Object()) {
            /*static的Object的对象o这种写法也可以*/
            //synchronized (o) {
            /*我们每通过class关键字创建一个类,就会在工作空间中生成一个唯一对应的类名.class字节码文件
            * 这个类名.class对应的对象我们称之为这个类的字节码对象
            * 字节码对象极其重要,是反射技术的基石,字节码对象中包含了当前类所有的关键信息
            * 所以,用这样一个唯一且明确的对象作为同步代码块的锁对象,再合适不过了*/
            synchronized (TickectT2.class) {/*比较标准的写法*/
                if(tickets > 0){
                    //6.添加线程休眠,暴露问题
                    try {
                        Thread.sleep(10);//让线程休眠,增加线程状态切换的频率
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名与票数-1
                    System.out.println(getName() + "=" + tickets--);
                }
                //4.2给程序设置一个出口,没有票的时候就停止卖票
                if (tickets <= 0) break;
            }
        }
    }
}

2、实现Runnable

package cn.tedu.tickets;

/*本类用于改造多线程售票案例,解决数据安全问题*/
public class TestRunnableV2 {
    public static void main(String[] args) {
        //5.创建目标业务类对象
        TicketR2 target = new TicketR2();
        //6.创建线程对象
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        //7.以多线程的方式运行
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

/*1.多线程中出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*2.同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
 * 加锁之后,就有了同步(排队)的效果,但是加锁的话,需要考虑:
 * 锁的范围:不能太大,太大,干啥都得排队,也不能太小,太小,锁不住,还是会有安全隐患*/
//1.创建自定义多线程类
class TicketR2 implements Runnable {
    //3.定义成员变量,保存票数
    int tickets = 100;
    //创建锁对象
    Object o = new Object();

    //2.实现接口中未实现的方法,run()中放着的是我们的业务
    @Override
    public void run() {
        //4.通过循环结构完成业务
        while (true) {
            /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
            /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
            //synchronized (new Object()){
            //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
            synchronized (o) {//同步代码块解决的是重卖的问题
                //如果票数>0就卖票
                if (tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名以及票数-1
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.2退出死循环--没票的时候就结束
                if (tickets <= 0) break;
            }
        }
    }
}

3、实现线程池

package cn.tedu.tickets;

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

/*本类用于测试线程池*/
public class TestThreadPool {
    public static void main(String[] args) {
        //5.创建接口实现类TicketR3类的对象作为目标业务对象
        TicketR3 target = new TicketR3();
        /*Executors是用来辅助创建线程池的工具类对象
        * 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
        * 创建出来的线程池对象是ExecutorService:用来存储线程的池子,负责:新建/启动/关闭线程*/
        //6.使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService池对象
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            /*execute()让线程池中的线程来执行业务,每次调用都会将一个线程加入到就绪队列*/
            pool.execute(target);/*本方法的参数就是你要执行的业务,也就是目标业务类对象*/
        }
    }
}
//同步锁问题解决方案笔记:1.4.1从26行复制到58行,TicketR2改成TicketR3
//1.创建自定义多线程类
class TicketR3 implements Runnable {
    //3.定义成员变量,保存票数
    int tickets = 100;
    //创建锁对象
    Object o = new Object();

    //2.实现接口中未实现的方法,run()中放着的是我们的业务
    @Override
    public void run() {
        //4.通过循环结构完成业务
        while (true) {
            /*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
             * 同步代码块在同一时刻,同一资源只会被一个线程独享*/
            /*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
            //synchronized (new Object()){
            //修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
            synchronized (o) {//同步代码块解决的是重卖的问题
                //如果票数>0就卖票
                if (tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.1打印当前正在售票的线程名以及票数-1
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.2退出死循环--没票的时候就结束
                if (tickets <= 0) break;
            }
        }
    }
}

拓展:线程锁

1、悲观锁和乐观锁

悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

2、两种常见的锁

synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

1、读写锁的实现

package cn.tedu.thread;

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

/**
 * 本类用于改造售票案例,使用可重入读写锁
 * ReentrantReadWriteLock
 * */
public class TestSaleTicketsV3 {
	public static void main(String[] args) {
		SaleTicketsV3 target = new SaleTicketsV3();
		Thread t1 = new Thread(target);
		Thread t2 = new Thread(target);
		Thread t3 = new Thread(target);
		Thread t4 = new Thread(target);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class SaleTicketsV3 implements Runnable{
	static int tickets = 100;
	//1.定义可重入读写锁对象,静态保证全局唯一
	static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
	@Override
	public void run() {
		while(true) {
			//2.在操作共享资源前上锁
			lock.writeLock().lock();
			try {
				if(tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "=" + tickets--);
				}
				if(tickets <= 0) break;
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				//3.finally{}中释放锁,注意一定要手动释放,防止死锁,否则就独占报错了
				lock.writeLock().unlock();
			}
		}
	}
} 

2、Synchronized(锁)

多个线程去访问同一个资源的时候对这个资源上锁。

什么要上锁呢?访问某一段代码或者临界资源的时候是需要有一把锁的概念在这儿的。比如 :我们对一个数字做递增,两个程序对它一块儿来做递装置,递增就是把一个程序往上加1,如果两个线程共同访问的时候,第一个线程一读它是0,然后把它加1,在自己线程内部内存里面算还没有写回去的时候而第二个线程读到了它还是0,加1再写回去,本来加了两次,但还是1,那么我们在对这个数字递增的过程当中就上把锁,就是说第一个线程对这个数字访问的时候是独占的,不允许别的线程来访问,不允许别的线程来对它计算,我必须加完1后释放锁,其他线程才能对它继续加锁。

实质上,这把锁并不是对数字进行锁定的,你可以任意指定,相锁谁就锁谁。

package com.cy.synchronized;
 
import java.util.Vector;
 
// synchronized 关键字:对某个对象加锁
public class T01 {
    private int count = 10;
    private Object o = new Object();
 
    public void m() {
        synchronized(o) { // 任何线程要想执行下面的代码,必须先拿到o的锁
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        T01 t01 = new T01();
        Vector vector = new Vector();
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(() -> {
                t01.m();
            });
            vector.add(thread);
            thread.start();
        }
 
 
        // 等待所有工作线程执行完成
        for (int i = 0; i < vector.size(); i++) {
            Thread t = (Thread)vector.get(i);
            t.join();
        }
 
        System.out.println("All thread run finished!");
    }
}

如果说你每次都定义一个锁的对象Object o 把它new 出来那加锁的时候太麻烦每次都要new 一个新的对象出来,所以呢,有一个简单的方式就是synchronized(this) 锁定当前对象就行。

package com.cy.synchronized;
 
import java.util.Vector;
 
// synchronized 关键字:对某个对象加锁
public class T02 {
    private int count = 10;
 
    public void m() {
        synchronized(this) { // 任何线程要想执行下面的代码,必须先拿到this 的锁
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        T02 t01 = new T02();
        Vector vector = new Vector();
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(() -> {
                t01.m();
            });
            vector.add(thread);
            thread.start();
        }
 
        // 等待所有工作线程执行完成
        for (int i = 0; i < vector.size(); i++) {
            Thread t = (Thread)vector.get(i);
            t.join();
        }
 
        System.out.println("All thread run finished!");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值