多线程总结上篇
1、进程和线程的区别:
一个进程是多个线程组成的,进程是操作系统管理内存最小单位,线程使用内存,从进程申请。2、线程创建的两种方式
方式一:用Thread类创建线程声明为一个 Thread 类的子类,并覆盖 run() 方法
class MyThread extends Thread {
public void run( ) {
/* 覆盖该方法*/
}
}
当使用继承创建线程,这样启动线程:
new MyThread().start();</span>
声明为一个实现 Runnable 接口的类,并实现 run() 方法
<span style="font-size:14px;">class MyThread implements Runnable{
public void run() {
/* 实现该方法*/
}
}</span>
当使用实现接口创建线程,这样启动线程:
new Thread(new MyThread()).start();
3、两种实现方式的比较:
使用Runnable接口可以将代码和数据分开,形成清晰的模型,还可以从其他类继承,保持程序风格的一致性;
直接继承Thread类,不能再从其他类继承 ,编写简单,可以直接操纵线程 。
在实际编程中,我们优先使用Runnable接口创建多线程,因为JAVA是单继承的,继承了Thread,就不能继承其他的类了
备注:可以用Thread.currentThread().getName()获取线程的名字。
4、线程的4种状态
创建(new Thread().start())
执行状态 (线程run方法正在运行)
阻塞状态(run方法在执行一段时间后暂停执行)
线程死亡(run方法执行结束、发生异常)
执行状态 (线程run方法正在运行)
阻塞状态(run方法在执行一段时间后暂停执行)
线程死亡(run方法执行结束、发生异常)
那么这四种状态,是如何进行切换的呢?如右边图:
* 新建 --- 运行 (获得cpu的使用权)
* 运行 --- 阻塞 (Thread.sleep join、使用同步锁 wait IO读写、网络传输)
* 阻塞 --- 运行 结束阻塞,重新获得cpu使用权
* 运行 --- 阻塞 (Thread.sleep join、使用同步锁 wait IO读写、网络传输)
* 阻塞 --- 运行 结束阻塞,重新获得cpu使用权
这里简要概述,下一篇详细说明。。。。
关于图中部分方法的解释:
sleep 使当前线程睡眠一段时间,睡眠过程中,不释放锁资源
join 等待目标线程执行结束后,当前线程才能继续执行
wait 当你获得一个同步锁后,选择在锁上面监视器进行等待,等待必须由别人进行
关于图中部分方法的解释:
sleep 使当前线程睡眠一段时间,睡眠过程中,不释放锁资源
join 等待目标线程执行结束后,当前线程才能继续执行
wait 当你获得一个同步锁后,选择在锁上面监视器进行等待,等待必须由别人进行
唤醒 notify notifyAll
5、线程锁
加锁目的:保证一段程序同一时间只能由一个线程进行执行,阻止两个线程同时执行一 段代码 ,java中每个对象都可以作为锁 ,锁的本质,就是锁定一块内存地址。经典案例:卖火车票
情景描述:票源只有一个,可以有多个窗口进行卖票,保证无错票产生
思路:因为保证票源只有一个所以可以考虑用单例设计模式,可以循环产生多个线程模拟多个窗口,并且保证线程同步。
代码如下:
class Ticket{
private static Ticket ticket=null;
private int totalTicket=100;//总票数
private int hasSaledTicket=0;//已卖的票数
//构造函数私有化
private Ticket(){
}
public static Ticket getTicketIntance(){
if(ticket==null){
synchronized (Ticket.class) {
if(ticket==null){
ticket=new Ticket();
}
}
}
return ticket;
}
public synchronized void sale(){
if(totalTicket<=0){
throw new RuntimeException("票已经卖完了");
}
++hasSaledTicket;//卖了一张票
System.out.println("打印一张票"+hasSaledTicket);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
--totalTicket;//总票数减一
System.out.println("剩余票数"+totalTicket);
}
}
// 准备窗口
class SaleTicketWindow implements Runnable {
private Ticket ticket;
//将票源传入
SaleTicketWindow(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
try {
ticket.sale();// 卖票
} catch (RuntimeException e) {
break;// 捕获到异常,说明票卖完,跳出循环
}
}
}
}
public class SaleTicketSystem {
//单例设计模式获取对象
private static Ticket mTicket=Ticket.getTicketIntance();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
SaleTicketWindow window=new SaleTicketWindow(mTicket);
Thread t=new Thread(window);
t.start();//启动线程
}
}
}
在多窗口卖票系统中:
这个程序运行时正常的,但是如果不加synchronized关键字修饰Ticket的sale方法,运行程序会发现打印出诸如-1,-2等错票。 这就是多线程的运行出现了安全 问题。
这个程序运行时正常的,但是如果不加synchronized关键字修饰Ticket的sale方法,运行程序会发现打印出诸如-1,-2等错票。 这就是多线程的运行出现了安全 问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分(CPU就切换到另外的线程去),还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式,有两种解决方式,同步代码块和同步函数,这两种方式可以保证同步中只能有一个线程在运行,下面分别对 两种实现方式进行介绍 。
A、同步代码块 ,用synchronized关键字来进行定义。
同步代码块格式 :
synchronized(唯一对象){
需要被同步的代码;
}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁
同步的前提:
a、必须要有两个或者两个以上的线程。
b、必须是多个线程使用同一个锁。
从程序上看就解决了线程的同步问题,但是问题就来了,同步的锁是什么呢?
锁可以是任意对象,我们创建了一个对象,结果解决了问题。
synchronized(唯一对象){
需要被同步的代码;
}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁
同步的前提:
a、必须要有两个或者两个以上的线程。
b、必须是多个线程使用同一个锁。
从程序上看就解决了线程的同步问题,但是问题就来了,同步的锁是什么呢?
锁可以是任意对象,我们创建了一个对象,结果解决了问题。
直观分析:
由于同步代码块使用的锁是任意对象,很直观就可以想到函数是需要被对象调用才执行的。 那么函数都有一个所属对象引用。就是this。
但这个对象是怎么传递进来的呢?
原来是调用函数的时候,函数内持有一个函数对象的引用。
那我们可不可以把同步定义在函数上?
答案是可以的,结果就有了同步函数。
B、同步函数:
所谓的同步函数就是在函数的返回值前面加一个synchronized关键字就是同步函数了。使用同步函数注意事项:
一定要明确哪个代码是需要进行同步,如果同步函数中的代码都是需要同步的,就可以使用同步函数。 如果同步函数被静态修饰后,使用的锁是什么呢? 因为静态方法中也不可以定义this。所以静态同步函数的锁不可能是this但是这个对象又是谁呢? 静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。 类名.class 该对象的类型是Class , 经过验证,静态同步函数的锁是 类名.class 。
6 、多线程是否会加快程序的执行效率呢?
答:不会,机器的速度是一定的,CPU在多个线程之间来回切换需开销。故速度上可能还会减慢的。
今天博客写到这,接下来下一篇的博客,会写关于线程剩余的知识总结,come on!