并发和并行
并行:指两个或多个时间在同一时刻发生(同时发生);
并发:指两个或多个事件在一个时间段内发生。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
进程与线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:进程内部的一个独立执行单元;一个进程可以同时并发的运行多个线程,可以理解为一个进程便相当于一个单 CPU 操作系统,而线程便是这个系统中运行的多个任务。
注意:
1、因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。
2、Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。
3、由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。
多线程的创建
继承Thread
public static void main(String[] args) throws IOException {
SubThread subThread1=new SubThread();
SubThread subThread2=new SubThread();
//调用线程的start(),启动此线程;调用相应的run()方法
subThread1.start();
subThread2.start();
//一个线程只能够执行一次start(),start()中会判断threadStatus的状态是否为0,不为0则抛出异常
//subThread1.start();
//不能通过Thread实现类对象的run()去启动一个线程,此时只是主线程调用方法而已,并没有启动线程
//subThread1.run();
for (int i=0;i<=100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
//1.创建一个继承Thread的子类
class SubThread extends Thread{
//2.重写run方法,方法内实现此子线程要完成的功能
@Override
public void run(){
for (int i=0;i<=100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
如图所示,还可以看到线程的随机性
区别:
①避免java单继承的局限性
②如果多个线程要操作同一份资源,更适合使用实现的方式
实现runnable接口
public class TreadDemo {
public static void main(String[] args) throws IOException {
//此程序存在线程的安全问题,打印车票时,会出现重票、错票,后面线程同步会讲到
Window window=new Window();
Thread thread1=new Thread(window,"窗口一");
Thread thread2=new Thread(window,"窗口二");
Thread thread3=new Thread(window,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window implements Runnable{
int ticket=100;
@Override
public void run(){
while (true){
if(ticket > 0){
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}else {
break;
}
}
}
}
实现callable接口
public class TreadDemo {
public static void main(String[] args) throws IOException {
//此程序存在线程的安全问题,打印车票时,会出现重票、错票,后面线程同步会讲到
FutureTask<Integer> futureTask = new FutureTask<>(new CallDemo());
new Thread(futureTask).start();
}
}
class CallDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ""+i);
}
return 0;
}
}
与前者相比,会有相应的返回这,futask提供了一些阻塞方法来获取返回值
使用线程池
todo 后续进行演示
线程的同步
复现场景
public class TestThread2 {
public static void main(String [] args){
Window window=new Window();
Thread thread1=new Thread(window,"窗口一");
Thread thread2=new Thread(window,"窗口二");
Thread thread3=new Thread(window,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window implements Runnable{
int ticket=50;
@Override
public void run(){
while (true){
if(ticket > 0){
try {
TimeUnit.MILLISECONDS.sleep(200);//模拟卖票需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}else {
System.out.println("票卖完了");
break;
}
}
}
}
输出结构
此时发现会有重票和超卖现象
结果分析
- 当票号为10时:A线程、B线程、C线程同时进入到if(ticket > 0)的代码块中,A线程已经执行了打印输出语句,但是还没有做ticket--操作,ticket--不是原子操作;
- 这时B线程就开始执行了打印操作,那么就会出现两个线程打印票数一样,即卖的是同一张票,当票号为1时:A线程、B线程,C线程同时进入到if(ticket > 0)的代码块中,A线程执行了打印语句,并且已经做完了ticket--操作,则此时ticket=0,判断和ticket--不是原子操作;B线程再打印时就出现了0的情况,同理C线程打印就会出现-1的情况。
解决方案
使用同步代码块
synchronized (this){
if(ticket > 0){
try {
TimeUnit.MILLISECONDS.sleep(200);//模拟卖票需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}else {
System.out.println("票卖完了");
break;
}
}
这样可以保证判断和票数的计算是原子性操作
this需要保证唯一性,因为this指向该对象,所以目前是可以进行保证.
使用同步方法进行
public void run(){
while (true){
sellTicket();
if (ticket<1){
break;
}
}
}
private synchronized void sellTicket(){
if(ticket > 0){
try {
TimeUnit.MILLISECONDS.sleep(200);//模拟卖票需要一定的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
}else {
System.out.println("票卖完了");
}
}
改方法保证卖票操作原子性
线程状态
线程是一个动态执行的过程,它也有从创建到死亡的过程
线程状态枚举
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
相关状态详解:
- 新建状态(new):使用 new 创建一个线程,仅仅只是在堆中分配了内存空间
新建状态下,线程还没有调用 start()方法启动,只是存在一个线程对象而已
Thread t = new Thread();//这就是t线程的新建状态 - 可运行状态(runnable):新建状态调用 start() 方法,进入可运行状态。而这个又分成两种状态,ready 和 running,分别表示就绪状态和运行状态
就绪状态:线程对象调用了 start() 方法,等待 JVM 的调度,(此时该线程并没有运行)
运行状态:线程对象获得 JVM 调度,如果存在多个 CPU,那么运行多个线程并行运行
注意:线程对象只能调用一次 start() 方法,否则报错:illegaThreadStateExecptiong - 阻塞状态(blocked):正在运行的线程因为某种原因放弃 CPU,暂时停止运行,就会进入阻塞状态。此时 JVM 不会给线程分配 CPU,知道线程重新进入就绪状态,才有机会转到 运行状态。
注意:阻塞状态只能先进入就绪状态,不能直接进入运行状态
阻塞状态分为两种情况:
①、当线程 A 处于可运行状态中,试图获取同步锁时,却被 B 线程获取,此时 JVM 把当前 A 线程放入锁池中,A线程进入阻塞状态
②、当线程处于运行状态时,发出了 IO 请求,此时进入阻塞状态 - 等待状态(waiting):等待状态只能被其他线程唤醒,此时使用的是无参数的 wait() 方法
①、当线程处于运行状态时,调用了 wait() 方法,此时 JVM 把该线程放入等待池中 - 计时等待(timed waiting):调用了带参数的 wait(long time)或 sleep(long time) 方法
①、当线程处于运行状态时,调用了带参数 wait 方法,此时 JVM 把该线程放入等待池中
②、当前线程调用了 sleep(long time) 方法 - 终止状态(terminated):通常称为死亡状态,表示线程终止
①、正常终止,执行完 run() 方法,正常结束
②、强制终止,如调用 stop() 方法或 destory() 方法
③、异常终止,执行过程中发生异常
join的相关用法
public class TestThread2 {
public static void main(String [] args) throws InterruptedException {
Thread thread = new Thread(new Window());
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ""+i);
if (i==20){
System.out.println(Thread.currentThread().getState()+"-===");
thread.join();
System.out.println(Thread.currentThread().getState()+"-===");
}
}
}
}
class Window implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ""+i);
}
}
}