11-01-多线程概述
l 进程定义:进程是一个正在执行中的程序。每个进程都有一个执行顺序,该执行顺序做
控制单元。
l 线程定义:线程是进程中独立的一个控制单元。线程在控制着进程的执行。
一个进程中至少有一个线程。
² 多线程存在的意义:多线程的意义在于一个应用程序的多个逻辑单元可以并发地执行。
但是多线程并不意味着多个用户进程在执行,操作系统也不把每个线程作为独立的进程来分配独立的系统资源。进程可以创建其子进程,子进程与父进程拥有不同的可执行代码和数据内存空间。而在用于代表应用程序的进程中多个线程共享数据内存空间,但保持每个线程拥有独立的执行堆栈和程序执行上下文。
11-02-创建多线程
l 创建线程有两种方式:继承Thread类和实现Runnable接口。
第一种方式:继承Thread类。
步骤:
1.定义继承 Thread类。
2.复写Thread类中的run方法。
3.调用线程的start方法。
代码示例:
class Demo extends Thread//继承1.Thread类
{
public voidrun()//2.复写run方法
{
for(int i=0;i<60;i++)
{
System.out.println(“Demo run--”+i);
}
}
}
public class ThreadDemo
{
public staticvoid main(String[] args)
{
Demo d = new Demo();
d.start();//3.开启线程并执行run方法
//d.run();//仅仅是对象调用方法,而线程创建了并没有执行。
}
for(inti=0;i<60;i++)
{
System.out.println(“main--”+i);
}
}
多次运行后每一次运行结果都不一样。
原因:多个线程都获取到cpu的执行权,cpu执行到谁,谁就运行。在某一时刻,只有一个程序在运行(多核除外),cpu做着快速的切换,已达到看上去是同时运行的效果,我们可以形象地把多线程的运行行为看成是在互相抢夺cpu的执行权。
² 结论:多线程的一个特点:随机性。谁抢到谁就执行,至于执行多长时间,取决于cpu。
11-03-线程run和start特点
l 线程run和start的特点
run方法的特点:用于储存线程要运行的代码。
start方法的特点:启动线程并执行该线程的run方法(在启动线程后不一定竞争到cpu的执行权)。
11-08-创建线程-实现Runnable接口
l 创建线程的第二种方法:实现Runnable接口
步骤:
1.定义类实现Runnable接口。
2.覆盖Runnable接口中的run方法(将线程要运行的代码存放在run方法中)。
3.建立Runnable接口的子类对象。
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5.调用Thread类的start方法开启并调用Runnable接口。
代码示例:
class Ticket implements Runnable//1.实现Runnable接口
{
private int tick= 100;
public voidrun()//2.重写run方法
{
while(true)
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+”...sale”+tick--);
}
}
}
}
class TicketDemo
{
public staticvoid main(String[] args)
{
Ticket t = new Ticket();//3.创建Runnable接口的子类对象
//4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
thread t1 = new Thread(t);
thread t2 = new Thread(t);
thread t3 = new Thread(t);
thread t4 = new Thread(t);
//5.调用Thread类的start方法开启线程并调用Runnable接口子类中的run方法
t1.start();
t2.start();
t3start();
t4.start();
}
}
l 实现Runnable接口的好处:避免了单继承的局限性,适合于资源共享。
11-09-多线程的安全问题
通过对11-08代码示例运行结果的分析,出现了打印出0,-1,-2等错票。
11-08示例代码运行结果分析:
(1)运行run方法有四个线程t1、t2、t3、t4。首先t1获取cpu执行权。
(2)当tick = 1时,刚判断完,t1卧倒(t1具备执行资格,但是此刻cpu执行权被其他线程抢走了或是切换到其他程序去了),t1并没有执行if语句后面代码块中的语句。接着t2获得了cpu执行权,这时的tick = 1,刚判断完,t2也卧倒。t3、t4如同上述情况也处于卧倒状态。
(3)某一时刻,t1获取到了cpu执行权,执行if语句代码块,tick--,tick = 0;
(4)按理说tick = 0,if语句代码块就不在执行,但是t2、t3、t4已经过判断,有执行if语句代码块的资格。t1运行完毕,t2、t3、t4不在经过if语句判断,t2获取cpu执行权,执行if语句代码块,打印0号票,tick--;t2运行完毕,t3获取cpu执行权,打印-1号票,tick--;同理,t4运行打印-2号票。
l 问题的原因?
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程就参与进来执行,导致共享数据的错误。
l 解决方法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与进来。
l synchronized
同步的前提:
1.必须要有多个(两个或两个以上)线程。
2.必须是多个线程使用同一个锁。(注意:不同线程使用不同锁会出现线程安全问题)
3.必须保证同步中只有一个线程在运行。
synchronized的两种表现形式:
1.同步代码块。(对象锁的运用要准确,要不然会出现线程安全问题)
2.同步函数。(经过视频展示,同步函数要运用到位,要不然达不到多线程的目的,因为同步函数一次只能进入一个线程)
同步代码块示例:
synchronized(对象)
{
需要同步的代码块;
}
同步函数示例:
public synchronized int add(int a){
//需要同步的函数代码
}
² synchronized的利与弊:
利:解决多线程的安全问题。
弊:多线程需要判断锁,较为消耗资源。
² 小收获:继承接口的run方法,调用sleep()等方法时只能try...catch,不能抛异常。因为接口中的run方法未抛异常。
11-12-多线程-同步函数的锁是this
l 同步函数用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。所以同步函数使用的锁是this。
如果一个类里面中既有同步函数又有同步代码块,示例如下:
class Ticket implements Runnable
{
private int tick= 100;
object obj = newObject();
boolean flag =true;
public voidrun()
{
if(flag)
{
while(true)
{
//如果synchronized的参数是obj而不是this,在两个或两个以上的线程执行过
//程中就会出现线程安全问题:打印0号票。
synchronized(obj)//<1>
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+”...code”+tick--);
}
}
}
}
else
while(true)
{
show();//或是this.show()
}
}
publicsynchronized void show()//<2>
{
if(tick>0)
{
System.out.println(Thread.currentThread().getName()+”...show...”+tick--);
}
}
public staticvoid main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t1 = new Thread(t);
t1.start();
try{Thread.sleep()10;}catch(Exception e){}
t2.start();
t.flag = false;
t2.start();
}
}
运行结果表明:在有非静态的同步函数和同步代码块的类中,只有同步代码块中的对象锁为this时,才不会打印出错票的情况。
11-13-多线程-静态同步函数的锁是class对象
l 如果同步函数被静态修饰符修饰后,使用的锁是什么的呢?
通过验证,发现不是this,因为静态方法中不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。就是
类名.class,该对象的类型是Class。
代码示例:
如果将11-12示例代码中的<2>处改为:public static synchronized voidshow(){...}
那么<1>处应该改为:synchronized(Ticket.class){...}
要不然对象锁不一致会出现线程安全问题:打印出0号票。
11-15-多线程-死锁
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 导致死锁的根源在于不适当地运用“synchronized”关键字来管理线程对特定对象的访问,使同步中嵌套同步。l 死锁代码举例:
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
while(true){
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"...if locka ");
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"..if lockb");
}
}
}
}
else{
while(true){
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"..else lockb");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+".....else locka");
}
}
}
}
}
}
class MyLock{
static Objectlocka =new Object();
static Objectlockb =new Object();
}
class DeadLockTest{
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
总结:采用多线程能充分利用CPU资源,提高代码的运行效率。但是在采用多线程的同时必须解决好线程的安全问题:代码同步,避免死锁的出现。