网站的访问量非常的巨大,常常会出现多个用户端访问客户端的情况,多线程可以使我们更好去应对这种高并发的情况,最大可能去充分利用cpu,加快访问速度,提升用户使用的舒适度
进程:
1. 进程是程序的一次动态执行过程, 占用特定的地址空间。
2. 每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西,这样的缺点是:浪费内存,cpu的负担较重。
3. 多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占CPU的使用权。
4. 进程的查看
Windows系统: Ctrl+Alt+Del,启动任务管理器即可查看所有进程。
Unix系统: ps or top。
线程:
一个进程可以产生多个线程。同多个进程可以共享操作系统的某些资源一样,同一进程的多个线程也可以共享此进程的某些资源(比如:代码、数据),所以线程又被称为轻量级进程(lightweight process)。
1. 一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。
2. 一个进程可拥有多个并行的(concurrent)线程。
3. 一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。
4. 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
5. 线程的启动、中断、消亡,消耗的资源非常少。
线程和进程的区别:
1. 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销
2. 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
3. 线程和进程最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
4. 多进程: 在操作系统中能同时运行多个任务(程序)。
5. 多线程: 在同一应用程序中有多个顺序流同时执行。
6. 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
7. . 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
8. 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
进程与程序的区别:
程序是一组指令的集合,它是静态的实体,没有执行的含义。而进程是一个动态的实体,有自己的生命周期。一般说来,一个进程肯定与一个程序相对应,并且只有一个,但是一个程序可以有多个进程,或者一个进程都没有。除此之外,进程还有并发性和交往性。简单地说,进程是程序的一部分,程序运行的时候会产生进程。
一.继承Thread类(这种不推荐,类单继承,后期不易于代码维护)
/**
* 创建线程的方式一:
* 创建:继承Thread+重写run方法
* 启动:.start()
* 不推荐,因为java中类单继承,不易于后期代码维护
* @author Zrd
*/
public class StartThread extends Thread{
/**
* run方法是线程入口点
*/
@Override
public void run () {
for(int i=0;i<20;i++){
System.out.println("听歌");
}
}
public static void main(String[] args){
new StartThread().start();
for(int i=0;i<100;i++){
System.out.println("敲代码");
}
}
}
二.实现Runnable接口(推荐,避免单继承的局限性)
/**
* 创建线程的方式二:
* 创建:实现Runnable+重写run
* 启动:创建实现类对象+Thread对象+start
* 推荐:避免了单继承的局限性,优先使用接口,便于后期代码维护
* 方便资源共享
* @author Zrd
*/
public class StartRunnable implements Runnable{
/**
* run方法不能抛出异常,只能try catch
*/
@Override
public void run () {
for(int i=0;i<20;i++){
System.out.println("听歌");
}
}
public static void main (String[] args) {
//创建代理对象
new Thread(new StartRunnable()).start();
for(int i=0;i<100;i++){
System.out.println("敲代码");
}
}
}
三.实现Callable接口(推荐,juc中使用,属于高级并发编程,笔者暂时能力有限,先临摹,等深入了解后再来做分析)
/**
* 创建线程的方式三:
* Callable+重写run;创建执行服务;提交执行;获取结果;关闭服务
* 高级并发编程中使用
* @author Zrd
*/
public class StartCallable implements Callable<Boolean> {
@Override
public Boolean call () throws Exception {
for(int i=0;i<20;i++){
System.out.println("听歌"+i);
}
return true;
}
public static void main (String[] args) throws Exception{
StartCallable c1 = new StartCallable();
StartCallable c2 = new StartCallable();
StartCallable c3 = new StartCallable();
//创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交执行
Future< Boolean > s1 = executorService.submit(c1);
Future< Boolean > s2 = executorService.submit(c2);
Future< Boolean > s3 = executorService.submit(c3);
//获取结果
s1.get();
s2.get();
s3.get();
//关闭服务
executorService.shutdownNow();
}
}
四:线程池(笔者暂时还未涉略,后期深入理解后再做补充)
一个线程对象在它的生命周期内,需要经历5个状态:
操控线程状态的几种方式:
一.sleep(睡眠)
使用sleep,让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。(不会释放锁)
public class TestThreadState {
public static void main(String[] args) {
StateThread thread1 = new StateThread();
thread1.start();
StateThread thread2 = new StateThread();
thread2.start();
}
}
//使用继承方式实现多线程
class StateThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + ":" + i);
try {
Thread.sleep(2000);//调用线程的sleep()方法;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
二:yield (礼让)
使用yield,让正在运行的线程直接进入就绪状态,让出CPU的使用权。(线程重新竞争cpu的使用权,所以礼让不一定成功)
public class TestThreadState {
public static void main(String[] args) {
StateThread thread1 = new StateThread();
thread1.start();
StateThread thread2 = new StateThread();
thread2.start();
}
}
//使用继承方式实现多线程
class StateThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + ":" + i);
Thread.yield();//调用线程的yield()方法;
}
}
}
三.线程的联合join()
线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。如下面示例中,“爸爸线程”要抽烟,于是联合了“儿子线程”去买烟,必须等待“儿子线程”买烟完毕,“爸爸线程”才能继续抽烟。
public class TestThreadState {
public static void main(String[] args) {
System.out.println("爸爸和儿子买烟故事");
Thread father = new Thread(new FatherThread());
father.start();
}
}
class FatherThread implements Runnable {
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完了");
System.out.println("爸爸让儿子去买包红塔山");
Thread son = new Thread(new SonThread());
son.start();
System.out.println("爸爸等儿子买烟回来");
try {
son.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("爸爸出门去找儿子跑哪去了");
// 结束JVM。如果是0则表示正常结束;如果是非0则表示非正常结束
System.exit(1);
}
System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");
}
}
class SonThread implements Runnable {
public void run() {
System.out.println("儿子出门去买烟");
System.out.println("儿子买烟需要10分钟");
try {
for (int i = 1; i <= 10; i++) {
System.out.println("第" + i + "分钟");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("儿子买烟回来了");
}
}
public class TestThread {
public static void main(String[] argc) throws Exception {
Runnable r = new MyThread();
Thread t = new Thread(r, "Name test");//定义线程对象,并传入参数;
t.start();//启动线程;
System.out.println("name is: " + t.getName());//输出线程名称;
Thread.currentThread().sleep(5000);//线程暂停5分钟;
System.out.println(t.isAlive());//判断线程还在运行吗?
System.out.println("over!");
}
}
class MyThread implements Runnable {
//线程体;
public void run() {
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
一.处于就绪状态的线程,会进入“就绪队列”等待JVM来挑选。
二.线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
三.使用下列方法获得或设置线程对象的优先级
int getPriority();
void setPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
public class TestThread {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(), "t1");
Thread t2 = new Thread(new MyThread(), "t2");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
一.什么是线程同步?
线程同步的本质是一种等待机制,在处理多线程问题中,多个线程同时访问一个对象,有些甚至想修改这个对象,假如我们不做人很处理,就会出现数据不正确(线程不安全)的问题,解决思路:多个需要同时访问这个对象的线程进入这个对象的等待池形成队列,等前面的线程处理完毕后,下一个线程再使用
二. 实现线程同步
使用synchronized关键字;它包括两种用法:synchronized 方法和 synchronized 块。
1.synchronized 方法
通过在方法声明中加入 synchronized关键字来声明,语法如下:
public synchronized void accessVal(int newVal);
synchronized 方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
2.synchronized块
相比起synchronized 方法的优势:精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
synchronized(syncObject)
{
//允许访问控制的代码
}
自定义一个类测试synchronized 方法,代码如下
/**
* 线程安全:在并发的时候保证数据的正确性,效率尽可能的高
* 1。采用synchronized 同步方法 (锁的是this)
* @author Zrd
*/
public class SynchronizeTest01 {
public static void main (String[] args) {
SafeWeb12306 safeWeb12306 = new SafeWeb12306();
new Thread(safeWeb12306,"线程一").start();
new Thread(safeWeb12306,"线程二").start();
}
}
/**
* 定义一个抢票类,体会同步方法
*/
class SafeWeb12306 implements Runnable{
/**
* 票数
*/
private int votes=10;
/**
* 标识 判断是否还有票
*/
private boolean flag=true;
@Override
public void run () {
while (flag){
test();
}
}
public synchronized void test(){
/**
* 当票数<=0 时候 直接跳出方法
*/
if(votes<=0){
flag=false;
return;
}
//模拟延时
try {
Thread.sleep(200);
}catch (InterruptedException e){
//阻塞异常
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+votes--);
}
}
自定义一个synchronized块案例,代码如下:
/**
* 线程安全
* 2.采用synchronized(obj){} 同步块直接锁资源,提高性能
* @author Zrd
*/
public class SynchronizeTest02 {
public static void main (String[] args) {
Account account = new Account(150, "总账户");
Withdrawal w1 = new Withdrawal(account, "一号操作员", 30);
Withdrawal w2 = new Withdrawal(account, "二号操作员", 30);
Withdrawal w3 = new Withdrawal(account, "三号操作员", 90);
Withdrawal w4 = new Withdrawal(account, "四号操作员", 90);
new Thread(w1).start();
new Thread(w2).start();
new Thread(w3).start();
new Thread(w4).start();
}
}
/**
* 存款账户类
*/
class Account{
/**
* 余额
*/
int balance;
/**
* 账户姓名
*/
String accountName;
public Account () {
}
public Account (int balance, String accountName) {
this.balance = balance;
this.accountName = accountName;
}
}
/**
* 模拟取款类
*/
class Withdrawal implements Runnable{
/**
* 账户对象
*/
private Account account;
/**
* 操作员
*/
private String operator;
/**
* 取走的金额
*/
private int fundsWithdrawn;
public Withdrawal (Account account, String operator, int fundsWithdrawn) {
this.account = account;
this.operator = operator;
this.fundsWithdrawn = fundsWithdrawn;
}
public Withdrawal () {
}
/**
* 使用synchronized块锁账户资源 account
*/
@Override
public void run () {
//采取优化,当余额<0直接跳出方法, 不需要去锁资源 ,提高性能
if(account.balance-fundsWithdrawn<0){
return;
}
/**
* 注意synchronized只能锁住一个对象
* 当需要同时锁多个对象,将对象进行包装,锁住包装类
*/
synchronized (account){
if(account.balance-fundsWithdrawn<0){
return;
}else {
System.out.println(operator+"取走了"+account.accountName+" "+fundsWithdrawn+"元,剩下"+(account.balance-fundsWithdrawn));
//总账户的钱减去取走的钱
account.balance=account.balance-fundsWithdrawn;
}
}
}
}
一.什么时候会出现死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
死锁的解决方案
出现死锁一般是锁中嵌套锁,解决的方案是:将锁从另外一个锁中解套出来
下面编写一个案例体会死锁的出现,和解决方案
/**
* 死锁:过多的同步可能造成相互不释放资源
* 从而相互等待,一般发生与同步块中持有多个对象的锁
* 解决方案:不要在锁中嵌套锁,解套
* @author Zrd
*/
public class DeadLock {
public static void main (String[] args) {
for(int i=0;i<100;i++){
MakeUp g1 = new MakeUp(true, "一号女孩");
MakeUp g2 = new MakeUp(false, "二号女孩");
new Thread(g1).start();
new Thread(g2).start();
}
}
}
/**
* 口红类
*/
class Lipstick{
}
/**
* 镜子类
*/
class Mirror{
}
/**
* 化妆
*/
class MakeUp implements Runnable{
/**
* 口红对象,采用static 保证只有一只口红
* 对static对象初始化
*/
static Lipstick lipstick=new Lipstick();
/**
* 镜子对象,采用static 保证只有一面镜子
*/
static Mirror mirror=new Mirror();
/**
* true->选择口红,false->选择镜子
*/
boolean choice;
/**
* 化妆女孩对象
*/
String girl;
public MakeUp () {
}
public MakeUp (boolean choice, String girl) {
this.choice = choice;
this.girl = girl;
}
/**
*包含两个案例一个会出现死锁,一个是基于对死锁的解决
*/
@Override
public void run () {
/**
* 死锁案例
*/
try {
makeUpDeadLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 解决后的案例
*/
try {
makeUpDeadLockSolved();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 死锁案例
* 化妆过程 锁中嵌套了锁,容易出现死锁
*/
private void makeUpDeadLock() throws InterruptedException {
if (choice){
//获得口红锁
synchronized (lipstick){
System.out.println(this.girl+"获得口红");
//1秒后 ,获取镜子
Thread.sleep(1000);
//获得镜子锁
synchronized (mirror){
System.out.println(this.girl+"获得镜子");
}
}
}else {
//获得镜子锁
synchronized (mirror){
System.out.println(this.girl+"获得镜子");
//1秒后 ,获取口红
Thread.sleep(1200);
//获得口红锁
synchronized (lipstick){
System.out.println(this.girl+"获得口红");
}
}
}
}
/**
* 解决死锁案例
* 思路,解套
*/
private void makeUpDeadLockSolved() throws InterruptedException {
if (choice){
//获得口红锁
synchronized (lipstick){
System.out.println(this.girl+"获得口红");
}
//1秒后 ,获取镜子
Thread.sleep(1000);
//获得镜子锁
synchronized (mirror){
System.out.println(this.girl+"获得镜子");
}
}else {
//获得镜子锁
synchronized (mirror){
System.out.println(this.girl+"获得镜子");
}
//1秒后 ,获取口红
Thread.sleep(1000);
//获得口红锁
synchronized (lipstick){
System.out.println(this.girl+"获得口红");
}
}
}
}
一 生产者于消费者模式
两种方式实现
1.管程法:通过定义一个缓冲容器,判断缓冲容器中空间存放情况,有空间生产者就生产数据,有数据消费者就可以进行消费,不能的话就线程等待Thread.wait(),生产和消费之间可以相互通知Thread.notiflyAll()
下面编写一个案例体会管程法:
package 多线程.并发.几种解决并发的模式;
/**
* 生产者与消费者实现方式一:管程法
* 解决的问题:1.什么时候能生产,2什么时候能消费
* 借助缓冲容器
* @author Zrd
*/
public class ManagementMethod {
public static void main (String[] args) {
BufferContainer bufferContainer = new BufferContainer();
new Thread(new Producer(bufferContainer)).start();
new Thread(new Consumer(bufferContainer)).start();
}
}
/**
* 生产者
*/
class Producer implements Runnable{
BufferContainer bufferContainer;
public Producer (BufferContainer bufferContainer) {
this.bufferContainer = bufferContainer;
}
@Override
public void run () {
//生产数据
for(int i=0;i<100;i++){
bufferContainer.push(new Data(i));
System.out.println("生产了"+i+"数据");
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable{
BufferContainer bufferContainer;
public Consumer (BufferContainer bufferContainer) {
this.bufferContainer = bufferContainer;
}
@Override
public void run () {
//消费数据
for(int i=0;i<20;i++){
bufferContainer.pop();
System.out.println("消费了"+i+"数据");
}
}
}
/**
* 数据缓冲区
* 本质上是定义一个缓冲容器
*/
class BufferContainer{
/**
* 定义一个大小为10的Data数组,用作数据缓存容器
*/
Data[] datas=new Data[10];
/**
* 计数器
*/
int count=0;
/**
* 存数据
*/
public synchronized void push(Data data) {
/**
* 什么时候能生产数据,容器中存在空间
* 当容器中不存在空间,只能等待
*/
if(count==datas.length){
//this.wait() 当前线程处于阻塞状态,等待消费者的通知
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器有空间可以消费
datas[count]=data;
count++;
//通知消费者消费
this.notifyAll();
}
/**
*获取数据
*/
public synchronized Data pop() {
/**
* 何时能消费?
* 判断容器中是否存在数据
* 容器中没有数据,只能等待
*/
if(count==0){
//this.wait() 当前线程处于阻塞状态,等待生产者的通知
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存在数据可以消费
count--;
//通知生产者生产
this.notifyAll();
return datas[count];
}
}
/**
* 数据
*/
class Data{
/**
* 加入一个成员 好区别
*/
int i;
public Data (int i) {
this.i = i;
}
public Data () {
}
}
2.信号灯法:通过定义一个标识位*,控制生产和消费的实现
下面编写一个案例体会信号灯法:
package 多线程.并发.几种解决并发的模式;
import java.lang.reflect.TypeVariable;
/**
* 生产者与消费者实现方式二:信号灯法
* 解决的问题:1.什么时候能生产,2什么时候能消费
* 借助标识位
* @author Zrd
*/
public class SemaphoreMethod {
public static void main (String[] args) {
Tv tv = new Tv();
new Thread(new Actor(tv)).start();
new Thread(new Audience(tv)).start();
}
}
/**
* 电视类
* 演员表演 和 观众观看
*/
class Tv{
/**
* 选择做的事情
*/
String voice;
/**
* true->演员表演,false->观众观看
*/
boolean flag;
/**
* 演员表演(生产)
*/
public synchronized void play(){
/**
* 借助标识位,控制能否表演
* 等待
*/
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始表演(生产)
System.out.println(this.voice);
//改变标识位
flag=false;
//通知观看(消费) 唤醒等待的线程
this.notifyAll();
}
/**
* 观众观看(消费)
*/
public synchronized void watch(){
/**
* 借助标识位,控制能否观看
* 等待
*/
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//开始观看(消费)
System.out.println(this.voice);
//改变标识位
flag=true;
//通知生产(生产) 唤醒等待的线程
this.notifyAll();
}
}
class Actor implements Runnable{
/**
* 操作tv对象
*/
Tv tv;
public Actor (Tv tv) {
this.tv = tv;
}
@Override
public void run () {
for(int i=0;i<100;i++){
tv.voice="生产(演员)"+i;
tv.play();
}
}
}
class Audience implements Runnable{
/**
* 操作tv对象
*/
Tv tv;
public Audience (Tv tv) {
this.tv = tv;
}
@Override
public void run () {
for(int i=0;i<20;i++){
tv.voice="消费(观众)"+i;
tv.watch();
}
}
}