进程:是一个正在执行中的程序,每一个进程都有一个执行顺序是,该顺序是一个执行路径或者叫一个控制单元。
线程:是进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程中至少有一个线程。
多线程的意义:宏观上使多个作业被同时执行,提高了效率。
二、线程的特性
其实在多线程状态下,某一时刻,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()方法
暂停当前正在执行的线程对象,并执行其他线程。