课程内容
1.线程基础知识:线程创建。线程状态.线程转换关系,方法
2.并发与并行,并发的特征
3.Synchronized
4.volatile关键字 5。线程间通信 notify,notifyAll,wait
6.生产者,消费者类型
线程基础知识介绍
程序,进程和线程
程序
(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
进程
:是计算机运行
的一个独立的应用程序,进程是一个动态的概念 必须是运行状态,如果一个应用成组没有启动,那就不是一个进程
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位
,系统在运行时会为每个进程分配不同的内存区域
线程
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开
销小
一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,
可以
访问相同的变量和对象。这就使得线程间通信更简便、高效。
但多个线程操作共享的系统资源
可能就会带来安全的隐患
进程和线程区别
1.内存空间的区别
进程是有独立空间的,在线程创建时就会分配空间。每个进程的空间是相互独立的,互不影响
线程有共享的空间,也有独立的空间
收到
堆空间:线程共有 虚拟机栈:线程私有
2.线程安全性
进程之间是相互独立的,一个线程的崩溃不会影响到其他线程的执行,进程之间是安全的
线程之间存在内存共享,一个线程的崩溃可能会影响到其他线程的执行,线程的安全性不如进程
java中很少使用到进程的概念,但也可以使用
//启动一个进程
Rutime runtime = Runtime.getRuntime();
线程的创建和启动
注意点:
- 如果自己
手动
调用run()
方法,那么就只是普通方法,没有
启动多线程模式。- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
3.想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常
“IllegalThreadStateException”。
构造函数
//创建新的Thread对象
public Thread() {}
//常见线程并制定线程实例名
public Thread(String name) {}
//指定创建线程的目标对象,它实现了Runnable接
//口中的run方法
public Thread(Runnable target) {
}
//创建新的Thread对象
public Thread(Runnable target, String name)
线程创建的三种方式
1. 继承Thread类
public class Thread212 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 子线程正在执行");
}
}
class Testdemo{
public static void main(String[] args) {
Thread212 thread212 = new Thread212();
// thread212.setName("");
thread212.start();
//如何实现多线程
new Thread212().start();
new Thread212().start();
System.out.println(Thread.currentThread().getName() + " 主线程正在执行");
}
}
代码运行结果
小练习:用继承的方式创建多线程,实现多个窗口买票
2. 实现Runable接口
public class Runable212 implements Runnable {
@Override
public void run() {
int i = 0;
while (i++ < 3) {
System.out.println(Thread.currentThread().getName() + " 子线程正在执行");
}
}
}
class RunableTest{
public static void main(String[] args) {
Runable212 runable212 = new Runable212();
Thread thread = new Thread(runable212);
thread.start();
int i=0;
while(i++ <3){
System.out.println(Thread.currentThread().getName()+"主线程正在执行");
}
}
}
代码运行结果:
需要注意的是根据CPU的不同,程序每次输出的顺序不一样。这是为什么呢?
当当当:当然因为我们是多线程呀!,main 函数和子线程各占一个CPU,但只有一个print具体的顺序取决于时间啦!
如果是我们之前的继承方式:就不是多线程了,每次输出的顺序也是固定的
比较创建线程的两种方式。
开发中:优先选择;实现Runnable接口的方式
原因:
1.实现的方式没有类的单继承性
的局限性
2.实现的方式更适合来处理多个线程有共享数据
的情况。
联系:
public class Thread impl ements Runnable
相同点:
两种方式都需要重写run(),将线程要执行的逻辑声明在run()中.
!
1.例子:创建三个窗口卖票,总票数为100张,用继承Thread的方法
存在线程安全问题
class Window extends Thread{
private int ticket =1;
@Override
public void run() {
while(true){
if(ticket>0){
//System.out.println(Thread.currentThread().getName()+"");
System.out.println(getName()+": 买票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public class TicketDEMO {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 解决方案,换成staic 变量,共享对象
2.例子:创建三个窗口卖票,总票数为100张,使用Runnable接口的方式
class MyYThread implements Runnable{
private int ticket =3;
@Override
public void run() {
while(true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+": 买票,票号为:"+ticket);
ticket--;
}else{
break;
}
}
}
}
public class TestDemo1 {
public static void main(String[] args) {
//创建实现类的对象
Thread t1 = new Thread(new MyYThread());
Thread t2 = new Thread(new MyYThread());
Thread t3 = new Thread(new MyYThread());
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
对比上面讲的继承买票的形式
3.实现Callable接口
Callable声明形式
public interface Callable<V> {
V call() throws Exception;
}
Callable接口提供了call方法,方法具有返回值,通过泛型类定义的
,且可以以抛出异常
该接口的实现类无法直接使用,需要借助FutureTask类
FutureTask类声明如下:
public class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
FutureTask的构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
FutureTask类是实现自RunnableFuture接口,FutureTask是可以接口Callable的接口
而RunnableFuture接口是继承自Runable接口,意味着该FutureTask类的实例可以作为参数传递给Thread
FuturetTask构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
代码实现如下
public class Callable212 implements Callable<String>{
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
int i=0;
while (i++<3){
System.out.println(name+"子线程正在执行");
}
return name;
}
}
class Test1{
public static void main(String[] args) {
Callable212 callable212 = new Callable212();
FutureTask<String> stringFutureTask = new FutureTask<>(callable212);
Thread thread = new Thread(stringFutureTask);
thread.start();
System.out.println(Thread.currentThread().getName()+"主线程正在执行");
}
}
ExecutorService 和Executors
public class ThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 =(ThreadPoolExecutor) service;
//System.out.println(service.getClass());//得到接口地实现类
service1.execute(new Runnable2());
}
}
class Runnable2 implements Runnable{
@Override
public void run() {
for(int i=0;i<3;i++){
System.out.println(i);
}
}
}
线程的状态及状态转换
线程状态
jdk中提供了线程的状态,在Thread类中提供了一个内部的枚举类:State
public enum State {
NEW,
RUNNABLE,//runnable
BLOCKED,//blovked
WAITING,//waiting
TIMED_WAITING, //timed waiting
TERMINATED; //terminated
}
新建状态(NEW)
用new语句创建的线程处于新建状态,此时和其他对象一样,在堆中仅仅给他分配了内存
就绪状态(RUNNABLE)
当一个线程创建后,调用了start方法,线程状态就进入到就绪状态,处于就绪状态的线程意味着所有需要的资源已经装备就绪,等待CPU的调用,CPU的调度是由操作系统控制的,用户无法操控
运行状态(RUNNING)
处于运行状态的线程占用CPU资源,执行程序代码,只有处于就绪状态的线程才能有机会进入到运行状态
1、如果一个时间片用完或者调用yield方法,线程会从运行状态进入到就绪状态
2、如果线程已经执行结果了,线程会从运行状态进入终止状态
3、如果线程执行过程中因为等待一些资源而进入阻塞状态(WAITING、TIME_WAITTING、BLOCKED)
阻塞状态(BLOCKED)
进入这种阻塞状态的原因可以是:线程期望进入同步方法或者同步代码块(Synchronized),尚未获取到锁资源的情况下,可以从运行状态进入到blocked状态
等待状态(WAITING)
如果调用wait()方法,就进入到waiting状态,即调用wait()会触发线程从运行状态进入到阻塞状态而无法执行,直到其他线程发出notify或notifyAll这个方法,此时线程才会从waiting状态进入到blocked状态,进而进入就绪状态
睡眠等待(TIMED_WAITING)
如果线程调用sleep(long)、join(long)、wait(long)等方法是,会触发线程进入到TIMED_WAITING状态,即指定了阻塞的时间,到达了给定的时间后就可以继续进入就绪状态等待CPU的调度
终止状态(TERMINATED)
当线程退出run()方法时,线程就进入了终止状态,该状态是结束线程的声明周期
线程状态转换(重要)
一个线程的生命周期中需要的状态:NEW、RUNNABLE、RUNNING、TERMINATED四个状态,当线程需要响应特定资源时,进入到阻塞状态:BLOCKED、WATING、TIMED_WAITING状态
线程方法
currentThread():静态方法
返回执行当前代码的线程
start():启动线程
start方法作用是用来启动一个新线程,start方法需要首先调用,start方法是不能被重复调用的
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
start方法的实现是调用了一个native的方法,本质上java创建的多线程其实是有操作系统所提供的多线程方式来创建的
start方法启动子线程是通过调用系统提供的方式来启动线程
run():子线程执行体
run方法中是子线程的执行体
run方法和普通的成员方法一样,可以被重复调用,如果在当前线程调用执行run(),是不会启动新线程
yield():线程让步
是暂停当前线程的执行,并让步于其他同优先级或更高优先级的线程先执行
public static native void yield();
yield方法是定义在Thread类下的静态方法
当线程中调用yield方法,会让当前正在执行的线程由”RUNNING“状态进入到”RUNABLE状态,线程锁占用的锁是不会释放的
yield让出CPU的执行权,当让给谁,是由系统决定的,系统会让具有相同优先级的或更高优先级的线程获取CPU执行权,如果没有相应优先级的线程,当前线程就会立即执行
sleep():线程睡眠
是让线程休眠,而且是哪个线程调用,就是那个线程休眠
sleep方法是Thread类下的静态方法,该方法执行过程中会抛出InterruptedException异常,即可以终止异常
sleep所在的线程休眠期间,线程会释放掉CPU资源给其他线程,但如果当前线程本身持有锁,锁是不会释放的,
线程会由”RUNNING“状态进入到TIMED_WAITING状态,当到达休眠时间或者是被中断掉休眠,就会从睡眠状态进入到就绪状态,从而等待CPU的调用
方法:
sleep(long millis)
sleep(long millis, int nanos)
这两个方法都是Thread类的静态方法,不属于某个对象
//设置休眠的时间,单位是毫秒
public static native void sleep(long millis) throws InterruptedException;
//设置休眠时间 毫秒和纳秒
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
join():线程同步
参考文献https://www.cnblogs.com/lcplcpjava/p/6896904.html
上面程序结果是先打印完小明线程,在打印小东线程;
上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。注意,这里调用的join方法是没有传参的,join方法其实也可以传递一个参数给它的,具体看下面的简单例子:
join方法特点:
1.普通方法,可以抛出Interrupted异常
2.t.join()是表示当前线程让t插队,等到t执行完,当前线程继续执行,
3.join方法调用会让当前线程进入阻塞的状态或者time_waiting状态
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
//Object类下的native方法,是一个线程间通信的方法,notify或者notifyAll方法,
public final native void wait(long timeout) throws InterruptedException;
join方法执行的本质是使用线程间通信机制来完成线程同步功能
线程的顺序打印:给定4个线程,线程名分别为A,B,C,D,让每个线程打印各自名称,按照DCBA来打印
c 调 d.join
b 调 c.join
a b
public class JoinDemo extends Thread {
private Thread thread;
private String name;
public JoinDemo(Thread thread, String name) {
this.thread = thread;
this.name = name;
}
@Override
public void run() {
if (thread != null) {
try {
//让子线程先执行
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//打印当前线程信息
System.out.println(name);
}
JoinDemo threadd = new JoinDemo(null, "D");
JoinDemo threadc = new JoinDemo(threadd, "C");
JoinDemo threadb = new JoinDemo(threadc, "B");
JoinDemo threada = new JoinDemo(threadb,"A");
threadd.start();
threadc.start();
threadb.start();
threada.start();
2.interrupt()中断线程
用来中断当前的线程,终止处于“阻塞状态”的线程
interrupt方法是在Thread类中的一个普通方法,由对象调用该方法
方法:
boolean isInterrupted():判断线程是否发生中断,true:表示中断 false:非中断
interrupt():中断方法
方法特点: interrupt方法执行是对中断标志位做了修改
1、如果线程当前是可中断的阻塞状态(调用sleep、join、wait等方法会导致线程进入到阻塞状态WAITING/TIMED_WAITING/BLOCKED),
在任意的其他线程调用t.interrupt方法,那么线程会立即抛出一个InterruptedException异常,退出阻塞状态
2、如果线程t当前存储于运行状态,则调用t.interrupt()方法,线程会继续执行,直到发生了阻塞(调用sleep、join、wait)后立即抛出异常,跳出阻塞状态,Interrupt并不会终止处于“运行状态”的线程,其仅仅是对标志位做了修改
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
private native void interrupt0();
demon守护线程
java中有两种线程,用户线程和守护线程,
可以通过isDaemon()来区分线程是否是守护线程 守护线程也称为”后台线程“
用户线程一般是用来执行用户级任务
守护线程也称之为“后台线程”,服务于用户线程,一般用来执行后台任务 例如“:垃圾回收线程是单独用来处理无用对象的回收的, 负责垃圾回收的线程就是守护线程
守护线程生命周期:
完全依赖与用户线程,用户线程存在即存在,消亡即消亡
void setDaemon(boolean on);//true守护线程, false 非守护线程,默认false
boolean isDaemon() //判断当前线程是否是守护线程
Priority 线程优先级
获取线程优先级
public final int getPriority() {
return priority;
}
设置线程优先级 newPriority 必须在1~10 ,默认是5
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority)
高优先级的线程要抢占优先级线程cpu的执行权。但是只是从檄率上讲。高优先级的线程高概率的情况下被执行。并不童味着只有当高优先级的线程执行完以后,低优先级的线程才执行.
public class demo {
public static void main(String[] args) {
MyThread h1 = new MyThread();
Thread thread = new Thread(new thr());
//设置分线程的优先级
thread.setPriority(Thread.MAX_PRIORITY);
h1.start();
//给子线程命名
h1.setName("子线程");
//给主线程命名
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for(int i=0;i<100;i++){
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+" "+i);
}
}
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+" "+i);
}
}
}
}
class thr implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2 !=0) {
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+" "+i);
}
}
}
}
优先级特点
线程优先级:指导线程的执行的先后顺序的
方法:
//int getPriority() 获取线程的优先级
//setPriority(int newPriority) 设置线程优先级 newPriority必须在1~10之间的整的,默认的是5
优先级特点:
1、java线程的优先级并不绝对,他所控制的是执行的优先机会,优先级的决定权在操作系统
,java设置的优先级只能是被优先执行的概率会高一些,并不绝对
2、java中优先级共有10级,分为1-10,
数值越大,表明优先级越高,一般普通的线程优先级是5
3、优先级具有继承性,
如果一个线程B在另一个线程A中创建出来,那么线程B的优先级和线程A保持一致