襄樊网站建设哪家好,宁波网站建设设计服务公司,义乌住房与城乡建设官网,东莞网站高端建设一、基本概念
1 cpu
CPU的中文名称是中央处理器#xff0c;是进行逻辑运算用的#xff0c;主要由运算器、控制器、寄存器三部分组成#xff0c;从字面意思看就是运算就是起着运算的作用#xff0c;控制器就是负责发出cpu每条指令所需要的信息#xff0c;寄存器就是保存运…一、基本概念
1 cpu
CPU的中文名称是中央处理器是进行逻辑运算用的主要由运算器、控制器、寄存器三部分组成从字面意思看就是运算就是起着运算的作用控制器就是负责发出cpu每条指令所需要的信息寄存器就是保存运算或者指令的一些临时文件这样可以保证更高的速度。也就是我们的线程运行在cpu之上。
2 进程/线程
进程是资源分配最小单位线程是程序执行的最小单位。 计算机在执行程序时会为程序创建相应的进程进行资源分配时是以进程为单位进行相应的分配。每个进程都有相应的线程在执行程序时实际上是执行相应的一系列线程 总结进程是资源分配最小单位线程是程序执行的最小单位 什么是进程 cpu从硬盘中读取一段程序到内存中该执行程序的实例就叫做进程 一个程序如果被cpu多次被读取到内存中则变成多个独立的进程 什么是线程 线程是程序执行的最小单位在一个进程中可以有多个不同的线程同时执行。
3 多线程优点
采用多线程的形式执行代码目的就是为了提高程序的效率。
比如现在一个项目只有一个程序员开发需要开发功能模块会员模块、支付模块、订单模块。 如果小军小明小薇的开发水平一样一个人5天只能完成一个模块那么一个人完成全部工作需要15天3个人同时完成这些工作只需要5天。
4 并行/串行
串行也就是单线程执行 代码执行效率非常低代码从上向下执行 同步操作
并行就是多个线程并行一起执行效率比较高。 异步操作
并发指两个或多个事件在同一个时间段内发生。
5 CPU时间片 单核的cpu上每次只能够执行一次线程如果在单核的cpu上开启了多线程则会发生对每个线程轮流执行 。 Cpu每次单个计算的时间成为一个cpu时间片实际只有几十毫秒人为感觉好像是在多线程。 对于线程来说存在等待cpu调度的时候 该线程的状态是为就绪状态如果被cpu调度则该线程的状态为运行状态 当cpu转让执行其他的线程时则该线程有变为就绪状态。
6 计算密集型/IO密集型
计算密集型长时间占用cpu例如 视频剪辑
IO密集型 cpu计算时间短 访问外接设备时间长Input/output
7 上下文切换
多线程编程中一般线程的个数都大于 CPU 核心的个数而一个 CPU 核心在任意时刻只能被一个线程使用为了让这些线程都能得到有效执行CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用这个过程就属于一次上下文切换。
二、创建方式
创建方式 继承Thread类创建线程 实现Runnable接口创建线程 使用Callable和Future创建线程 使用线程池例如用Executor框架 使用匿名内部类的形式创建线程 使用lambda表达式创建线程 1 继承Thread类创建线程
public class ThreadDemo01 extends Thread{Overridepublic void run() {System.out.println(Thread.currentThread().getName()在运行);}public static void main(String[] args) {ThreadDemo01 demo01 new ThreadDemo01();demo01.start();}
} 2 实现Runnable接口创建线程
public class ThreadDemo02 implements Runnable {Overridepublic void run() {System.out.println(Thread.currentThread().getName() ,我是子线程);}public static void main(String[] args) {new Thread(new ThreadDemo02()).start();}
}
3 使用Callable和Future创建线程
从Java 5开始Java提供了Callable接口该接口是Runnable接口的增强版Callable接口提供了一个call()方法可以看作是线程的执行体但call()方法比run()方法更强大。
call()方法可以有返回值。
call()方法可以声明抛出异常。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class ThreadCallable implements CallableInteger {Overridepublic Integer call() throws Exception {System.out.println(Thread.currentThread().getName());return 1;}public static void main(String[] args) throws ExecutionException, InterruptedException {ThreadCallable callable new ThreadCallable();FutureTaskInteger futureTask new FutureTaskInteger(callable);new Thread(futureTask).start();Integer result futureTask.get();System.out.println(result);}
}
4 使用线程池例如用Executor框架
public class ThreadDemo03{public static void main(String[] args) {ExecutorService service Executors.newCachedThreadPool();//方法参数是Runnable的实现类service.execute(()-System.out.println(Thread.currentThread().getName() 我是子线程));}
}
5 使用匿名内部类的形式创建线程
public class ThreadDemo04 {public static void main(String[] args) {new Thread(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}).start();}
} 6 使用lambda表达式创建线程
public class ThreadDemo04 {public static void main(String[] args) {new Thread(()-System.out.println(Thread.currentThread().getName())).start();}
}
7 Thread和Runnable的区别
实现Runnable接口比继承Thread类所具有的优势 适合多个相同的程序代码的线程去共享同一个资源。 可以避免java中的单继承的局限性。 增加程序的健壮性实现解耦操作代码可以被多个线程共享代码和线程独立。 线程池只能放入实现Runable或Callable类线程不能直接放入继承Thread的类。 三、线程状态 1 新建
当用new关键字创建一个线程时还没调用start 就是新建状态。
2 就绪
调用了 start 方法之后线程就进入了就绪阶段。此时线程不会立即执行run方法需要等待获取CPU资源。
3 运行
当线程获得CPU时间片后就会进入运行状态开始执行run方法。
4 阻塞
当遇到以下几种情况线程会从运行状态进入到阻塞状态。 调用wait方法jvm会将线程放入等待队列waiting queue使线程进入等待。 当线程去获取同步锁的时候锁正在被其他线程持有jvm会将该线程放入锁池lock pool 其他阻塞运行状态的线程调用sleep方法、join方法或者进入io请求的时候会导致线程阻塞。
需要注意的是阻塞状态只能进入就绪状态不能直接进入运行状态。因为从就绪状态到运行状态的切换是不受线程自己控制的而是由线程调度器所决定。只有当线程获得了CPU时间片之后才会进入运行状态。
5 死亡
当run方法正常执行结束时或者由于某种原因抛出异常都会使线程进入死亡状态。另外直接调用stop方法也会停止线程。但是此方法已经被弃用不推荐使用。
四、线程的基本方法
1 线程等待wait方法 调用wait()方法线程会进入WATING状态只有等到其他线程的通知活被中断后才会返回。需要注意的是在调用wait()方法的后会释放对象的锁因此wait()方法一般被用到同步方法或者同步代码块中。
2 线程睡眠sleep方法 用sleep方法会导致当前线程休眠。与 wait方法不同的是sleep方法不会释放当前占有的锁会导致线程进人 TIMED-WATING 状态而 wait方法会导致当前线程进入WATING.
3 线程让步 yield 方法
调用yield 方法会使当前线程让出(释放CPU执行时间片与其他线程一起重新竞争CPU 时间片。在一般情况下优先级高的线程更有可能竞争到CPU时间片但这不是绝对的,有的操作系统对线程的优先级并不敏感。
4 线程中断interrupt方法 interrupt方法用于向线程发行一个终止通知信号会影响该线程内部的一个中断标识位这个线程本身并不会因为调用了interrupt方法而改变状态阻塞、终止等)。状态的具体变化需要等待接收到中断标识的程序的最终处理结果来判定。
5 线程加入 join方法 8 start方法和run方法的区别 join方法用于等待其他线程终止如果在当前线程中调用一个线程的 join方法则当前线程转为阻塞状态等到另一个线程结束当前线程再由阻塞状态转为就绪状态等待获取CPU 的使用权。在很多情况下主线程生成并启动了子线程,需要等到子线程返回结果并收集和处理再退出这时就要用到join方法具体的使用方法如下: System.out.println(”子线程运行开始!):
ChildThread childThread - new ChildThread();
childThread.join(0);//等待子线程childThread执行结束
System.out.println(子线join ()结束开始运行主线程); 6 线程唤醒 notify方法 Object类有个 notify方法用于唤醒在此对象监视器上等待的一个线程,如果所有线程都在此对象上等待则会选择唤醒其中一个线程,选择是任意的我们通常调用其中一个对象的 wait方法在对象的监视器上等待 直到当前线程放弃此对象上的锁定才能继续执行被唤醒的线程被唤醒的线程将以常规方式与在该对象上主动同步的其他线程竞争。类似的方法还有notifyAll用于唤醒在监视器上等待的所有线程。 7 sleep方法与 wait方法的区别如下 sleep方法属于Thread类wait方法则属于Object类。 sleep方法暂停执行指定的时间让出 CPU 给其他线程但监控器状态依然保持在指定的时间过后又会自动恢复运行状态。 start方法用于启动线程,真正实现了多线程运行。在调用了线程的start方法后线程会在后台执行无须等待run方法体的代码执行完毕就可以继续执行下面的代码。 在通过调用 Thread 类的 start方法启动一个线程时,此线程处于就绪状态并没有运行。 run方法也叫作线程体包含了要执行的线程的逻辑代码,在调用run方法后线程就进入运行状态开始运行 run方法中的代码。在run方法运行结束后,该线程终止CPU再调度其他线程 在调用sleep方法的时候线程不会释放对象锁。 在调用wait方法时,线程会放弃对象锁进入等此对象的等待锁池只有针对此对象调用notify方法后,该线程才能进人对象锁池准备获取对象锁并进入运行状态。 五、线程安全问题
1 问题出现的原因
如果有多个线程在同时运行而这些线程可能会同时操作一个变量。程序每次运行结果和单线程运行的结果是一样
的而且其他的变量的值也和预期的是一样的就是线程安全的。
# 我们通过一个案例演示线程的安全问题 电影院要卖票我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”本次电影的座位共100个
(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)
需要窗口采用线程对象来模拟需要票Runnable接口子类来模拟
public class Ticket implements Runnable {private int ticket 100;/** 执行卖票操作*/Overridepublic void run() {//每个窗口卖票的操作//窗口 永远开启while (true) {if (ticket 0) {//有票 可以卖//出票操作//使用sleep模拟一下出票时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//获取当前线程对象的名字String name Thread.currentThread().getName();System.out.println(name 正在卖: ticket--);}}}
}
public class Demo01 {public static void main(String[] args) {Ticket ticket new Ticket();Thread t1 new Thread(ticket, 窗口1);Thread t2 new Thread(ticket, 窗口2);t1.start();t2.start();}
} 可以看到同一张票被买了两次这种问题几个窗口(线程)票数不同步了这种问题称为线程不安全。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作而无写 操作一般来说这个全局变量是线程安全的若有多个线程同时执行写操作一般都需要考虑线程同步 否则的话就可能影响线程安全。
2 如何解决问题
当我们使用多个线程访问同一资源的时候且多个线程中对资源有写的操作就容易出现线程安全问题。 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题Java中提供了同步机制(synchronized)来解决。
窗口1线程进入操作的时候窗口2线程只能在外等着窗口1操作结束窗口1和窗口2才有机会进入代码 去执行。也就是说在某个线程修改共享资源的时候其他线程不能修改该资源等待修改完毕同步之后才能去抢夺CPU 资源完成对应的操作保证了数据的同步性解决了线程不安全的现象。 3 解决方法 同步代码块。 同步方法。 锁机制 同步代码块
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁. 锁对象 可以是任意类型。 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
synchronized 关键字可以用于方法中的某个区块中表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){需要同步操作的代码
}
public class Ticket implements Runnable {private int ticket 100;Object lock new Object();/** 执行卖票操作*/Overridepublic void run() {//每个窗口卖票的操作//窗口 永远开启while (true) {synchronized (lock){if (ticket 0) {//有票 可以卖//出票操作//使用sleep模拟一下出票时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//获取当前线程对象的名字String name Thread.currentThread().getName();System.out.println(name 正在卖: ticket--);}}}}
}
当使用了同步代码块后上述的线程的安全问题解决了。 同步方法
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外 等着。
同步锁是谁? 对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
格式
public synchronized void method(){可能会产生线程安全问题的代码
}
public class Ticket implements Runnable {private int ticket 100;Overridepublic void run() {//每个窗口卖票的操作 ,窗口 永远开启while(true){sellTicket();}}/** 锁对象 是 谁调用这个方法 就是谁* 隐含 锁对象 就是 this**/public synchronized void sellTicket(){if(ticket0){//有票 可以卖//出票操作//使用sleep模拟一下出票时间try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//获取当前线程对象的名字String name Thread.currentThread().getName();System.out.println(name正在卖:ticket--);}}
}
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁加锁与释放锁方法化了如下 public void lock() :加同步锁。 public void unlock() :释放同步锁。 语法格式
Lock lock new ReentrantLock();
try{lock.lock();//加锁操作
}finally{lock.unlock();
}
public class Ticket implements Runnable {private int ticket 100;Lock lock new ReentrantLock();/** 执行卖票操作*/Overridepublic void run() {//每个窗口卖票的操作永远开启while(true){lock.lock();if(ticket0){//有票 可以卖//出票操作//使用sleep模拟一下出票时间try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}//获取当前线程对象的名字String name Thread.currentThread().getName();System.out.println(name正在卖:ticket--);}lock.unlock();}}
}