程序:在计算机上安装的程序代码(静态的)。
进程:运行中的程序,从硬盘上被加载到内存中,分配空间,是操作系统分配空间的单位。
线程:线程是进程内部的最小执行单元(单位),是操作系统调度单位。
举例:QQ
安装到电脑上(静态代码) 双击运行:加载到内存中
线程、进程的关系CPU执行以线程为单位。
线程隶属于进程。
一个进程中可以包含多个线程,一个进程中至少包含一个线程(即主线程)。
可以在主线程中创建其他线程。
main方法用来启动java主线程的。
线程线程是操作系统调度执行的单位,线程是具备可以独立执行的。如果是多线程,那么两个线程可以是同时执行的。
线程是一个可以独立被操作系统执行调度的,多核CPU下,同时可以执行多个线程。
创建线程//此程序非多线程! //这种现象它只是方法调用,程序会按照调用的先后顺序执行。public class Sample { public void method1(String str){ System.out.println(str); } public void method2(String str){ method1(str); } public static void main(String[] args) { Sample s = new Sample(); s.method2("hello!"); } }
继承线程的两种方式 继承Thread类的方式不可以再继承其他类。
extends Thread:
MyThread myThread = new MyThread();
myThread.start();
public class MyThread extends Thread{ //线程中执行的任务代码必须写在run()中 @Override public void run(){ for (int i = 0; i < 1000; i++) { System.out.println("MyThread:"+i); } }}public class Test { //启动java主线程 public static void main(String[] args) { //创建自己实现的线程对象 MyThread myThread = new MyThread();// myThread.run(); 调用run()方法,并非启动线程,就是普通的方法调用。 //启动线程,并不是启动后就会立即执行。在操作系统处排上队了,什么时候执行由操作系统调度算法决定。 myThread.start(); System.out.println("main结束运行"); //此时已经是多线程了 for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}
实现Runnable接口的方式还可以继承其他类。
implements Runnable:
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();
public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 1000; i++) { System.out.println("ThreadDemo:"+i); } }}public class Test { public static void main(String[] args) { //方式2 //创建线程的执行任务,并不是线程。 ThreadDemo threadDemo = new ThreadDemo(); //Thread是线程的管理类,创建线程,并为其分配任务。 Thread thread = new Thread(threadDemo); //启动线程 thread.start(); for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}
Thread类Thread类是用来管理线程的。
run()方法:实现线程任务
start()方法:启动线程
currentThread():获得当前执行的线程
线程是有优先级的,优先级范围是1-10,默认是5。
SetPriority(int newPriority):设置线程的优先级
getPriority():返回线程的优先级
以下三个方法会影响线程的状态。
join():等待当前线程结束。
sleep():线程休眠指定的时间。
yield():线程主动让出。
线程优先级优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级。
较高优先级的线程有更多的机会获得CPU的执行权(不是绝对的)。
public class ThreadDemo implements Runnable{ @Override public void run(){ System.out.println("thread:"+Thread.currentThread().getPriority()); //优先级 }}public class Test { public static void main(String[] args) { //方式2 //创建线程的执行任务,并不是线程。 ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.setName("mythread111"); //为线程设置名称 thread.start(); thread.setPriority(3); Thread.currentThread().setPriority(10); //main优先级设置为10// System.out.println("thread:"+thread.getPriority()); //优先级 System.out.println("main:"+Thread.currentThread().getPriority()); //优先级 }}
调度策略
时间片
抢占式:高优先级的线程抢占CPU。
Java的调度方法
同优先级线程组成先进先出队列,使用时间片策略。
对高优先级,使用优先调度的抢占式策略。
线程生命周期生命周期:就是什么时候创建,又什么时候销毁(死亡)。
五种状态新建
MyThread t = new MyThread( );
就绪
运行
阻塞
死亡
sleep():线程休眠指定的时间。
public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 100; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); } }}public class Test { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); }}
yield():可以直接从运行状态回到就绪队列中
public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 100; i++) { if(i%10==0){ //20%10 Thread.yield(); //主动让线程让步,让步后回到就绪队列,有可能又先于main获得执行权 } System.out.println(Thread.currentThread().getName()+":"+i); } }}public class Test { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}
join():等待当前线程结束。
public class Test { public static void main(String[] args) throws InterruptedException { ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); thread.join(); //等待当前线程结束,阻塞main线程 for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 100; i++) { if(i%10==0){ //20%10 Thread.yield(); //主动让线程让步,让步后回到就绪队列,有可能又先于main获得执行权 } System.out.println(Thread.currentThread().getName()+":"+i); } }}
线程分类Java中的线程分为两类:用户线程和守护线程。
正常创建的线程是用户线程,守护线程与用户线程功能是一样的,去完成某件事情。
用户线程的工作完成后就结束了,守护线程是等待所有的用户线程结束后,守护线程才会自动退出。
例如垃圾回收任务,就是在一个守护线程中进行。
守护线程的作用是为其他线程的运行提供便利服务。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。
//用户线程public class ThreadDemo1 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+i); } }}//守护线程public class ThreadDemo2 extends Thread{ @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()); } }}public class Test { public static void main(String[] args) { ThreadDemo1 t1 = new ThreadDemo1(); t1.setName("用户线程"); t1.start(); ThreadDemo2 t2 = new ThreadDemo2(); t2.setName("守护线程"); //setDaemon()将线程设置为守护线程,必须在启动线程前设置,否则会报错 t2.setDaemon(true); t2.start(); }}
多线程在一个应用程序中可以有多个线程任务执行。
程序中同时需要执行多个任务时,就需要多个线程。
多线程的优缺点 优点例如,360安全卫士
提高程序的响应(速度)。
提高CPU的利用率。
改善程序结构,将复杂任务分为对个线程,独立运行。
缺点线程也是程序,所以线程需要占用内存,线程越多占用内存也越多,多线程对内存消耗增加。
可以通过升级硬件设备来解决
多线程需要协调和管理,所以需要CPU时间跟踪线程,多线程对CPU要求提高。
可以通过升级硬件设备来解决
多个线程访问同一个共享资源,会出现线程安全问题(多线程&&访问同一个共享问题)。
现在都是多核CPU,那么就可以在同一时间点上同时处理多个线程。
单一的多线程不会出现线程安全问题,每一个线程都在做自己的事情,没有交集。
线程安全问题由并发引起。
解决线程安全问题:加锁+排队
线程同步 多线程同步为出票方法加锁,一次只能有一个线程进入到出票方法中。
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到。
同步就是排队+锁:
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。
一个线程持有锁会导致其他所有需要此锁的线程挂起;在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
并发与并行 并行多个CPU同时执行多个任务。
线程并行执行:多个人同时做不同的事情,互不干扰。
并发在一个时间段内依次执行操作。
线程并发执行:在一个时间段内,依次执行某件事情,一个一个来做,交替做。
同步锁例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行。
必须程序控制线程并发执行 高并发 双十一 秒杀 抢购
同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用来充当锁标记)。
同步执行过程1.第一个线程访问,锁定同步对象,执行其中代码。
2.第二个线程访问,发现同步对象被锁定,无法访问。
3.第一个线程访问完毕,解锁同步对象。
4.第二个线程访问,发现同步对象没有锁,然后锁定并访问。
模拟卖票线程两个线程同时访问一个共享数据,现在没有任何控制,两个线程同时可以进入方法中执行
public class TickThread extends Thread{ //假设有10张票 static变量只有一份,多个对象共享 static int num = 10; //模拟出票 @Override public void run() { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); num--; }else { System.out.println("票卖完了"); break; } } }}
窗口1:10窗口2:10窗口2:9窗口2:8窗口2:7窗口2:6窗口2:5窗口2:3窗口2:2窗口2:1票卖完了窗口1:2票卖完了
出现问题:
两个线程同时访问一个共享数据,现在没有任何控制,两个线程同时可以进入方法中执行。
确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源。
使用同步锁(正确)在Java代码中实现同步:使用synchronized(同步锁)关键字同步方法或代码块。
synchronized (同步锁){ // 需要被同步的代码; } //synchronized还可以放在方法声明中,表示整个方法,为同步方法。
使用extends:
使用synchronized关键字,synchronized可以修饰代码块。
synchronized修饰方法,如果是非静态方法,那么锁对象是this。如果是静态方法,那么锁对象是类的Class对象(一个类只有一个Class对象)
方法一:
public class TickThread extends Thread{ //假设有10张票 static变量只有一份,多个对象共享 static int num = 10; //多个线程共享的同一个锁对象 static Object obj = new Object(); //模拟出票 @Override public void run() { while (true){ synchronized (obj){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); num--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println("票卖完了"); break; } } } }}public class Test { public static void main(String[] args) { //创建两个线程对象 num是成员变量 TickThread t1 = new TickThread(); t1.setName("窗口1"); t1.start(); TickThread t2 = new TickThread(); t2.setName("窗口2"); t2.start(); }}
方法二:
public class TickThread extends Thread{ //假设有10张票 static变量只有一份,多个对象共享 static int num = 10; //多个线程共享的同一个锁对象 static Object obj = new Object(); //模拟出票 @Override public void run() { while(true){ this.print(); if(num == 0){ System.out.println("票卖完了"); break; } } } public static synchronized void print(){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; } }}public class Test { public static void main(String[] args) { //创建两个线程对象 num是成员变量 TickThread t1 = new TickThread(); t1.setName("窗口1"); t1.start(); TickThread t2 = new TickThread(); t2.setName("窗口2"); t2.start(); //t1对象地址==t2对象地址 System.out.println(t1.getClass() == t2.getClass()); //true }}
使用Runnable接口:
synchronized是靠底层指令控制实现。
synchronized加锁实现,底层运行是通过编译后的指令中来进行控制的。
synchronized可以修饰代码块,修饰方法,注意锁的对象(锁对象可能会变)。
synchronized加锁方式是隐式的,进入到同步代码块时,自动获取锁;同步代码块执行完成后,自动释放锁。
方法一:
public class TickThread implements Runnable{ //是否加static并无影响,因为只创建了一个线程任务对象,num只有一份 static int num = 10; //这就是共享资源 @Override public void run() { while (true){ //可以用this,因为this只用了一个 synchronized (this){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; }else { System.out.println("票卖光了"); break; } } } }}public class Test { public static void main(String[] args) { //创建一个出票的任务 TickThread t = new TickThread(); //只创建了一个线程任务对象 Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口2"); t2.start(); }}
方法二:
public class TickThread implements Runnable{ //是否加static并无影响,因为只创建了一个线程任务对象,num只有一份 static int num = 10; //这就是共享资源 @Override public void run() { while(true){ this.print(); if(num == 0){ System.out.println("票卖完了"); break; } } } public static synchronized void print(){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; } }}public class Test { public static void main(String[] args) { //创建一个出票的任务 TickThread t = new TickThread(); //只创建了一个线程任务对象 Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口2"); t2.start(); }}
Lock接口Lock锁是靠java代码来控制的。
ReentrantLock类实现了Lock接口,可以来控制与synchronized相同的功能,拥有与synchronized相同的并发性和内存语义,但是两者的实现细节完全不同。
在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
public class TickThread implements Runnable{ static int num = 10; //这就是共享资源 ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { //加锁 lock.lock(); if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; }else { System.out.println("票卖光了"); break; } }finally { //synchronized如果出现异常会自动释放锁 //释放锁,尽量放在finally代码块中释放锁,一旦try中出现异常,也保证把锁释放掉 lock.unlock(); } } }}public class Test { public static void main(String[] args) { //创建一个出票的任务 TickThread t = new TickThread(); //只创建了一个线程任务对象 Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口2"); t2.start(); }}
线程死锁不同的线程分别占用对方需要的同步资源不放弃,出现死锁,发生死锁后,程序不会报错,只会等待,所有的线程都处于阻塞状态,无法继续。
锁嵌套时,容易发生死锁现象。加锁时,考虑清楚锁的顺序,尽量减少嵌套使用。
public class DieThread extends Thread{ boolean flag ; static Object objA = new Object(); static Object objB = new Object(); public DieThread(boolean flag) { this.flag = flag; } @Override public void run() { //双方分别持有对方需要的同步资源不放弃,造成死锁 if(flag){ synchronized (objA){ System.out.println("if objA"); synchronized (objB){ System.out.println("if objB"); } } }else { synchronized (objB){ System.out.println("else objB"); synchronized (objA){ System.out.println("else objA"); } } } }}public class Test { public static void main(String[] args) { DieThread d1 = new DieThread(true); d1.start(); DieThread d2 = new DieThread(false); d2.start(); }}
线程通信线程通讯指的是多个线程通过相互牵制,相互调度,制约运行,即线程间的相互作用。
涉及三个方法:
wait():让线程等待,wait()方法后线程就进入到阻塞状态,,并释放同步监视器,必须通过另一个线程唤醒。
notify():唤醒等待中的线程。一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():唤醒等待中的所有被wait的线程。
注意:wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
//两个线程交替打印1-100之间的数字public class PrintThread implements Runnable{ int num = 0; //共享变量// Object obj = new Object(); //也可将锁换为obj @Override public void run() { while (true){ synchronized (this){ //唤醒等待的线程 this.notify(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(num <= 100){ System.out.println(Thread.currentThread().getName()+":"+num); num++; }else { break; } try { //让线程等待。wait方法一定要调用的时锁对象中的wait方法,可以释放掉锁。 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }}public class Test { public static void main(String[] args) { PrintThread p = new PrintThread(); Thread t1 = new Thread(p); t1.start(); Thread t2 = new Thread(p); t2.start(); }}
sleep()和wait()相同点:
都可以让线程进入阻塞状态。
不同点:
sleep()
是Thread类中的方法
不会释放锁
休眠时间到了后,会自动进入到就绪状态
wait()
是Object类中的方法
wait方法是可以释放锁
wait后的线程需要使用notify/notifyAll唤醒
生产者和消费者问题两个线程之间相互牵制使用。
问题描述:生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待。
生产者线程 +1
柜台中的商品
消费者线程 -1
public class Productor extends Thread{ Counter counter; public Productor(Counter counter) { this.counter = counter; } @Override public void run() { while (true){ counter.jia(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Customer extends Thread{ Counter counter; public Customer(Counter counter) { this.counter = counter; } @Override public void run() { while (true){ counter.jian(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Counter { int num = 1; //商品数量为1 public synchronized void jia(){ if(num == 0){ num++; System.out.println("生产者生产了一个商品"); this.notify(); }else { try { this.wait(); //生产者等待 释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } } //消费者调用 public synchronized void jian(){ if(num == 1){ num--; System.out.println("消费者取走了一个商品"); this.notify(); }else { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Test { public static void main(String[] args) { Counter counter = new Counter(); //共享同一个柜台,共享数据 Productor productor = new Productor(counter); productor.start(); //启动生产者线程 Customer customer = new Customer(counter); customer.start(); //启动消费者线程 }}
第三种创建线程的方式继承Thread还是实现Runnable,最终都是重写Run()。run方法没有返回值,也不能抛出异常,就存在局限性。
Java中推出了新的接口 Callable接口,定义了一个call()方法,可以有返回值(使用泛型自定义),可以抛出异常。
实现Callable接口与使用Runnable相比,Callable功能更强大些。
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回结果
接收任务:
FutureTask
创建线程:
Thread t = new Thread(futureTask); t.start(); Integer val = futureTask.get(); //获得线程call方法的返回值
示例:
public class SumThread implements Callable