java多线程知识点总结

时间:2023-02-26 18:40:57

多线程(英语:multithreading)

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程可以在程序里独立执行,由操作系统负责多个线程的调度和执行。

线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈程序计数器为其执行上下文.多线程主要是为了节约CPU时间,充分利用了CPU的空闲时间片,线程的运行中需要使用计算机的内存资源和CPU。

优点

·使用线程可以把占据时间长的 程序中的 任务放到 后台去处理
·用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
· 程序的运行速度可能加快
·在一些等待的 任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种 情况下可以释放一些珍贵的资源如 内存占用等等。

缺点

·如果有大量的线程,会影响性能,因为 操作系统需要在它们之间切换。
·更多的线程需要更多的 内存空间。
·线程可能会给 程序带来更多“bug”,因此要小心使用。
·线程的中止需要考虑其对 程序运行的影响。
·通常块模型数据是在多个线程间共享的,需要防止线程死锁 情况的发生。


JAVA中的线程

一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

 

java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。



java中创建线程的两种方式:继承Thread和实现Runnable接口
实现Runnable接口适用情况: 资源共享+多个线程执行相同代码。
资源共享例子:购票系统,多个线程对共享资源进行读写。
执行相同代码例子:购票系统。new Thread(obj);obj是实现runnable的对象。

java多线程购票系统代码:
class SellThread implements Runnable
{
int tickets=100;//对共享资源的访问
public synchronized void sell()//用关键字sychronized,标志同步方法
{
if(tickets>0)
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}

public void run()
{
while(true)
{
sell();
}
}
}
public class TicketSystem
{
public static void main(String[] args)
{
SellThread st=new SellThread();
new Thread(st).start();//多个购票窗口执行相同操作
new Thread(st).start();//多个购票窗口执行相同操作
new Thread(st).start();//多个购票窗口执行相同操作
}
}

