进程:是一个正在执行中的程序,每一个进程都有一个执行顺序是,该顺序是一个执行路径或者叫一个控制单元。
线程:是进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程中至少有一个线程。
多线程的意义:宏观上使多个作业被同时执行,提高了效率。
二、线程的特性
其实在多线程状态下,某一时刻,cpu只能执行一个线程,所以只能有一个程序在运行(多核除外),cpu在多个线程
间做着快速切换动作,使多个线程看似在同时运行,也可以理解为多个线程在抢夺cpu的执行权,谁抢到,cpu就执行
谁,这是多线程的一个特性:随机性。
三、线程的创建方式
1.方式1:继承Thread类
(1)步骤
1> 定义一个类继承Thread类
2> 复写Thread类的run()方法
3> 建立对象,也就是创建了一个线程。调用start()方法启动线程
(2)为什么要继承Thread类并复写run()方法呢?
在java中,Thread类用于描述线程。
该类定义了一个功能用于存储线程要运行的代码,该功能就是run方()法。
线程调用start()方法启动线程后,JVM会自动调用该线程的run()方法。
(3)线程名
线程有默认的名称,格式:“Thread-编号” 编号从0开始
我们可以通过setname方法或者构造函数给线程自定义名称
例子:
package test; class MyThread extends Thread { //覆盖run()方法 public void run() { System.out.println(Thread.currentThread().getName() + "--mythread"); } } public class Test9 { /** * @param args */ public static void main(String[] args) { //创建两个线程 MyThread mythread1 = new MyThread(); MyThread mythread2 = new MyThread(); //启动这两个线程 mythread1.start(); mythread2.start(); } }
1> 定义一个类实现Runnable接口
2> 在类中实现Runnable接口中的run方法
3> 通过Thread类建立线程对象
4> 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
5> 调用Thread类的start()方法开启线程
例子:
package test; class MyThread implements Runnable { //覆盖run()方法 public void run() { System.out.println(Thread.currentThread().getName() + "--mythread"); } } public class Test9 { /** * @param args */ public static void main(String[] args) { //创建一个Runnable子类对象 MyThread mythread = new MyThread(); //创建两个线程,并将Runnable子类对象传入 Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); //启动这两个线程 t1.start(); t2.start(); } }
实现方式避免了单继承的局限性。
继承:线程执行的代码存放在Thread子类的run方法中。
实现:线程执行的代码存放在Runnable接口的子类的run方法中。
四、线程的状态五、多线程的安全问题
1.安全问题的由来
由于线程的随机性,当多条语句在操作多个线程共享的数据时,一个线程对多条语句只执行了一部分,还没有执行
完,另一个线程参与进来执行,导致共享数据的错误。
如下面这个例子:有一个商品,如果生产者生产的是“basketball”,那么消费者得到的用处就是“play”,如果生产的是面包,消费者得到的用处就是吃掉,但是程序在运行过程中出现了“basketball”和 “吃的”这样的对应关系,显然是不对的。
例子:
package com.itheima; //定义一个商品类 class Goods { String name; String use; } //定义生产者类,用来生产商品 class Producer implements Runnable { Goods good; Producer(Goods good) { this.good = good; } @Override public void run() { boolean flag = true; while(true) { if(flag) { //生产者生产一个篮球 good.name = "basketball"; good.use = "play"; flag = false; } else { //生产者生产一个面包 good.name = "面包"; good.use = "吃的"; flag = true; } } } } //定义消费者类,用来消费商品 class Consumer implements Runnable { Goods good; Consumer(Goods good) { this.good = good; } @Override public void run() { while(true) { //消费者消费商品 System.out.println(good.name + "---" + good.use); } } } public class Test027 { /** * 有一个商品,生产者生产出来什么东西,消费者就消费什么东西。 * @param args */ public static void main(String[] args) { //定义一个商品对象 Goods good = new Goods(); //将这个商品和生产者、消费者关联 Producer producer = new Producer(good); Consumer consumer = new Consumer(good); //生产者开始生产,消费者开始消费 new Thread(producer).start(); new Thread(consumer).start(); } }2.安全问题的分析
以上面的程序为例,我们分析一下问题产生的原因:生产者生产“basketball”---“play”,然后再生产“面包”,注意,当生产者在生产面包的过程中,执行了good.name = "面包";这句话后,由于线程的随机性,此刻如果cpu的执行权被消费者抢到,那么消费者消费的就是“面包”---“play”,因为生产者还没有执行good.use = "吃的";
六、多线程安全问题的解决
1.安全问题解决办法
针对多线程安全问题产生的原因,我们可以采取相应的解决办法:对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中其他线程不可以参与进来,可以用synchronized同步代码块来解决。
2.synchronized同步代码块(1)格式
synchronized(对象)
{
需要被同步的代码;
}
对象如同一把锁,持有锁的线程可以在同步中执行。没有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
(2)同步的前提1> 必须要有两个或者两个以上的线程
2> 必须是多个线程使用同一个锁
(3)同步代码块的利弊
1> 解决了多线程的安全问题
2> 在运行中需要判断锁状态,较为耗费资源
(4)怎么确定哪些代码应该放在同步代码块中??
1> 明确哪些代码是多线程运行的代码
2> 明确多线程的共享数据
3> 明确多线程运行代码中哪些是操作共享数据的
上面的程序我们加上同步代码块:
package com.itheima; //定义一个商品类 class Goods { String name; String use; } //定义生产者类,用来生产商品 class Producer implements Runnable { Goods good; Producer(Goods good) { this.good = good; } @Override public void run() { boolean flag = true; while(true) { synchronized(good) //同步代码块,我们用good作为锁,在程序中它是唯一的对象 { if(flag) { //生产者生产一个篮球 good.name = "basketball"; good.use = "play"; flag = false; } else { //生产者生产一个面包 good.name = "面包"; good.use = "吃的"; flag = true; } } } } } //定义消费者类,用来消费商品 class Consumer implements Runnable { Goods good; Consumer(Goods good) { this.good = good; } @Override public void run() { while(true) { synchronized(good) //同步代码块 { //消费者消费商品 System.out.println(good.name + "---" + good.use); } } } } public class Test028 { /** * @param args */ public static void main(String[] args) { Goods good = new Goods1(); Producer producer = new Producer(good); Consumer consumer = new Consumer(good); new Thread(producer).start(); new Thread(consumer).start(); } }3.同步函数
(1)格式
在函数上加上synchronized修饰符即可,如public void synchronized method(){};
(2)同步函数用的是哪一个锁呢?
函数需要被对象调用,函数都有一个所属对象引用,就是this,所以同步函数用的锁是this
如果同步函数被static修饰,用的锁就不再是this,而是该方法所在的类的字节码文件对象,即类名.class
七、线程间通信
1.线程间通信:等待唤醒机制
需求:多条流水线生产手机,每部手机都有唯一的编号,多条流水线来销售手机,生产一部手机就销售一部手机,然
后再生产下一步手机,如生产“手机001”,销售“手机001”,再生产“002”,再销售“手机002”。这时用之前的
生产者消费者的程序就满足不了需求了,因为生产者生产篮球后,还可以拥有执行权,继续生产面包。在生产者生产
产品后,需要放弃执行权,让消费者消费,然后消费者放弃执行权,让生产者生产。
引入Object的wait()、notify()、notifyAll()三个方法
wait():放弃cpu执行权,放弃锁
notify():唤醒等待的线程
notifyAll():唤醒等待的所有线程
针对需求的代码:
package com.itheima; class Phone { private String name; private int number = 0; private boolean flag = false; public synchronized void setName(String name) { while(flag) //如果手机未卖出,生产者wait。这里用if,线程被唤醒后不会再判断flag,应该用while try { this.wait(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } this.name = name; System.out.println(Thread.currentThread().getName() + "生产--" + this.name + ++number); flag = true; //生产手机后将标识符改变 this.notifyAll(); //唤醒销售线程 // this.notify(); //使用notify()最终会使所有线程处于冻结状态 } public synchronized void sellPhone() { while(!flag) //如果手机未生产,销售wait。这里用if,线程被唤醒后不会再判断flag,应该用while try { this.wait(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "消费------" + this.name + number); flag = false; //销售后修改标识符 this.notifyAll(); //唤醒生产线程 // this.notify();//使用notify()最终会使所有线程处于冻结状态 } } //生产者 class PhoneProducer implements Runnable { private Phone phone; PhoneProducer(Phone phone) { this.phone = phone; } @Override public void run() { while(true) phone.setName("华为"); } } //销售者 class PhoneConsumer implements Runnable { private Phone phone; PhoneConsumer(Phone phone) { this.phone = phone; } @Override public void run() { while(true) phone.sellPhone(); } } public class Test030 { /** * @param args */ public static void main(String[] args) { Phone phone = new Phone(); PhoneProducer pp = new PhoneProducer(phone); PhoneConsumer pc = new PhoneConsumer(phone); new Thread(pp).start(); new Thread(pp).start(); new Thread(pc).start(); new Thread(pc).start(); } }
2. 线程间通讯
其实就是多个线程在操作同一个资源,但是各个线程操作的动作不同。
wait() notify() notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,同步中才有监视器(锁)。
3.为什么这些方法都定义在Object类中?
这些方法在操作同步中的线程时,需要标示线程所持有的锁,因为只有持有同一把锁的线程,才可以相互唤醒。
而锁可以是任意对象,所以这些方法定义在所有类的超类Object中。
4.对应多个生产者消费者,为什么要定义while判断标记?
原因是让被唤醒的线程再一次判断标记
5.对应多个生产者消费者,为什么使用notifyAll()?
因为需要唤醒对方线程,只用notify 容易出现只唤醒本方线程的情况,最后导致程序中的所有线程都冻结
6. JDK1.5中提供了多线程升级解决方案
将同步synchronized替换成Lock操作
将Object中的wait notify notifyAll替换了Condition对象,该对象可以通过Lock获取。
实现了本方线程只唤醒对方线程。
例子:
package com.itheima; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Phone1 { private String name; private int number = 0; private boolean flag = false; //定义一个锁 Lock lock = new ReentrantLock(); //定义两个Condition对象,一个锁可以对应多个Condition对象 Condition pro = lock.newCondition(); Condition con = lock.newCondition(); public void setName(String name) throws InterruptedException { lock.lock(); //上锁 try { while(flag) pro.await(); //生产者等待 this.name = name; System.out.println(Thread.currentThread().getName() + "生产--" + this.name + ++number); flag = true; con.signal(); //唤醒销售者 } finally { //释放锁的代码一定要执行 lock.unlock(); } } public void sellPhone() throws InterruptedException { lock.lock(); try { while(!flag) con.await(); //销售者等待 System.out.println(Thread.currentThread().getName() + "消费------" + this.name + number); flag = false; pro.signal(); //唤醒生产者 } finally { //释放锁的代码一定要执行 lock.unlock(); } } } class PhoneProducer1 implements Runnable { private Phone1 phone; PhoneProducer1(Phone1 phone) { this.phone = phone; } @Override public void run() { while(true) try { phone.setName("华为"); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } class PhoneConsumer1 implements Runnable { private Phone1 phone; PhoneConsumer1(Phone1 phone) { this.phone = phone; } @Override public void run() { while(true) try { phone.sellPhone(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } } } public class Test031 { /** * @param args */ public static void main(String[] args) { Phone1 phone = new Phone1(); PhoneProducer1 pp = new PhoneProducer1(phone); PhoneConsumer1 pc = new PhoneConsumer1(phone); new Thread(pp).start(); new Thread(pp).start(); new Thread(pc).start(); new Thread(pc).start(); } }
1.停止线程
(1)由于stop()方法已过时,停止线程的方式只有一种,run()方法结束。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
例子:
package com.itheima; class MyThread1 implements Runnable { //定义标识符 private boolean flag = true; @Override public void run() { int num = 1; while(flag) { System.out.println(Thread.currentThread().getName() + "...." + num++); if(num == 5) flag=false; //修改标识符,控制循环 } } } public class Test032 { /** * @param args */ public static void main(String[] args) { MyThread1 mythread = new MyThread1(); new Thread(mythread).start(); new Thread(mythread).start(); for(int x = 0; x <=60; x++) { System.out.println(Thread.currentThread().getName() + "..." + x); } } }(2)特殊情况
当线程处于冻结状态,就不会读取到标示符,线程就不会结束。
这时需要对线程的冻结状态进行清除,强制让线程恢复到运行状态中,需要用到Thread类提供的interrupt方法
例子:
package com.itheima; class MyThread2 implements Runnable { //定义标识符 private boolean flag = true; @Override public synchronized void run() { int num = 1; while(flag) { try { wait(); //线程等待 } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); flag = false; //修改标识符,控制循环 } System.out.println(Thread.currentThread().getName() + "...." + num++); if(num == 5) flag = false; //修改标识符,控制循环 } } } public class Test033 { /** * @param args */ public static void main(String[] args) { MyThread2 mythread = new MyThread2(); Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); t1.start(); t2.start(); for(int x = 0; x <=60; x++) { System.out.println(Thread.currentThread().getName() + "..." + x); if(x == 60) { //中断线程的冻结状态 t1.interrupt(); t2.interrupt(); } } System.out.println("over"); } }2.join()方法
当A线程执行到了B线程的join方法时,A线程就会等B线程执行完后再执行。
join可以用来临时加入线程执行。
3.yield()方法
暂停当前正在执行的线程对象,并执行其他线程。