目录
程序进程线程
-
程序
程序(program),是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,存在于硬盘当中
-
进程
进程((process),就是正在执行的程序,进程是程序运行和操作系统资源分配的最小单位
-
线程
线程(thread),进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元
线程和进程之间的区别,我觉得可以用这个例子来看出两者的不同,进程就是一栋房子,线程就是住在房子里的人。进程是一个独立的个体,有自己的资源,线程是在进程里的,多个线程共享着进程的资源。
- 一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
- 每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序, java程序的入口main()方法就是在主线程中被执行的。
- 在主线程中可以创建并启动其它的线程;
- 一个进程内的所有线程共享该进程的内存资源。
线程互斥的4中方式
创建线程
线程代码存放Thread子类run方法中
启动线程一定不能调用run() , 否则就是普通方法调用
1.继承Thread类
public class MyThread extends Thread { //继承Thread类
@Override ////重写run方法
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("Thread:"+i);
}
}
}
public class Text { //主线程
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); //启动线程
for (int i = 0; i < 10000; i++) {
System.out.println("main:"+i);
}
}
}
2.实现Runnable接口
线程代码存在接口的子类的run方法
Runnable接口的存在主要是为了解决Java中不允许多继承的问题
public class MyThread implements Runnable{
@Override //创建一个线程作为外壳,将myThread包起来
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("thread"+i);
}
}
}
public class Text {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread =new Thread(myThread);
thread.start();
for (int i = 0; i < 10000; i++) {
System.out.println();
}
}
}
实现Runnable的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
常用方法
sleep()
Thread类下的静态本地方法,调用此方法会让当前线程进入休眠状态,不会释放当前占有的锁,需要处理异常
wait()
Object类下的方法,调用此方法会让线程进入waiting状态
,只有等待其他线程的通知唤醒或被中断后才会返回,会释放占有的锁,一般用在同步代码块中完成线程通信
notify()
Object类下的方法,对于被wait()进入等待的线程,会对这些线程随机唤醒一个
yield()
Thread类下的静态本地方法,会让当前线程让出CPU执行时间片,与其他线程重新竞争CPU时间片。
interrupt()
向线程发出一个中止信号。不能中断在运行中的线程,它只能改变中断状态而已,实际完成的是让受阻塞的线程退出阻塞状态;也就是如果当前线程正在睡眠:则抛出InterruptedException
异常并提前退出阻塞状态
join()
用于等待其他线程终止,A线程调用B线程的join(),A会等待B线程执行完成自己才会执行,在此期间A进入阻塞状态
sleep()和wait()
- sleep是Thread类中的静态方法,Wait是Object类的本地方法
- sleep不会释放锁,而wait会释放锁,加入等待队列中
- sleep自动唤醒,wait需要通过notify唤醒
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
- sleep可以在任何地方使用, 而wait只能在同步控制方法或者同步控制块里面使用(synchronized)
- sleep是当前线程用于放缓休眠,而wait用于多线程之间通信(其他线程可以调用)
yeild()和join()
- yeild直接进入就绪状态,join进入阻塞状态,等待这个线程死亡(执行完)
为什么不调用run()
new一个Thread会使线程进入新建状态,使用start()会准备线程启动的相关的操作,获取到时间片后会自动执行run()中的方法;如果执行run(),会把其当作一个普通的函数调用。
线程优先级
-
计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;
-
优先级较高的线程有更多获得CPU的机会,反之亦然;
-
优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过
setPriority()
和getPriority()方法来设置或返回优先级;
线程状态
- 新建:当一个线程类new出一个实例后,就进入新建状态
- 就绪:具备了运行的条件,只是没分配到CPU资源
- 运行:获得CPU执行权
- 阻塞:线程被挂起,让出 CPU执行权,进入阻塞状态
- 等待阻塞: 调用wait()后,JVM会将线程放入等待序列 waiting queue
- 同步阻塞:获取同步锁失败,进入锁池 lock pool
- 其他阻塞:sleep(),join(),等待同步锁
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常 导致结束
线程阻塞三种形式
线程的分类
用户线程
用来编写方法线程
守护线程
-
任何一个守护线程都是整个JVM中所有非守护线程的保姆
-
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作
-
守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
-
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没 有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了
-
设置线程为守护线程必须在启动线程之前,否则会跑出一个 IllegalThreadStateException异常
SHThread shThread = new SHThread();
shThread.setDaemon(true); //设置为守护线程
shThread.start();
多线程
概念
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
何时需要多线程
-
程序需要同时执行两个或多个任务
-
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等
-
需要一些后台运行的程序时
优点
- 提高程序的响应
- 提高CPU的利用率
- 改善程序结构,将复杂任务分为多个线程,独立运行
缺点
- 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多
- 多线程需要协调和管理,所以需要CPU时间跟踪线程
- 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题
public class ManyThread implements Runnable
public static void main(String[] args) {
ManyThread manyThread = new ManyThread();
Thread mt1 = new Thread(manyThread); //创建线程对象
mt1.setName("窗口1");
Thread mt2 = new Thread(manyThread);
mt2.setName("窗口2");
mt1.start();
mt2.start();
}
线程同步
并发与并行
-
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
-
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事(同一个对象被多个线程同时操作)
多线程同步
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到
(买票,取款)
synchronized
修饰代码块
- synchronized (同步监视器(obj)){ // 需要被同步的代码; }
synchronized在继承Thread情况下修饰代码块时 , 因为创建了两个Thread对象 , 为了保证只有一个进入synchronized修饰的代码块内 , 所以static Object obj = new Object(); obj表示锁对象
但如果在继承Runnable接口的情况下 , 只new了一个对象 , 所以直接用synchronized (this) {…}修饰(表示锁对象)
//修饰代码块
public class ManyThread extends Thread {
static int num = 20;
static Object obj = new Object();//保证只有一个
@Override
public void run() {
while (true) {
synchronized (obj) { //创建同步锁(对象)
if (num > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-----" + num);
num--;
} else {
break;
}
}
}
}
}
修饰方法
synchronized还可以放在方法声明中,表示整个方法为同步方法。
public synchronized void show (String name){
// 需要被同步的代码; }
synchronized在修饰方法时 , 同步对象(锁)默认是this , 一旦创建多个线程对象 (只针对继承Thread类的方式)–用static修饰 : static修饰后 , 同步对象变为 线程类(类只有一个)
public class TicketThread extends Thread{
static int num = 10;//票数 共享资源
@Override
public void run() {
while(true){
if(num>0){
print();
}else{
break;
}
}
}
/*
synchronized在修饰方法时,同步对象(锁), 默认是this 一旦创建多个线程对象(只针对继承Thread类的方式)
static修饰后,同步对象变为 线程类(类只有一个)
*/
public static synchronized void print(){ //静态
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出票:"+num);//0
//线程1再减之前 0
num--;
}
}
}
implements Runnable情况下不用加static
//购票
public class BuyTicket implements Runnable {
static int num = 100;
@Override
public synchronized void run() {
while (num > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
System.out.println(Thread.currentThread().getName() + "\t已购买,剩余" + num + "张票");
}
}
public void buy() {
num--;
}
}
public class Text {
public static void main(String[] args) {
BuyTicket mythread = new BuyTicket();
//两个线程执行一个任务 num是只有一个的
Thread thread1 = new Thread(mythread, "t1");
Thread thread2 = new Thread(mythread, "t2");
thread1.start();
thread2.start();
}
}
-
同步就是排队+锁
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
-
同步监视器
同步监视器可以是任何对象,必须唯一,保证多个线程获得是同一个对象 (锁).
-
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中代码.
2.第二个线程访问,发现同步监视器被锁定,无法访问.
3.第一个线程访问完毕,解锁同步监视器.
4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问.
LOCK
通过显式定义同步锁对象来实现同步
synchronized–>隐式加锁 , 自动释放锁 (可以锁定代码块, 也可以锁定方法)
Lock --> 可以显示的加锁 , 需要释放 , 只能锁定代码块
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存 语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加 锁、释放锁
private Lock lock = new ReentrantLock(); //添加锁对象
lock.lock(); //获得锁
lock.unlock(); //释放锁
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock(); //添加锁对象
@Override
public void run() {
while (true) {
try {
lock.lock(); //获得锁
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (101 - tickets) + "张票");
tickets--;
}
}finally {
lock.unlock(); //释放锁
}
}
}
}
线程死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步 资源,就形成了线程的死锁。出现死锁后,不会报异常,只是所有的线程都处于阻塞状态,无法继续运行
产生死锁的必要条件
- 互斥条件:一个资源任意时刻只能被一个线程占用
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放
- 不可剥夺条件:进程已获得的资源不能被剥夺,只能自己用完释放
- 循环等待条件:多线程之间形成一种头尾相连循环等待资源的关系
避免死锁的方式
- 破坏请求和保持条件:一次性申请所有资源(小锁变大锁)
- 破坏不可剥夺条件:一个线程请求不到他的资源,可以主动释放自己的资源
- 锁排序法:如果某个线程需要获得A锁和B锁才能对资源进行操作,那我们可以设定让其获取到A锁才有资格获取到B锁
死锁示例
public class DieLock extends Thread{
static Object objA = new Object();
static Object objB = new Object(); //两个锁
boolean flag;
DieLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){ //线程1
synchronized (objA){
System.out.println("ifobjA");
synchronized (objB){
System.out.println("ifobjB");
}
}
}else { //线程2
synchronized (objB){
System.out.println("elseobjB");
synchronized (objA){
System.out.println("elseobjA");
}
}
}
}
}
public class Test {
public static void main(String[] args) {
DieLock dieLock1 = new DieLock(true);
DieLock dieLock2 = new DieLock(false);
dieLock1.start();
dieLock2.start();
}
}
--------------------
ifobjA
elseobjB
哲学家就餐
都先拿左筷子再拿右筷子,会出现死锁
让一个人先拿右边,解决,但是要一个一个吃,效率低
所以每隔一个人让这个人先拿右边
线程通信
线程通讯指的是多个线程通过消息传递实现相互牵制,相互调度,即线程间的相互作用
notify()
{ }
wait() 释放锁
-
.wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
-
.notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
-
.notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
说明:
.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中
案例一 : 两个线程交替打印1~100
public class PrintNum extends Thread{
static int num = 0;
static Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj){
obj.notify(); //唤醒等待的线程,由于已经有线程持有锁了,也是不能进入到同步代码块
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num<100){
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
}else{
break;
}
try {
obj.wait(); //线程等待,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test {
public static void main(String[] args) {
PrintNum printNum1 = new PrintNum();
PrintNum printNum2 =new PrintNum();
printNum1.start();
printNum2.start();
}
}
案例二 :生产者/消费者问题
生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待
//柜台
public class Counter {
int num = 0;
// 同步对象时this 就是同一个Counter对象
public synchronized void add(){
if(num==0){
num++;
System.out.println("生产一个,剩余"+num+"个");
this.notify(); //商品数量足够,唤醒消费者
}else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sub(){
if(num>0){
num--;
System.out.println("售出一个,剩余"+num+"个");
this.notify();
}else {
try {
this.wait();
} 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){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.sub();
}
}
}
//生产者生产线程
public class Productor extends Thread{
Counter counter;
public Productor(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counter.add();
}
}
}
public class Test {
public static void main(String[] args) {
Counter counter = new Counter(); //创建两个线程的同一共享对象
Customer customer = new Customer(counter);
Productor productor =new Productor(counter);
customer.start();
productor.start();
}
}
--------------------------------------
生产一个,剩余1个
售出一个,剩余0个
生产一个,剩余1个
售出一个,剩余0个
......
新增创建线程方式
实现Callable接口
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,获取返回结果
//线程
public class CallableThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int a = 100;
return a; //返回a
}
}
//测试
public class Text {
public static void main(String[] args) {
CallableThread callableThread = new CallableThread();
FutureTask<Integer> futureTask= new FutureTask<Integer>(callableThread); //借助FutureTask类,获取返回结果
Thread t = new Thread(futureTask); //实类化
t.start();
try {
Integer res = futureTask.get(); //获得线程call方法的返回值
System.out.println(res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}