继承Thread类适用情况:
多个线程执行不同的代码,则分别继承重写Thread类。
如:生产者和消费者例子。
很多后台服务程序并发控制的基本原理都可以归纳为生产者 / 消费者模式。
用wait() / notify()方法实现生产者消费者例子
(代码来自 http://blog.csdn.net/monkey_d_meng/article/details/6251879,感谢原作者
[java] view plain copy
import java.util.LinkedList;

/**
* 仓库类Storage实现缓冲区
*
* Email:530025983@qq.com
*
* @author MONKEY.D.MENG 2011-03-15
*
*/
public class Storage
{
// 仓库最大存储量
private final int MAX_SIZE = 100;

// 仓库存储的载体
private LinkedList<Object> list = new LinkedList<Object>();

// 生产num个产品
public void produce(int num)
{
// 同步代码段
synchronized (list)
{
// 如果仓库剩余容量不足
while (list.size() + num > MAX_SIZE)
{
System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:"
+ list.size() + "/t暂时不能执行生产任务!");
try
{
// 由于条件不满足,生产阻塞
list.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

// 生产条件满足情况下,生产num个产品
for (int i = 1; i <= num; ++i)
{
list.add(new Object());
}

System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());

list.notifyAll();
}
}

// 消费num个产品
public void consume(int num)
{
// 同步代码段
synchronized (list)
{
// 如果仓库存储量不足
while (list.size() < num)
{
System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:"
+ list.size() + "/t暂时不能执行生产任务!");
try
{
// 由于条件不满足,消费阻塞
list.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

// 消费条件满足情况下,消费num个产品
for (int i = 1; i <= num; ++i)
{
list.remove();
}

System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());

list.notifyAll();
}
}

// get/set方法
public LinkedList<Object> getList()
{
return list;
}

public void setList(LinkedList<Object> list)
{
this.list = list;
}

public int getMAX_SIZE()
{
return MAX_SIZE;
}
}
/**
* 生产者类Producer继承线程类Thread
*
* Email:530025983@qq.com
*
* @author MONKEY.D.MENG 2011-03-15
*
*/
public class Producer extends Thread
{
// 每次生产的产品数量
private int num;

// 所在放置的仓库
private Storage storage;

// 构造函数,设置仓库
public Producer(Storage storage)
{
this.storage = storage;
}

// 线程run函数
public void run()
{
produce(num);
}

// 调用仓库Storage的生产函数
public void produce(int num)
{
storage.produce(num);
}

// get/set方法
public int getNum()
{
return num;
}

public void setNum(int num)
{
this.num = num;
}

public Storage getStorage()
{
return storage;
}

public void setStorage(Storage storage)
{
this.storage = storage;
}
}
/**
* 消费者类Consumer继承线程类Thread
*
* Email:530025983@qq.com
*
* @author MONKEY.D.MENG 2011-03-15
*
*/
public class Consumer extends Thread
{
// 每次消费的产品数量
private int num;

// 所在放置的仓库
private Storage storage;

// 构造函数,设置仓库
public Consumer(Storage storage)
{
this.storage = storage;
}

// 线程run函数
public void run()
{
consume(num);
}

// 调用仓库Storage的生产函数
public void consume(int num)
{
storage.consume(num);
}

// get/set方法
public int getNum()
{
return num;
}

public void setNum(int num)
{
this.num = num;
}

public Storage getStorage()
{
return storage;
}

public void setStorage(Storage storage)
{
this.storage = storage;
}
}
/**
* 测试类Test
*
* Email:530025983@qq.com
*
* @author MONKEY.D.MENG 2011-03-15
*
*/
public class Test
{
public static void main(String[] args)
{
// 仓库对象
Storage storage = new Storage();

// 生产者对象
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = new Producer(storage);
Producer p5 = new Producer(storage);
Producer p6 = new Producer(storage);
Producer p7 = new Producer(storage);

// 消费者对象
Consumer c1 = new Consumer(storage);
Consumer c2 = new Consumer(storage);
Consumer c3 = new Consumer(storage);

// 设置生产者产品生产数量
p1.setNum(10);
p2.setNum(10);
p3.setNum(10);
p4.setNum(10);
p5.setNum(10);
p6.setNum(10);
p7.setNum(80);

// 设置消费者产品消费数量
c1.setNum(50);
c2.setNum(20);
c3.setNum(30);

// 线程开始执行
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}
【要消费的产品数量】:50 【库存量】:0 暂时不能执行生产任务!
【要消费的产品数量】:30 【库存量】:0 暂时不能执行生产任务!
【要消费的产品数量】:20 【库存量】:0 暂时不能执行生产任务!
【已经生产产品数】:10 【现仓储量为】:10
【要消费的产品数量】:20 【库存量】:10 暂时不能执行生产任务!
【要消费的产品数量】:30 【库存量】:10 暂时不能执行生产任务!
【要消费的产品数量】:50 【库存量】:10 暂时不能执行生产任务!
【已经生产产品数】:10 【现仓储量为】:20
【要消费的产品数量】:50 【库存量】:20 暂时不能执行生产任务!
【要消费的产品数量】:30 【库存量】:20 暂时不能执行生产任务!
【已经消费产品数】:20 【现仓储量为】:0
【已经生产产品数】:10 【现仓储量为】:10
【已经生产产品数】:10 【现仓储量为】:20
【已经生产产品数】:80 【现仓储量为】:100
【要生产的产品数量】:10 【库存量】:100 暂时不能执行生产任务!
【已经消费产品数】:30 【现仓储量为】:70
【已经消费产品数】:50 【现仓储量为】:20
【已经生产产品数】:10 【现仓储量为】:30
【已经生产产品数】:10 【现仓储量为】:40

看完上述代码,对 wait() / notify() 方法实现的同步有了了解。你可能会对 Storage 类中为什么要定义 public void produce(int num); public void consume(int num); 方法感到不解,为什么不直接在生产者类 Producer 和消费者类 Consumer 中实现这两个方法,却要调用 Storage 类中的实现呢?淡定,后文会有解释。我们先往下走。


wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。

这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
    如果对象调用了wait方法就会使
持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
    如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
    如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。



有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),

所以

让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。


任何一个对象都有锁,wait(),notify(),notifyall()方法

当前线程必须拥有这个对象的monitor(即锁)

,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。


对象的控制权就是对象的锁。

注:拥有这个对象锁的线程才能运行运行其中synchronized方法,也即拥有这个对象控制权的线程才能执行synchrozied方法。进而,拥有了控制权,才能用wait()释放控制权

 


synchronized关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。

当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。

当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。

当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。


     四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。