进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间;
线程:进程中的一个执行单元,负责进程中的程序的运行,一个进程至少要有一个线程,一个进程可以有多个线程,具有多个线程的程序称为多线程程序
为什么要继承Thread类,而不直接实例化Thread类的对象,并调用Thread的run方法?
答: 因为Thread类中的run方法里面其实什么事情也没有做。所以我们需要通过继承Thread类,重写run方法,在其中添加自己想要执行的代码,然后创建子类对象,调用start方法开启线程,这样就能实现多线程。
获取当前线程的名称:Thread.cuncurretnThread().getName()
一、实现多线程的两种方法:
1、创建继承Thread的子类,重写run方法,实例化该类调用start方法,开启线程;
2、创建类,实现Runnable,重写run方法,创建该类的实例化对象,作为Thread的参数创建Thread对象,调用start方法开启线程;
实现Runnable接口的方式,更加符合面向对象,线程分为两个部分,一部分为线程对象,一部分为线程任务; 继承Thread类的方式,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,又有线程任务。 实现Runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。实现Runnable接口还可以避免单继承的局限性。
二、线程的状态:
三、线程安全问题:
产生安全问题的原因:
- 多个线程在操作共享的数据;
- 线程任务操作共享数据的代码有多条(即有多次运算);线程任务操作共享数据的代码有多条(即有多次运算);
解决思路:
只要让一个线程在执行线程任务是将多条操作共享数据的代码执行完,在执行过程中,不要让其他线程参与运算,就行了;
同步的好处:解决了多线程的安全问题;
同步弊端:降低了程序的性能;
同步的前提:必须保证多个线程在同一个同步中使用的是同一个锁。
代码体现:
- Java中通过同步代码块来解决多线程安全问题:
synchronized(对象) {
需要被同步的代码;
}
- Java中还可以通过同步方法来解决多线程安全问题:
public synchronized void function() {
需要被同步的代码;
}
同步方法使用的锁是调用该方法的对象,即this;
同步代码块使用的锁可以是任意对象。
注意:static同步方法使用的锁不是this,而是字节码文件对象,即 类名.class。
同步只需将操作共享数据的代码加入同步,没有必要对所有代码进行同步。
四、死锁
- 情景1
当多线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步,就可能引发死锁。
// Thread-0
synchronized (obj1) {
synchronized (obj2) {
//业务代码
}
}
// Thread-1
synchronized (obj2) {
synchronized (obj1) {
//业务代码
}
}
/**
* 死锁之情况1
*
* 2018年9月30日上午11:26:24
*/
public class DeadLockTest{
public static void main(String []args){
DeadLock dl1 = new DeadLock(true);
DeadLock dl2 = new DeadLock(false);
Thread t1 = new Thread(dl1);
Thread t2 = new Thread(dl2);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finish...");
}
}
class DeadLock implements Runnable{
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
}
public void run(){
if(this.flag){
synchronized (Lock.LOCK_A){
System.out.println(Thread.currentThread().getName()+"...if...LOCK_A");
synchronized (Lock.LOCK_B){
System.out.println(Thread.currentThread().getName()+"...if...LOCK_B");
}
}
} else {
synchronized (Lock.LOCK_B){
System.out.println(Thread.currentThread().getName()+"...else...LOCK_B");
synchronized (Lock.LOCK_A){
System.out.println(Thread.currentThread().getName()+"...else...LOCK_A");
}
}
}
}
}
//定义一个锁类,用于存储锁对象
class Lock{
public static final Object LOCK_A = new Object();
public static final Object LOCK_B = new Object();
}
运行结果:
2. 情景2
多线程中最为常见的应用案例——生产者消费者问题
生产和消费同时执行,需要多线程,但是执行的任务却不相同,处理的资源却是相同的,这就是线程间通信。
/**
* 多线程应用情景2:
* 生产者消费者问题
*/
public class ProducerConsumer {
public static void main(String[] args) {
//1、创建资源对象
Resource r = new Resource();
//2、创建线程任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//3、创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
//4、启动线程
t1.start();
t2.start();
}
}
//1、描述资源。属性:商品名称和编号;行为:对商品名称赋值,获取商品
class Resource{
private String name;
private int count = 1;
public String getName() {
System.out.println(Thread.currentThread()+".....消费了..."+this.name);
return name;
}
public void setName(String name) {
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread()+"...生产了..."+this.name);
}
}
//2、描述生产者
class Producer implements Runnable {
private Resource r;
//生产者一初始化就要有资源,需要将资源传递到构造方法中
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
for(int i=0;i<50;i++) {
r.setName("面包");
}
}
}
//3、描述消费者
class Consumer implements Runnable {
private Resource r;
//消费者一初始化就要有资源,需要将资源传递到构造方法中
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
for(int i=0;i<50;i++) {
r.getName();
}
}
}
运行结果:
问题分析:数据错误:很早期生产的商品,过了很久才被消费。出现了线程安全问题。
问题解决:加入同步机制(
public synchronized String getName() {
System.out.println(Thread.currentThread()+".....消费了..."+this.name);
return name;
}
public synchronized void setName(String name) {
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread()+"...生产了..."+this.name);
}
),解决,不会再消费到很早期生产出来的商品。
加入同步后的运行结果:
分析结果:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
理想的结果:希望生产一个商品,消费一个商品,再生产下一个商品。
分析与解决:
搞清楚几个问题?
生产者何时生产呢?消费者何时消费呢?
当没有了商品后,生产者就生产,有了商品后,就不要生产。
当有了商品后,消费者就消费,没有了商品后,就不要消费。
生产者生产了商品后应该告诉消费者来消费,这时的生产者应该处于等待状态;
消费者消费了商品后应该告诉生产者来生产,这时的消费者应该处于等待状态。
等待:wait();
告诉:notify();//唤醒
package thread类测试.同步的问题及解决.生产者消费者.等待唤醒机制;
/**
* 多线程应用情景2:
* 生产者消费者问题
* 加入同步后,解决了消费早期商品的问题,但出现了
* 问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
*
* 分析与解决:
* 搞清楚几个问题?
* 生产者何时生产呢?消费者何时消费呢?
* 当没有了商品后,生产者就生产,有了商品后,就不要生产。
* 当有了商品后,消费者就消费,没有了商品后,就不要消费。
* 生产者生产了商品后应该告诉消费者来消费,这时的生产者应该处于等待状态;
* 消费者消费了商品后应该告诉生产者来生产,这时的消费者应该处于等待状态。
*
* 等待:wait();
* 告诉:notify();//唤醒
* 2018年9月30日下午4:03:21
*/
public class ProducerConsumer2 {
public static void main(String[] args) {
//1、创建资源对象
Resource r = new Resource();
//2、创建线程任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//3、创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
//4、启动线程
t1.start();
t2.start();
}
}
//1、描述资源。属性:商品名称和编号;行为:对商品名称赋值,获取商品
class Resource{
private String name;
private int count = 1;
//定义标记,false表示没有商品,true表示有商品待消费
private boolean flag = false;
public synchronized String getName() {
if(!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread()+".....消费了..."+this.name);
//消费了商品,修改标记
flag = false;
//唤醒生产者
notify();
return name;
}
public synchronized void setName(String name) {
if(flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread()+"...生产了..."+this.name);
//生产了商品,修改标记
flag = true;
//唤醒消费者
notify();
}
}
//2、描述生产者
class Producer implements Runnable {
private Resource r;
//生产者一初始化就要有资源,需要将资源传递到构造方法中
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
for(int i=0;i<50;i++) {
r.setName("面包");
}
}
}
//3、描述消费者
class Consumer implements Runnable {
private Resource r;
//消费者一初始化就要有资源,需要将资源传递到构造方法中
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
for(int i=0;i<50;i++) {
r.getName();
}
}
}
多生产多消费问题:
问题1:生产了商品没有被消费,同一个商品被消费多次
产生原因:被唤醒的线程没有判断标记,造成问题1的产生。
解决:只要让被唤醒的线程必须判断标记就可以了,即将if判断标记的方式更改为while判断标记的方式。记住:多生产多消费,必须使用while判断标记。
问题2:加入while判断后,出现了死锁。
产生原因:生产方唤醒了线程池中等待的生产方的线程,即本方唤醒了本方。
解决:希望本方要唤醒对方,没有对应的方法可供调用,因此只能唤醒所有,即调用notifyAll()唤醒。
此种解决方案解决了问题2,但是降低了效率,因为有些线程可以不用唤醒。
package thread类测试.同步的问题及解决.生产者消费者.多生产多消费;
/**
* 多线程应用情景2:
* 多生产多消费者问题
* 问题1:生产了商品没有被消费,同一个商品被消费多次
* 产生原因:被唤醒的线程没有判断标记,造成问题1的产生。
* 解决:只要让被唤醒的线程必须判断标记就可以了,即将if判断标记的方式更改为while判断标记的方式。**记住:多生产多消费,必须使用while判断标记。**
* 问题2:加入while判断后,出现了死锁。
* 产生原因:生产方唤醒了线程池中等待的生产方的线程,即本方唤醒了本方。
* 解决:希望本方要唤醒对方,没有对应的方法可供调用,因此只能唤醒所有,即调用notifyAll()唤醒。
* 此种解决方案解决了问题2,但是降低了效率,因为有些线程可以不用唤醒。
*
* 2018年9月30日下午4:03:21
*/
public class ProducerConsumer3 {
public static void main(String[] args) {
//1、创建资源对象
Resource r = new Resource();
//2、创建线程任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//3、创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
//4、启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1、描述资源。属性:商品名称和编号;行为:对商品名称赋值,获取商品
class Resource{
private String name;
private int count = 1;
//定义标记,false表示没有商品,true表示有商品待消费
private boolean flag = false;
public synchronized String getName() {
if(!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread()+".....消费了..."+this.name);
//消费了商品,修改标记
flag = false;
//唤醒生产者
notify();
return name;
}
public synchronized void setName(String name) {
if(flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + this.count;
this.count++;
System.out.println(Thread.currentThread()+"...生产了..."+this.name);
//生产了商品,修改标记
flag = true;
//唤醒消费者
notify();
}
}
//2、描述生产者
class Producer implements Runnable {
private Resource r;
//生产者一初始化就要有资源,需要将资源传递到构造方法中
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
for(int i=0;i<50;i++) {
r.setName("面包");
}
}
}
//3、描述消费者
class Consumer implements Runnable {
private Resource r;
//消费者一初始化就要有资源,需要将资源传递到构造方法中
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
for(int i=0;i<50;i++) {
r.getName();
}
}
}
出现问题1:
更改if为while出现了问题2:
更改notify()为notifyAll(),解决问题2。
五、等待/唤醒机制
wait() : 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
notify() : 会唤醒线程池中任意一个等待的线程。
notifyAll() : 会唤醒线程池中所有的等待线程。
记住:以上这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。同一个锁上的notify只能唤醒该锁上被wait的线程。
为什么这些方法定义在Object类中呢?
因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然是在Object类中定义的。