Java进程和多线程
进程和线程的一些概述
进程:
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
多进程:
单进程计算机只能做一件事情。而我们现在计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程的操作系统。比如:Windows,Linux和Mac等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏和音乐进程是同时运行的吗?并不是,因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间频繁切换,且切换速度很快,所以让我们感觉游戏和音乐是同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
线程:
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU基本单位。所以,进程是拥有资源的基本单位,线程是CPU调度的基本单位.
多线程:
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率
因为我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率应该比单线程程序抢到的概率要大,那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率,但是即使是多线程程序,那么他们中的那个线程能抢到CPU资源呢,这个是不确定的,所以多线程具有随机性。
为什么不讲Java的多进程编程?
由于用Java写的程序都是在JVM中运行,对操作系统来说,JVM就是一个进程,因为JVM其实就是java.exe运行起来,因为一个进程中不能进行多进程编程,所以java不讲多进程编程而讲多线程编程。
并发:
指在某一个时间点同时运行多个程序。并发就是多线程的原理,就是假如你有两个任务执行,多线程的原理就是让你几乎不可能察觉到的速度不断去切换执行这个两个任务,“已达到同时执行的效果”,其实并不是,是计算机的速度太快了,我们无法察觉到而已。
并行:
指在某一个时间内同时运行多个程序。并行就是指在同一时刻,有多条指令在多个处理器上同时执行,所以无论从微观还是宏观来看,二者都是一起执行的。
多线程程序实现的方式
方式一:
1.创建一个任务类去继承Thread类
2.重写run()方法;将要执行的代码写进去
3.在main()中实例化任务类并调用start();方法开启线程
为什么开启线程是用start();方法而不是去调用run();方法
你如果调用run();方法的话,就是当线程去执行一个普通函数而已,根本没有什么新线程创建出来。而调用start();方法,则会在主线程中重新创建一个新线程,等到cpu的时间段后则会执行所对应的run();方法体的代码。
方式二:
1.创建一个任务类去实现这个Runnable接口
2.重写run方法
3.在main()方法中创建这个Runnable接口的子类对象
4.在main()方法中创建Thread类对象,把Runnable接口的子类对象传进去
5.Thread类对象调用start();方法,启动线程
方式三:
1.创建一个任务类去实现这个Callable接口
2.重写call();方法相当于run();方法,将要执行的代码放进去
3.在main()方法中创建这个Callable接口的子类对象
4.在main()方法中创建这个FutureTask类将Callable接口的子类对象作为参数传进去
5.在main()方法中创建Thread类,将FutureTask对象作为参数传进去
6.Thread类对象调用start();方法,启动线程
Runnable接口和Callable任务接口的区别
1.Runnable任务接口的run方法没有返回值,也不能抛出异常;而Callable<Object>任务接口中的call方法,是有返回值的,并且可以抛出异常。2.执行Callable方式,需要FutureTask实现类的支持,用于接受运算结果。FutureTask是Future接口的实现类。
案列:三个窗口一起卖票100张
public class MyTest {
public static void main(String[] args) {
CellThread th1 = new CellThread("窗口1");
CellThread th2 = new CellThread("窗口2");
CellThread th3 = new CellThread("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class CellThread extends Thread{
static int ticket=100;
public CellThread() {
}
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":正在卖"+(ticket--)+"张票");
}
}
}
}
结果是:
窗口1:正在卖100张票
窗口1:正在卖97张票
窗口1:正在卖96张票
窗口1:正在卖95张票
窗口1:正在卖94张票
窗口1:正在卖93张票
窗口1:正在卖92张票
窗口1:正在卖91张票
窗口1:正在卖90张票
窗口1:正在卖89张票
窗口1:正在卖88张票
窗口1:正在卖87张票
窗口1:正在卖86张票
窗口1:正在卖85张票
窗口1:正在卖84张票
窗口1:正在卖83张票
窗口1:正在卖82张票
窗口1:正在卖81张票
窗口1:正在卖80张票
窗口1:正在卖79张票
窗口1:正在卖78张票
窗口1:正在卖77张票
窗口1:正在卖76张票
窗口1:正在卖75张票
窗口1:正在卖74张票
窗口1:正在卖73张票
窗口1:正在卖72张票
窗口1:正在卖71张票
窗口1:正在卖70张票
窗口3:正在卖98张票
窗口3:正在卖68张票
窗口3:正在卖67张票
窗口3:正在卖66张票
窗口3:正在卖65张票
窗口3:正在卖64张票
窗口3:正在卖63张票
窗口3:正在卖62张票
窗口3:正在卖61张票
窗口3:正在卖60张票
窗口3:正在卖59张票
窗口3:正在卖58张票
窗口3:正在卖57张票
窗口3:正在卖56张票
窗口3:正在卖55张票
窗口3:正在卖54张票
窗口3:正在卖53张票
窗口3:正在卖52张票
窗口2:正在卖99张票
窗口2:正在卖50张票
窗口2:正在卖49张票
窗口2:正在卖48张票
窗口2:正在卖47张票
窗口2:正在卖46张票
窗口2:正在卖45张票
窗口2:正在卖44张票
窗口2:正在卖43张票
窗口2:正在卖42张票
窗口2:正在卖41张票
窗口2:正在卖40张票
窗口2:正在卖39张票
窗口2:正在卖38张票
窗口2:正在卖37张票
窗口2:正在卖36张票
窗口2:正在卖35张票
窗口2:正在卖34张票
窗口2:正在卖33张票
窗口2:正在卖32张票
窗口2:正在卖31张票
窗口2:正在卖30张票
窗口2:正在卖29张票
窗口2:正在卖28张票
窗口2:正在卖27张票
窗口2:正在卖26张票
窗口2:正在卖25张票
窗口2:正在卖24张票
窗口2:正在卖23张票
窗口2:正在卖22张票
窗口2:正在卖21张票
窗口2:正在卖20张票
窗口2:正在卖19张票
窗口2:正在卖18张票
窗口2:正在卖17张票
窗口2:正在卖16张票
窗口2:正在卖15张票
窗口2:正在卖14张票
窗口2:正在卖13张票
窗口2:正在卖12张票
窗口2:正在卖11张票
窗口3:正在卖51张票
窗口3:正在卖9张票
窗口3:正在卖8张票
窗口3:正在卖7张票
窗口3:正在卖6张票
窗口3:正在卖5张票
窗口3:正在卖4张票
窗口3:正在卖3张票
窗口3:正在卖2张票
窗口3:正在卖1张票
窗口1:正在卖69张票
窗口2:正在卖10张票
上面的结果看似合理,但是这是理想状况,一旦出现网络延迟等问题,上面的结果就会出现零票和重复卖票,所以上面run方法中的代码存在线程安全性问题。下面将上面代码改动一下模拟一下网络延迟,看一下结果。
public class MyTest {
public static void main(String[] args) {
CellThread th1 = new CellThread("窗口1");
CellThread th2 = new CellThread("窗口2");
CellThread th3 = new CellThread("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class CellThread extends Thread{
static int ticket=100;
public CellThread() {
}
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":正在卖"+(ticket--)+"张票");
}
}
}
}
结果是:
窗口3:正在卖100张票
窗口2:正在卖99张票
窗口1:正在卖98张票
窗口3:正在卖97张票
窗口1:正在卖96张票
窗口2:正在卖95张票
窗口2:正在卖93张票
窗口1:正在卖94张票
窗口3:正在卖94张票
窗口1:正在卖91张票
窗口3:正在卖91张票
窗口2:正在卖92张票
窗口3:正在卖90张票
窗口2:正在卖89张票
窗口1:正在卖90张票
窗口1:正在卖88张票
窗口3:正在卖87张票
窗口2:正在卖86张票
窗口2:正在卖85张票
窗口1:正在卖84张票
窗口3:正在卖85张票
窗口3:正在卖83张票
窗口2:正在卖82张票
窗口1:正在卖82张票
窗口3:正在卖81张票
窗口1:正在卖79张票
窗口2:正在卖80张票
窗口3:正在卖77张票
窗口1:正在卖78张票
窗口2:正在卖76张票
窗口1:正在卖75张票
窗口2:正在卖75张票
窗口3:正在卖75张票
窗口3:正在卖74张票
窗口1:正在卖73张票
窗口2:正在卖73张票
窗口3:正在卖72张票
窗口1:正在卖70张票
窗口2:正在卖71张票
窗口2:正在卖69张票
窗口1:正在卖67张票
窗口3:正在卖68张票
窗口3:正在卖66张票
窗口1:正在卖65张票
窗口2:正在卖65张票
窗口2:正在卖64张票
窗口3:正在卖63张票
窗口1:正在卖62张票
窗口1:正在卖61张票
窗口3:正在卖60张票
窗口2:正在卖59张票
窗口1:正在卖58张票
窗口2:正在卖57张票
窗口3:正在卖56张票
窗口2:正在卖54张票
窗口1:正在卖53张票
窗口3:正在卖55张票
窗口3:正在卖52张票
窗口1:正在卖51张票
窗口2:正在卖51张票
窗口3:正在卖50张票
窗口2:正在卖49张票
窗口1:正在卖49张票
窗口3:正在卖48张票
窗口2:正在卖46张票
窗口1:正在卖47张票
窗口3:正在卖45张票
窗口1:正在卖44张票
窗口2:正在卖44张票
窗口3:正在卖43张票
窗口1:正在卖41张票
窗口2:正在卖42张票
窗口2:正在卖40张票
窗口3:正在卖39张票
窗口1:正在卖39张票
窗口1:正在卖38张票
窗口3:正在卖37张票
窗口2:正在卖36张票
窗口2:正在卖35张票
窗口1:正在卖33张票
窗口3:正在卖34张票
窗口2:正在卖32张票
窗口1:正在卖31张票
窗口3:正在卖31张票
窗口2:正在卖30张票
窗口1:正在卖30张票
窗口3:正在卖30张票
窗口3:正在卖29张票
窗口1:正在卖28张票
窗口2:正在卖27张票
窗口2:正在卖26张票
窗口1:正在卖25张票
窗口3:正在卖25张票
窗口1:正在卖24张票
窗口3:正在卖24张票
窗口2:正在卖23张票
窗口2:正在卖22张票
窗口1:正在卖21张票
窗口3:正在卖21张票
窗口2:正在卖20张票
窗口3:正在卖20张票
窗口1:正在卖20张票
窗口2:正在卖19张票
窗口3:正在卖17张票
窗口1:正在卖18张票
窗口2:正在卖16张票
窗口1:正在卖14张票
窗口3:正在卖15张票
窗口1:正在卖12张票
窗口2:正在卖12张票
窗口3:正在卖13张票
窗口3:正在卖11张票
窗口2:正在卖10张票
窗口1:正在卖9张票
窗口1:正在卖8张票
窗口2:正在卖6张票
窗口3:正在卖7张票
窗口2:正在卖5张票
窗口3:正在卖5张票
窗口1:正在卖5张票
窗口2:正在卖4张票
窗口3:正在卖3张票
窗口1:正在卖3张票
窗口1:正在卖2张票
窗口3:正在卖1张票
窗口2:正在卖0张票
出现了0票和重复票,这就是线程安全问题
出现线程安全性问题条件:
1.是不是多线程
2.多个线程是否有去操作共享资源
3.是不是有多条语句在操作这个共享资源,也就是对共享资源的操作,是不是一个原子性的操作
原子性的操作:
原子性的操作可以看成是一个步骤,也可以是多个操作步骤,但是顺序不可以被打乱,也不可以被切割而只执行其中的一部分(不可中断性)。将整个操作视为一个整体是原子性的核心
而上面代码中ticket++;是不是一个原子性操作呢?
当然是不是的,要不然不会出现重复票和零票等结果,它的步骤分为三步:读 改 写
说到这里就说一下java内存模型
java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主存。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递需要自己工作内存和主存之间进行数据同步进行。
所以这样就可以解释为何会出现重复票和零票了,比如窗口1将第100张票读到工作内存中将100改成99被等待,这时窗口2抢到时间片进来,窗口2也将第100张票读到工作内存中改成99,然后将99写入主存中被等待,然后窗口1抢到时间片,将99存入到主存中覆盖掉窗口二的读进的99,所以就会出现重复票,零票也是这样出现的,窗口3将第1张票读到主存中改掉0还没有将0写入到主存就被等待,这时窗口2抢到时间片执行if(ticket>0)代码这时主存里的ticket是1然后线程3这时醒了抢到时间片将0存到主存中,然后窗口2继续运行将0打印出来,并将-1写入主存。
解决线程安全问题的方法:
1.我们可以使用一个同步代码块,把有可能出现线程安全问题的代码包裹起来
synchronized(锁对象){
需要同步的代码;
}
锁对象:可以是Java中任意的对象
同步代码块:就是每一个线程要进同步代码块,就会持有一把锁其他线程没有获取到锁对象,就处于等待状态,一个线程除了同步代码块就会释放锁,这时所有线程再抢锁对象,随机的,抢到就可以进同步代码块。
2.同步方法:
就是在方放上加入synchronized关键字,就成了同步方法,同步方法的锁对象是this,那个对象谁调用它,那个对象就是锁。
3.Lock接口
Java为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock中的加锁和释放锁方法:
void lock(); 加锁
void unlock(); 释放锁
一般用Lock接口加锁和释放锁时,会将它的实现类ReentrantLock对象创建出来应用
典型的使用Lock锁的代码:
class X{
private final ReentrantLock lock=new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
同步的好处:同步的出现解决了多线程的安全问题
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这时很耗费资源的,无形中会降低程序的运行效率。
public class MyTest {
public static void main(String[] args) {
Object obj = new Object();
CellThread th1 = new CellThread("窗口1",obj);
CellThread th2 = new CellThread("窗口2",obj);
CellThread th3 = new CellThread("窗口3",obj);
th1.start();
th2.start();
th3.start();
}
}
class CellThread extends Thread{
private Object obj;
static int ticket=100;
public CellThread() {
}
public CellThread(String name,Object obj) {
super(name);
this.obj=obj;
}
@Override
public void run() {
while (true){
synchronized (obj){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":正在卖"+(ticket--)+"张票");
}
}
}
}
}
结果是:
窗口1:正在卖100张票
窗口1:正在卖99张票
窗口1:正在卖98张票
窗口1:正在卖97张票
窗口1:正在卖96张票
窗口1:正在卖95张票
窗口1:正在卖94张票
窗口1:正在卖93张票
窗口1:正在卖92张票
窗口1:正在卖91张票
窗口1:正在卖90张票
窗口1:正在卖89张票
窗口1:正在卖88张票
窗口1:正在卖87张票
窗口1:正在卖86张票
窗口1:正在卖85张票
窗口1:正在卖84张票
窗口1:正在卖83张票
窗口1:正在卖82张票
窗口1:正在卖81张票
窗口1:正在卖80张票
窗口1:正在卖79张票
窗口1:正在卖78张票
窗口1:正在卖77张票
窗口1:正在卖76张票
窗口1:正在卖75张票
窗口1:正在卖74张票
窗口1:正在卖73张票
窗口1:正在卖72张票
窗口1:正在卖71张票
窗口1:正在卖70张票
窗口1:正在卖69张票
窗口1:正在卖68张票
窗口1:正在卖67张票
窗口1:正在卖66张票
窗口1:正在卖65张票
窗口1:正在卖64张票
窗口1:正在卖63张票
窗口1:正在卖62张票
窗口1:正在卖61张票
窗口1:正在卖60张票
窗口1:正在卖59张票
窗口1:正在卖58张票
窗口1:正在卖57张票
窗口1:正在卖56张票
窗口1:正在卖55张票
窗口1:正在卖54张票
窗口1:正在卖53张票
窗口1:正在卖52张票
窗口1:正在卖51张票
窗口1:正在卖50张票
窗口1:正在卖49张票
窗口1:正在卖48张票
窗口1:正在卖47张票
窗口1:正在卖46张票
窗口1:正在卖45张票
窗口1:正在卖44张票
窗口1:正在卖43张票
窗口1:正在卖42张票
窗口1:正在卖41张票
窗口1:正在卖40张票
窗口1:正在卖39张票
窗口1:正在卖38张票
窗口1:正在卖37张票
窗口1:正在卖36张票
窗口1:正在卖35张票
窗口1:正在卖34张票
窗口1:正在卖33张票
窗口1:正在卖32张票
窗口1:正在卖31张票
窗口1:正在卖30张票
窗口1:正在卖29张票
窗口1:正在卖28张票
窗口1:正在卖27张票
窗口1:正在卖26张票
窗口1:正在卖25张票
窗口1:正在卖24张票
窗口1:正在卖23张票
窗口1:正在卖22张票
窗口1:正在卖21张票
窗口1:正在卖20张票
窗口1:正在卖19张票
窗口1:正在卖18张票
窗口1:正在卖17张票
窗口1:正在卖16张票
窗口1:正在卖15张票
窗口1:正在卖14张票
窗口1:正在卖13张票
窗口1:正在卖12张票
窗口1:正在卖11张票
窗口1:正在卖10张票
窗口1:正在卖9张票
窗口1:正在卖8张票
窗口1:正在卖7张票
窗口1:正在卖6张票
窗口1:正在卖5张票
窗口1:正在卖4张票
窗口1:正在卖3张票
窗口1:正在卖2张票
窗口1:正在卖1张票
public class MyTest {
public static void main(String[] args) {
CellThread cellThread = new CellThread();
Thread th1 = new Thread(cellThread, "窗口1");
Thread th2 = new Thread(cellThread, "窗口2");
Thread th3 = new Thread(cellThread, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class CellThread extends Thread{
static int ticket=100;
static Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":正在卖"+(ticket--)+"张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
结果是:
窗口1:正在卖100张票
窗口1:正在卖99张票
窗口1:正在卖98张票
窗口1:正在卖97张票
窗口1:正在卖96张票
窗口1:正在卖95张票
窗口1:正在卖94张票
窗口1:正在卖93张票
窗口1:正在卖92张票
窗口1:正在卖91张票
窗口1:正在卖90张票
窗口1:正在卖89张票
窗口1:正在卖88张票
窗口1:正在卖87张票
窗口1:正在卖86张票
窗口1:正在卖85张票
窗口1:正在卖84张票
窗口1:正在卖83张票
窗口1:正在卖82张票
窗口1:正在卖81张票
窗口1:正在卖80张票
窗口1:正在卖79张票
窗口1:正在卖78张票
窗口1:正在卖77张票
窗口1:正在卖76张票
窗口1:正在卖75张票
窗口1:正在卖74张票
窗口1:正在卖73张票
窗口1:正在卖72张票
窗口1:正在卖71张票
窗口1:正在卖70张票
窗口1:正在卖69张票
窗口1:正在卖68张票
窗口1:正在卖67张票
窗口1:正在卖66张票
窗口1:正在卖65张票
窗口1:正在卖64张票
窗口1:正在卖63张票
窗口1:正在卖62张票
窗口1:正在卖61张票
窗口1:正在卖60张票
窗口1:正在卖59张票
窗口1:正在卖58张票
窗口1:正在卖57张票
窗口1:正在卖56张票
窗口1:正在卖55张票
窗口1:正在卖54张票
窗口1:正在卖53张票
窗口1:正在卖52张票
窗口1:正在卖51张票
窗口1:正在卖50张票
窗口1:正在卖49张票
窗口1:正在卖48张票
窗口1:正在卖47张票
窗口1:正在卖46张票
窗口1:正在卖45张票
窗口1:正在卖44张票
窗口1:正在卖43张票
窗口1:正在卖42张票
窗口1:正在卖41张票
窗口1:正在卖40张票
窗口1:正在卖39张票
窗口1:正在卖38张票
窗口1:正在卖37张票
窗口1:正在卖36张票
窗口1:正在卖35张票
窗口1:正在卖34张票
窗口1:正在卖33张票
窗口1:正在卖32张票
窗口1:正在卖31张票
窗口1:正在卖30张票
窗口1:正在卖29张票
窗口1:正在卖28张票
窗口1:正在卖27张票
窗口1:正在卖26张票
窗口1:正在卖25张票
窗口1:正在卖24张票
窗口1:正在卖23张票
窗口1:正在卖22张票
窗口1:正在卖21张票
窗口1:正在卖20张票
窗口1:正在卖19张票
窗口1:正在卖18张票
窗口1:正在卖17张票
窗口1:正在卖16张票
窗口1:正在卖15张票
窗口1:正在卖14张票
窗口1:正在卖13张票
窗口1:正在卖12张票
窗口1:正在卖11张票
窗口1:正在卖10张票
窗口1:正在卖9张票
窗口1:正在卖8张票
窗口1:正在卖7张票
窗口1:正在卖6张票
窗口1:正在卖5张票
窗口1:正在卖4张票
窗口1:正在卖3张票
窗口1:正在卖2张票
窗口1:正在卖1张票