1.线程与进程
进程的概念
进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。
所以,进程是系统中的并发执行的单位。
线程的概念
线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。如果把进程理解为在逻辑上操作系统所要完成的任务,那么线程表示完成该任务的许多可能的子任务之一。
2.线程的优势
(1)易于调度。
(2)提高并发性。通过线程可方便有效地实现并发。进程可创建多个线程来执行同一程序的不同部分或相同部分。
(3)开销少。创建线程比创建进程要快,所需开销很少。
(4)利于充分发挥多处理器的功能。通过创建多线程进程(即一个进程可具有两个或更多个线程),每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。
3.进程与线程的关系
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)处理机分给线程,即真正在处理机上运行的是线程。
(4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
简单来说:
1)一个程序至少有一个进程,一个进程至少有一个线程.
2)进程在执行过程中拥有独立的资源,而多个线程共享进程中的资源
4.计算机中的进程
任务管理器可以列出当前运行的所有进程。在每个进程中有多个线程执行
5.线程的运行
多个线程同时抢占CPU的资源,谁先抢到谁先执行,没有先后顺序
6.线程的实现
线程实现的两种方式:继承Thread类、实现Runnable接口
7.线程实现方式一
第一种方式:继承Thread类
1)子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2)建立子类对象的同时线程也被创建。
3)通过调用start方法开启线程。
注意:在Thread子类中,必须明确的覆写Thread类中的run()方法,此方法为线程的主体
举例:
public class ThreadTest extends Thread {
/**
* 线程实现方法一:继承Thread类
*/
private String name;
public ThreadTest(String name){
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 6; i++) {
System.out.println(name+"\t"+"i="+i);
}
}
public static void main(String[] args) {
//创建线程对象
ThreadTest t1=new ThreadTest("线程一");
ThreadTest t2=new ThreadTest("线程二");
//启动线程
t1.start();
// t1.run();
//启动线程
t2.start();
// t2.run();
}
}
8.线程的启动
如果要想正确的启动线程,是不能直接调用run()方法的,应该调用从Thread类中继承而来的start()方法,才可以启
动线程
9.Start()方法
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
...
start0();
...
}
private native void start0();
从以上的代码中可以发现,在一个类中的start()方法调用时可能会抛出“IllegalThreadStateException”的异常,
一般在重复调用start()方法的时候会抛出这个异常。而且实际上此处真正调用的是start0()方法,此方法在声明处使用了native关键字声明,此关键字表示调用本机的操作系统函数,因为多线程的实现需要依靠底层操作系统支持。
10.线程的状态
要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有五种状态,即创建、就绪、运行、阻塞、终止。
11.线程的生命周期
1)创建
在程序中,用构造方法创建了一个线程对象后,新的线程对象便会处于新建状态,此时,它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread类的构造方法来实现,例如:Thread thread = new Thread();
2)就绪
新建线程对象后,调用该线程的start()方法就可以启动线程,当启动线程,线程就处于就绪状态。此时,线程将进入线程队列排队,等到CPU服务,这表明它已经具备了运行条件。
3)运行
当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程的run()方法,run()方法定义了该线程的操作于功能
4)阻塞
一个正在执行的线程在某些特殊情况下,如被认为挂起或者需要执行耗时的输入/输出操作时,就会让出CPU并暂时中止自己的执行,进入堵塞状态。在可执行状态下,如果调用sleep(),suspend(),wait()等方法,线程都将进入堵塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程才可以进入就绪状态。
5)死亡|终止
线程调用stop()方法时或者run()方法执行结束后,即可处于死亡状态,处于死亡状态的线程不具有继续行为能力
12.线程实现方式二
第二种方式:
实现Runnable接口
1)子类覆盖接口中的run方法。
2)通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3)Thread类对象调用start方法开启线程。
举例:
public class RunnableTest implements Runnable {
/**
* 线程实现方法二:实现Runnable接口
*/
private String name;
public RunnableTest(String name){
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 6; i++) {
System.out.println(name+"\t"+"i="+i);
}
}
public static void main(String[] args) {
//创建当前接口的对象
RunnableTest r1=new RunnableTest("线程一");
RunnableTest r2=new RunnableTest("线程二");
Thread t1=new Thread(r1);
Thread t2=new Thread(r2);
t1.start();
t2.start();
}
}
13.Thread类与Runnable接口的联系
Thread-->Runnable
MyThread-->Runnable Thread-->MyThread
14.Thread类与Runnable接口的区别
Thread类和Runnable接口之间在使用上也是有所区别的,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,则可以方便的实现资源的共享.
15.资源不共享的情况
举例:
/**
* 资源不共享的情况
* @author dell
*
*/
public class Test1 extends Thread{
private int ticket=5;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (ticket>0) {
System.out.println("票:"+ticket--);
}
}
}
public static void main(String[] args) {
//创建3个线程
Test1 t1=new Test1();
Test1 t2=new Test1();
Test1 t3=new Test1();
t1.start();
t2.start();
t3.start();
}
}
16.资源共享的情况
举例:
/**
* 资源共享
* @author dell
*
*/
public class Test2 implements Runnable{
private int ticket=5;
@Override
public void run() {
for(int i=1;i<=100;i++){
if(ticket>0){
System.out.println("票:"+ticket--);
}
}
}
public static void main(){
Test2 t=new Test2();
//创建三个线程
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
17.线程的方法
Thread类中的主要方法如下
方法 描述
public static Thread currentThread() 返回当前的线程
public final String getName() 返回线程名称
public final void setPriority(int priority) 设置线程优先级
public void start() 开始执行线程
public static void sleep(long m) 使用目前的线程休眠m秒(释放执行权和不释放锁)
public final void yield() 暂停目前的线程,运行其他线程
public void run() 执行线程
静态方法可以由类名直接调用
举例:
public class SleepTest extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i <= 3; i++) {
System.out.println("i="+i);
try {
//当前线程休眠2秒钟
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new SleepTest().start();
}
}
18.Join()
举例
Join代码实现打酱油情景-mother
public class MotherThread implements Runnable
{
public void run()
{
System.out.println("妈妈准备煮饭");
System.out.println("妈妈发现米酒用完了");
System.out.println("妈妈叫儿子去买米酒");
Thread son = new Thread(new SonThread()); //儿子线程创建
son.start(); //启动儿子线程
System.out.println("妈妈等待儿子把米酒买回来");
try {
son.join(); //儿子线程加入,并获得资源开始运行
}
catch (Exception ie)
{
System.err.println("发生异常!");
System.err.println("妈妈中断煮饭");
System.exit(1);
}
System.out.println(“妈妈开始煮饭”); //妈妈线程继续运行
System.out.println("妈妈把饭煮好了");
}
}
Join代码实现打酱油情景-son
public class SonThread implements Runnable
{
public void run() //儿子线程的运行主体
{
System.out.println("儿子出门去买米酒");
System.out.println("儿子买东西来回需要5分钟");
try {
for (int i=1; i<=5; i++)
{
Thread.sleep(2000);
System.out.print(i+"分钟");
}
}
catch (InterruptedException ie)
{
System.err.println("儿子发生意外");
}
System.out.println("\n儿子买米酒回来了");
}
}
Join代码实现打酱油情景-执行
public class Cooking
{
public static void main(String argv[])
{
Thread mother = new Thread(new MotherThread());
mother.start(); //只启动妈妈线程
}
}
19.setPriority()设置优先级
每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会
public static final int MIN_PRIORITY 最低优先级1
public static final int NORM_PRIORITY 中等优先级,线程默认的优先级5
public static final int MAX_PRIORITY 最高优先级10
举例:
public class ThreadTest extends Thread {
private String name;
public ThreadTest(String name){
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 6; i++) {
System.out.println(name+"\t"+"i="+i);
}
}
public static void main(String[] args) {
//创建线程对象
ThreadTest t1=new ThreadTest("线程一");
ThreadTest t2=new ThreadTest("线程二");
//设置优先级
t2.setPriority(10);
//启动线程
t1.start();
//启动线程
t2.start();
}
}
20.sleep() 线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用Thread.sleep()方法即可
21.yield()线程的礼让
在线程操作中,也可以使用yield()方法将一个线程的操作暂时让给其他线程执行。
举例:
class MyThread implements Runnable { // 实现Runnable接口
public void run() { // 覆写run()方法
for (int i = 0; i < 5; i++) { // 不断输出
System.out.
println(Thread.currentThread().getName()
+ "运行--> " + i); //输出线程名称
if (i == 3) {
System.out.print("线程礼让:");
Thread.currentThread().yield() ; // 线程礼让
}
}
}
}
public class ThreadYieldDemo {
public static void main(String args[]) {
MyThread my = new MyThread() ; // 实例化MyThread对象
Thread t1 = new Thread(my, "线程A") ; //定义线程对象
Thread t2 = new Thread(my, "线程B"); //定义线程对象
t1.start() ; // 启动多线程
t2.start() ; // 启动多线程
}
}
22.线程的结束
线程会以以下三种方式之一结束:
1)线程到达其run()方法的末尾;
2)线程抛出一个未捕获到的Exception或Error;
3)调用stop方法
23.Object类对线程的支持
方法 描述
public final void wait() 线程等待
public final void wait(long timeout)线程等待,并指定等待时间,以毫秒为单位(释放执行权和锁)
Public void notify() 唤醒第一个等待的线程
Public void notifyAll() 唤醒全部等待的线程
24.同步
A正在用这个资源
B也想用,但需要等待A将资源释放才可以使用
C也想用这个资源,也在等待A用完以后才可以使用
25.异步
A大家一起用
B好的好的,一起一起
C就是就是,资源共享
三个部分是融合在一起的
26.同步的特点
消耗资源,但是安全
1)同步的前提:
同步需要两个或者两个以上的线程。
多个线程使用的是同一资源。
未满足这两个条件,不能称其为同步
2)同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
27.线程的同步
同步是指在同一时间段内只能运行一个线程
同步可以解决线程中的安全问题
实现线程同步有俩种方法:同步代码块、同步方法
举例:
/**
* 线程同步案例
* */
public class TestSync implements Runnable {
Timer timer=new Timer();
public static void main(String[] args) {
TestSync test=new TestSync();
Thread t1=new Thread(test);
Thread t2=new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
public void run(){
timer.add(Thread.currentThread().getName());
}
}
class Timer{
private static int num=0;
//同步synchronized
public synchronized void add(String name){
synchronized (this) {
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(name+",你是第"+num+"个使用timer的线程");
}
}
}
28.死锁
同步的过程中会出现死锁
举例
//死锁
public class TestDeadLock implements Runnable {
public int flag=1;//表示角色,儿子或者爸爸
static Object o1=new Object(),o2=new Object();//表示成绩单或者玩具
@Override
public void run() {
//输出状态
System.out.println("flag="+flag);
if (flag==1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag==0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
TestDeadLock td1=new TestDeadLock();
TestDeadLock td2=new TestDeadLock();
td1.flag=1;
td2.flag=0;
Thread t1=new Thread(td1);
Thread t2=new Thread(td2);
t1.start();
t2.start();
}
}
29.线程通信-生产者和消费者问题
生成者、店员、消费者
1-生产者生产产品
2-生产者通知消费者可以取产品
3-消费者取走产品
4-消费者通知生产者可继续生产
举例:
/**生产者消费者问题,涉及到几个类
* 第一,这个问题本身就是一个类,即主类
* 第二,既然是生产者、消费者,那么生产者类和消费者类就是必须的
* 第三,生产什么,消费什么,所以物品类是必须的,这里是馒头类
* 第四,既然是线程,那么就不是一对一的,也就是说不是生产一个消费一个,既然这样,多生产的往哪里放,
* 现实中就是筐了,在计算机中也就是数据结构,筐在数据结构中最形象的就是栈了,因此还要一个栈类
*/
public class ProduceConsume {
public static void main(String[] args) {
SyncStack ss = new SyncStack();//建造一个装馒头的框
Producer p = new Producer(ss);//新建一个生产者,使之持有框
Consume c = new Consume(ss);//新建一个消费者,使之持有同一个框
Thread tp = new Thread(p);//新建一个生产者线程
Thread tc = new Thread(c);//新建一个消费者线程
tp.start();//启动生产者线程
tc.start();//启动消费者线程
}
}
//馒头类
class SteamBread{
int id;//馒头编号
SteamBread(int id){
this.id = id;
}
public String toString(){
return "steamBread:"+id;
}
}
//装馒头的框,栈结构
class SyncStack{
int index = 0;
SteamBread[] stb = new SteamBread[6];//构造馒头数组,相当于馒头筐,容量是6
//放入框中,相当于入栈
public synchronized void push(SteamBread sb){
while(index==stb.length){//筐满了,即栈满,
try {
this.wait();//让当前线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();//唤醒在此对象监视器上等待的单个线程,即消费者线程
stb[index] = sb;
this.index++;
}
//从框中拿出,相当于出栈
public synchronized SteamBread pop(){
while(index==0){//筐空了,即栈空
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.notify();
this.index--;//push第n个之后,this.index++,使栈顶为n+1,故return之前要减一
return stb[index];
}
}
//生产者类,实现了Runnable接口,以便于构造生产者线程
class Producer implements Runnable{
SyncStack ss = null;
Producer(SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
// 开始生产馒头
for(int i=0;i<20;i++){
SteamBread stb = new SteamBread(i);
ss.push(stb);
System.out.println("生产了"+stb);
try {
Thread.sleep(10);//每生产一个馒头,睡觉10毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//消费者类,实现了Runnable接口,以便于构造消费者线程
class Consume implements Runnable{
SyncStack ss = null;
public Consume(SyncStack ss) {
super();
this.ss = ss;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<20;i++){//开始消费馒头
SteamBread stb = ss.pop();
System.out.println("消费了"+stb);
try {
Thread.sleep(100);//每消费一个馒头,睡觉100毫秒。即生产多个,消费一个
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}