并行: 多个CPU实例或是多台机器同时执行一段处理逻辑,是真正的同时。
并发:一个CPU或一台机器,通过CPU调度算法,让用户看上去同时去执行,实际上从CPU操作层面并不是真正的同时。并发往往需要公共的资源,对公共的资源的处理和线程之间的协调是并发的难点。
(1)进程就是程序
在处理机制中的一次运行
(2)一个进程既包括其所要执行的指令,也包括了执行指令所需的任何系统资源,如CPU,内存空间,I/O端口等,
不同进程所占用的系统资源相对独立。
程序
是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念、而进程则是程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
进程
就是在操作系统中运行的程序,有独立的运行内存空间,比如应用和后台服务,windows是一个支持多进程的操作系统,内存越大能同时运行的程序越多,在java里一个进程指的是一个运行在独立JVM上的程序。
线程:一个程序离运行的多个任务,每个任务就是一个线程,线程是共享内存的。比如在qq中,可以同时接收发送消息,但是有一个内存占用。
java语言把线程或执行环境当作一个封装对象,包含CPU及自己的代码和数据,由虚拟机提供控制。
注意,很多多线程是模拟出来的,真正的多线程指的是由多个CPU,即多核,如服务器。如果是迷你出来的多线程,在一个CPU的情况下,在同一个时间节点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行错觉。
相关核心概念
(1)线程就是独立执行的路径
(2)在程序运行时即是没有创建自己的线程,后台也会有多个线程,如主线程,gc线程
(3)main称之为主线程,为系统的入口,用于执行整个程序
(4)在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
(5)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
(6)线程会带来额外的开销,如CPU调度时间,并发控制开销
(7)每个线程在自己的工作内存在交互,内存控制不当会造成数据的不一致
(8)线程不一定立即执行,CPU安排调度
3.使用多线程的好处
同时运行多个任务,提升CPU使用效率
共享内存,占用资源更少,线程之间可以通信
异步调用,避免阻塞。
从微观上讲,一个时间里只能有一个作业被执行,但要实现多线程,就是要在宏观上使多个作业被同时执行。
在java中线程可以被认为是由三部分组成的:
1.虚拟CPU,封装在java.lang.Thread类中,它控制着整个线程的运行
2.执行的代码,传递给Thread类,由Thread类控制顺序执行
3.处理的数据,传递给Thread类,是在代码执行过程中所要处理的数据。
注意:在java中,虚拟CPU体现于Thread类中。当一个线程被构造时,它由构造方法参数,
执行代码,操作数据来初始化。
多线程的优势
(1)多线程编程简单,效率高。使用多线程可以在线程之间共享数据和资源。
(2)适合于开发服务程序,如Web服务
(3)适合与开发由多种交互接口的程序
(4)适合于有人机交互又有计算量的程序。
使用多线程的好处:
1.同时执行多个任务,提升CPU的使用率
2共享内存,占用资源更少,线程间可以通信
3.异步调用,避免阻塞
(1)当生成一个Thread对象之后,就产生了一个线程。通过该对象实例,可以启动线程,终止线程,或者暂时挂起线程。
(2)Thread类本身只是线程的虚拟CPU,线程所执行的代码是通过方法中的run方法(包含在一个特定的对象中)完成的,方法run()称为线程体。实现线程体的对象是在初始化线程时传递给线程的。
(3)在一个线程被建立并初始化之后,java的运行时系统自动调用run()方法。
1.新建 New 线程对象被创建后,它只会短暂的处于这种状态。此时他已经分配了必须的系统资源,并执行了初始化。
Thread thread = new Thread("test");
2.就绪 Runnable 称为可运行状态
线程对象被创建之后,其他线程调用了该对象的start()方法,从而来启动该线程。例如
thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3.运行 Running:线程获取CPU权限进行执行。注意:线程只能从就绪状态进入运行状态。
4.死亡(dead)
(1)run()方法中最后一个语句执行完毕
(2)当线程遇到异常退出时
5.阻塞(blocked)
一个正在执行的线程因特殊的原因被暂停执行,就进入阻塞状态。阻塞时线程不进入就绪队列排队。必须等到阻塞的原因消除才能进入就绪队列。
阻塞情况分三种:
(1)等待阻塞:通过调用线程的wait()方法,让线程等待某项工作的完成
(2)同步阻塞:线程在获取synchronised同步锁失败(因为锁被其他线程嗲用),它就会进入同步阻塞状态。
(3)其他阻塞:通过调用线程的sleep()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时,join()等待线程终止或是超时。或是I/O处理完毕之后,线程就进入就绪状态。
6.中断线程
当run()方法执行结束返回时,线程自动结束。
(1)在程序中常常调用interrupt()方法来终止线程。该方法不仅可中断正在运行的线程,而且也能终端处于blocked
状态的线程,此时会抛出InterruptedException异常
.
java中测试线程是否被中断的方法
(1)void Interrupt() 像一个线程发送中断请求,同时把这个线程的“Interrupted”设置为true
(2)static boolean interrupted()检测当前线程是否已经被中断,并重置状态“interrupted”值,及如果连续两次调用
该方法,则第二次调用将返回false
boolean isInterrupted()检测当前线程是否已被中断,不改变“interrupted”的值
类Thread的一个构造方法如下
public Thread(ThreadGroup group,Runnable target,Strig name) //group指明了线程所属的线程组;target是线程体run()方法所在的对象 (target必须实现了Runnable接口);name是线程的名称. //在接口Runnable中只有一个run()方法作为线程体。 任何实现Runnable接口的对象都可以作为一个线程的目标对象
一,创建现线程的方法-继承Thread类
public class ThreadTest{ /** * 创建线程的方法一,继承Thread类*/ static Lefthand left; static Righthand right; public static void main(String[] args) { left = new Lefthand(); right = new Righthand(); left.start(); right.start(); } } class Lefthand extends Thread{ /** * 方法run()称为线程体,它是整个线程的核心,线程所要完成的任务的代码都定义在线程体中 * 实际上不同功能之间的区别就在于它们的线程体不同。*/ public void run(){ for (int i = 0; i < 6; i++) { System.out.println("You are Students!"); try { //多线程 要休息 sleep(500);//性能改进 System.out.println(getState()); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Righthand extends Thread{ public void run() { for (int i = 0; i < 6; i++) { System.out.println("I am a Teacher!"); try { sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } } } }
二,实现Runnable()接口
总之线程由Thread对象的实例来引用。线程执行的代码来源于传递给Thread构造方法的参数引用的类,这个类
必须实现了接口Runnable(),线程操作的数据来源于触底给Thread构造方法的Runnable实例。。。。。。。
import javax.swing.plaf.TableHeaderUI; public class xyz implements Runnable{ int i=0; @Override public void run() { while (i < 100){ System.out.println("Hello" + i++); try { Thread.sleep(500); //sleep()是Thread类中的静态方法,所以可以直接调用 //参数指定了线程再次启动前必须休眠的最短时间,保证一段时间后该线程回归到就绪队列。 } catch (InterruptedException e) { e.printStackTrace(); } } } } /** *public Thread(ThreadGroup group,Runnable target,String name * group指明了线程所属的线程组;target是线程体run()方法所在的对象,name是线程的名称*/ /** * Thread的构造方法中包含有一个Runnable实例的参数,这就是说,必须定义一个实现Runnable接口的类并产生一个该类的实例, * 对该实例的引用就是适合于这个构造方法的参数*/ class test{ public static void main(String[] args) { Runnable r = new xyz(); Thread t = new Thread(r); t.start(); } }
总结:线程由Thread对象的实例来引用。线程执行的代码来源于传递给Thread构造方法的参数引用的类,这个类必须实现了Runnable()接口,线程操做的数据来源于传递给Thread构造方法的Runnable实例。
1.适用于采用实现Runnable接口方法的情况
因为Java只允许单继承,如果一个类已经继承了Thread,就不再继承其他类,在
一些情况下,就被迫采用实现Runnable接口的方法。
2.适用于采用继承Thread方法的情况
当一个run()方法置于Thread类中时,this实际上引用的是控制当前运行系统的Thread实例
一个线程被创建之后,但并没有立即运行。要使线程真正在java环境中运行,必须通过start()方法来启动。
启动之后,线程的虚拟CPU已经就绪。
1.start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法。此时不需要等待run()方法执行完也可以继续执行下面的代码,所以也由此看出run()方法并没有实现多线程。
2.run()用来定义线程对象被调度之后锁执行的操作,用户必须重写run()方法
3.yield()强制终止当前线程的运行
4.isAlive()测试当前线程是否在活动
5.sleep(int millsecond)使线程休息一段时间,时间长短由参数决定
6.void wait()使线程处于等待状态
虽然就绪线程已经可以运行,但这不意味着这个线程一定能够立刻运行。
在Java中时间调度通常是抢占式,而不是时间片式。
抢占式调度是指可能有多个线程准备运行,但只有一个在真正运行。一个线程获得执行权,这个线程将持续运行下去,
直到它运行结束或因为某种原因阻塞,再或者有另一个高优先级线程就绪。
java的线程调度采用如下优先级策略
1.优先级高的先执行,优先级低的后执行
2.多线程系统会自动为每一个线程分配一个优先级,默认时,继承其父类的优先级
3.任务救急的线程,其优先级较高
4.同优先级的线程按“先进先出”的原则
java中Thread类几个与线程优先级有关的静态变量
1.MAX_PRIORITY 值为10
2.MIN_PRIORITY
3.NORM_PRIORITY 值为5
java类中几个常用的与优先级有关的方法有
1.void setPrioriy(int newPriority)
2.int getPriority()获得当前线程的优先级
3.static void yield()使当前线程放弃执行权
就绪队列与阻塞队列
1.所有就绪但没有运行的线程则根据其优先级排入一个就绪队列,当CPU空闲时,如果就绪队列不为空,队列中第一个具有最高优先级的线程将运行。
2.当一个线程被抢占而停止运行时,它的运行状态被改变并放到就绪队列的队尾,同样一个因为某种原因被阻塞的线程就绪后
通常也放到就绪队列的队尾。
1.结束线程
在程序代码中,可以利用Thread类中的静态方法currentThread()来引用正在运行的线程。
2.检查线程
isAlive 获取一个线程是否还在活动状态的信息。活动状态不意味着这个线程正在执行,而只说明这个线程已经被启动,
并且既没有运行方法stop()和方法run()。
3.挂起线程
暂停一个线程也称为挂起。在挂起之后,必须重新唤醒线程进入运行。
sleep()
线程不是休眠期满后就立刻被唤醒,因为此时其他线程可能正在执行,重新调度只在以下几种情况下才会发生:
。。。被唤醒的线程具有更高的优先级
。。。正在执行的线程因为其他原因被阻塞
。。。程序处于支持时间片的系统之中
wait()和notify()/notifyAll()
wait()方法导致当前线程等待,直到其他线程调用此对象的notify()发给发或nitifyAll()发给发,才能唤醒线程
join()
方法join()将引起现行线程等待,直至方法join()方法所调用的线程结束。例如已经生成并运行了一个线程tt,
而在另一个线程中执行方法timeout(),
public void timeout(){ //暂停该线程,等候其他线程结束 tt.join; //其他线程结束后,继续执行该线程 }
由于线程之间可以共享内存,则某个对象(变量)使可以被多个线程共享的,是可以被多个线程同时访问的。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步协同,这个类都能表现出正确行为,那么就称这个线程是同步的。
1.线程间通信,写线程往管道流中输入信息,读线程往管道流中读入信息。
2.线程间的资源互斥共享
通常一些同时运行的线程需要共享数据。在这种时候,每个线程就需要考虑与它一起
共享数据的其他线程的状态与行为,否则就不能保证共享数据的一致性。
import javax.swing.plaf.TableHeaderUI; import java.io.*; import java.util.Scanner; import java.util.Stack; class myWriter extends Thread{ private PipedOutputStream outStream; private String messages [] = {"Monday","Tuesday","Wednsday", "Thursday","Friday","Saturday","Sunday"}; public myWriter(PipedOutputStream o){ outStream = o; } public void run(){ /** * Creates a new print stream, without automatic line flushing, with the * specified OutputStream. Characters written to the stream are converted * to bytes using the platform's default character encoding. * * @param out The output stream to which values and objects will be * printed * * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream) * * public PrintStream(OutputStream out) { * this(out, false); * } */ PrintStream p = new PrintStream(outStream); for (int i = 0; i < messages.length; i++) { p.println(messages[i]);//将数据写入到管道流中, p.flush(); System.out.println("Write:" + messages[i]); } p.close(); p = null; } } class myReader extends Thread{ private PipedInputStream inputStream; public myReader(PipedInputStream i){ inputStream = i; } public void run(){ String line; boolean reading = true; Scanner scan = new Scanner(new InputStreamReader(inputStream)); while (scan.hasNextLine()){ System.out.println("Read:" + scan.nextLine()); try { Thread.currentThread().sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Pipethread{ public static void main(String[] args) { Pipethread thisPipe = new Pipethread(); thisPipe.process(); } private void process() { PipedInputStream inputStream; PipedOutputStream outputStream; try { /** * Creates a piped output stream that is not yet connected to a * piped input stream. It must be connected to a piped input stream, * either by the receiver or the sender, before being used. * * @see java.io.PipedInputStream#connect(java.io.PipedOutputStream) * @see java.io.PipedOutputStream#connect(java.io.PipedInputStream) * * public PipedOutputStream() { * } */ outputStream = new PipedOutputStream(); /** * Creates a {@code PipedInputStream} so * that it is connected to the piped output * stream {@code src}. Data bytes written * to {@code src} will then be available * as input from this stream. * * @param src the stream to connect to. * @throws IOException if an I/O error occurs. *在这里插入代码片 * public PipedInputStream(PipedOutputStream src) throws IOException { * this(src, DEFAULT_PIPE_SIZE); * } */ inputStream = new PipedInputStream(outputStream); new myWriter(outputStream).start(); new myReader(inputStream).start(); } catch (IOException e) { e.printStackTrace(); } } }
对象的锁定标志
在java语言中,引入了“对象互斥锁的概念”来实现不同线程对共享数据操作的同步,“对象互斥锁的概念”阻止多个线程同时访问同一个条件变量。java可以为每一个对象的实例配有一个“对象互斥锁”。
java内存模型中的可见性,原子性和有序性。
可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1,线程2…线程n能够立即读取到线程1修改后的值。
有序性:即程序执行时按照代码书写的先后顺序执行。在java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单位线程程序的执行,却会影响到多线程并发执行的正确性。
原子性:原子性通常指多个操作不存在只执行一部分的情况,要么全部执行,要么全部不执行。
线程池的概念
线程的创建时比较消耗内存的,所以我们要事先创建若干个可执行的线程放进一个“池”里面,需要的时候就直接从池里面取出来不需要自己创建,使用完毕也不需要销毁而是放进“池”中,从而减少了创建和销毁对象所产生的开销。
ExecutorService:线程池接口
ExecutorService poo(池名称) = Executors.常用线程池名;
常用线程池:
newsingleThreadExecutor :单个线程的线程池,即线程池中每次只有一个线程在工作,单线程串行执行任务
newfixedThreadExecutor(n):固定数量的线程池,每提交个任务就是一个线程, 直到达到线程池的最大数量,然后在后面等待队列前面的线程执行或者销毁
newCacheThreadExecutor:一个可缓存的线程池。当线程池超过了处理任务所需要的线程数,那么就会回收部分闲置线程(一般是闲置60s)。当有任务来时而线程不够时,线程池又会创建新的线程,当线程够时就调用池中线程。适用于大量的耗时较少的线程任务。
newScheduleThreadExecutor:一个大小无限的线程池,该线程池多用于执行延迟任务或者固定周
期的任务。
package ThreadPool; public class ticket implements Runnable{ private int ticket = 10; @Override public void run() { for (int i = 1; i <= 15; i++) { if (ticket > 0) System.out.println(Thread.currentThread().getName()+"买票,剩余"+ticket--); else { System.out.println(Thread.currentThread().getName()+"票卖完了"); break; } } } } package ThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TicketPool { public static void main(String[] args) { /** * 利用线程池创建线程 * */ //创建放5个线程的线程池 Executors是一个类 ExecutorService service = Executors.newFixedThreadPool(5); for (int i = 1; i < 5; i++) { //执行线程 service.execute(new ticket()); } service.shutdown();//关闭线程池 } }
(1)用关键字volatile声明一个共享数据(变量)
volatile具有可见性,有序性,不具备原子性。
可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1,线程2,…线程n能够立即读取到线程1修改的值。
有序性:即程序执行时按照代码书写的先后顺序执行。在java内存模型中,允许编译器和处理器对指令进行重排序,但时重排序过程不会影响到单线程序的执行,却会影响到多线程并发执行的正确性。
原子性:原子性通常指多个操作不存在只执行一部分的情况,要么全部执行,要么全部不执行。
synchronised时阻塞式同步,称为重量级锁。而volatile时非阻塞式同步,称为轻量级锁。被volatile修饰的变量能够保证每个线程能偶获取该变量的最新值。
(2)用关键字synchronized声明操作共享数据的一个方法或一段代码。synchronised是java的内置锁机制,是在JVM上的。
(3)ReentrantLock可重入锁,在javaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入,互斥,是吸纳了Lock接口的锁,他与使用synchronised方法和块 具有相同的基本行为和语义,并且扩展了其能力。
ReentrantLock():创建一个ReentrantLock实例
lock():获得锁
unlock():释放锁
可重入:甲获得锁后释放锁或锁失效,乙可继续获得这个锁。
为了保证数据操作的完整性
pan.baidu.com/s/1LmovqR4vuN8pxoRsnzCN6A
当线程执行到被同步的语句的时,它将传递的对象参数设置为锁定标志,禁止其他线程对该对象的访问。
在第一个线程拥有锁定标记时,如果另一个线程企图执行synchronised(this)中的语句,它将从对象this中获取锁定标记。
因为这个标记不可得,故该线程不能继续执行。实际上该线程将加入一个等待序列,这个等待序列与对象锁定标志相连,当标被返回该对象时,第一个等待它的线程将得到他并继续执行。当持有锁定标志的线程运行完synchronized()调用包含的程序块之后这个标志会自动返还。
同步方法
class myStack1{ int idx = 0; char data [] = new char[6]; public void push(char c){ synchronized (this){ data[idx] = c; idx++; } } public synchronized char pop(){ synchronized (this){ idx--; return data[idx]; } } }
果一个线程持有一个锁并试图获取另一个锁时,就有死锁的危险。
死锁的情况发生在 第一个线程等待第二个线程所持有的所,而第二个线程又在等待第一个线程持有的锁的时候,
每个线程都不能继续执行
1.为什么线程之间需要交互?
2.为了解决线程运行速度的问题,java提供了一种建立在对象实例之上的交互方法。java中的每个对象实例都有两个线程队列和它相连。第一个用雷排列等待锁定标志的线程,第二个则用来实现wait()和notify()的交互机制。
(1)wait()方法的作用是让当前线程释放其所持有的“对象互斥锁”,进入wait队列(等待队列)
(2)notify()和notifyAll()方法的作用是唤醒一个或所有正在等待队列中等待的线程,并将它们移入等待同一个对象互斥锁的队列
注意:这三个方法都只能在被声明为synchronised的方法或代码段中调用。