多线程

1 多线程

1.1 进程和线程

进程:是正在运行的程序

是系统进行资源分配和调用的独立单位

每一个进程都有它自己的内存空间和系统资源

线程:是进程中的单个顺序控制流,是一条执行路径

单线程:一个进程如果只有一条执行路径,则称为单线程程序多线程:一个进程如果有多条执行路径,则称为多线程程序

1.2 多线程的实现方式

方法一:

1)定义一个MyThread类继承Thread类

(2)在MyTread类中重写run方法

(3)创建MyThread类对象

(4)调用start方法启动线程

说明:

为什么要重写run方法:

因为run是用来封装被线程执行的代码

run和start方法的区别

run封装线程执行的代码,直接调用,相当于普通方法调用

start启动线程然后由JVM调用此线程的run方法

代码演示:

public class MyThread extends Thread{

  @override

public void run(){

}

}

public ………main(){

 

MyThread mt1=new MyThread();

MyThread mt2=new MyThread();

mt1.run();

mt1.start();

mt2.start();

}

 

1.3设置和获取线程名称

方法介绍

void setName(String name)将此线程的名称更改为等于参数name

String getName()返回此线程的名称

Thread currentThread()返回对当前正在执行的线程对象的引用

代码演示      

 

public class MyThread extends Thread {  

  public MyThread() {}    

public MyThread(String name) {        super(name);   }   

 @Override  

 public void run() {        System.out.println(getName();}

public class MyThreadDemo {    

public static void main(String[] args) {    

   MyThread my1 = new MyThread();      

  MyThread my2 = new MyThread();   

   my1.setName("高铁");        

my2.setName("飞机");         

   MyThread my1 = new MyThread("高铁");      

 MyThread my2 = new MyThread("飞机");

     System.out.println(Thread.currentThread().getName());

}}

1.4线程优先级

1.4.1线程调度

(1)两种调度方式:

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片.

抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

(2)Java使用的是抢占式调度模型

(3)随机性:

假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

1.4.2 线程优先级相关方法

final int getPriority() 返回此线程的优先级

final void setPriority(int newPriority)更改此线程的优先级,默认5,线程范围 1-10。

 

 

1.5线程控制

 相关方法

sleep(long millis):使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁

wait():使当前线程暂停执行并释放对象锁标志

notify()唤醒等待的线程

 join(): 直观解释就是”等待调用此方法的线程死去,再执行其他线程“。说得更直观一点就是:在线程 t1 内部调用t2.join(), 那么 t1 将会等待 t2 执行完毕后才会继续执行

 

Wait和sleep方法不同点 : 
1.每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。  
sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。  
2.wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用  
3.sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 

4.sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

5.wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

 

join方法的作用,代码演示

先写一个子线程

public class MyThread extends Thread{

     public MyThread(String name){

         super(name);

    }

    

    @Override

    public void run(){

        for(int i=1;i<=2;i++){

            System.out.println(this.getName() + ":" + i);

        }

    }

}

 

 

 再写主线程测试类

public class TestThread {

     public static void main(String[] args) throws InterruptedException

            MyThread mt = new MyThread("子线程");

        mt.start();

        for (int i = 1; i <= 2; i++) {

        System.out.println("主线程:" + i);

     }

     }

 

}

 

 

 执行main方法,打印结果很明显,主线程和子线程会交替打印

主线程:1

子线程:1

主线程:2

子线程:2

 

 

 然后,我们在主线程打印之前,加一句mt.join(),会怎样呢?

public static void main(String[] args) throws InterruptedException {

           

            MyThread mt = new MyThread("子线程");

        mt.start();

       

        mt.join();

       

        for (int i = 0; i <= 2; i++) {

        System.out.println("主线程:" + i);

            }

     }

 

 执行main方法,打印结果如下:

子线程:1

子线程:2

主线程:1

主线程:2

————————————————

结论:原来主线程和子线程是并行的关系,但是一旦使用了join()方法,就会变成串行的关系;当主线程调用子线程的join()方法时,意味着必须等子线程执行完毕之后,主线程才会开始执行。

 

 

1.6实现多线程方式二:实现Runnable接口

1.6.1 实现步骤

(1)定义一个类MyRunnable实现Runnable接口

(2)在MyRunnable类中重写run()方法

(3)创建MyRunnable类的对象创建Thread类的对象,把MyRunnable对象作为构造方法的参数

(4)启动线程

1.6.2代码演示:

public class MyRunnable implements Runnable {  

 @Override    

public void run() {            

System.out.println(Thread.currentThread().getName());       }}

public class MyRunnableDemo {  

  public static void main(String[] args) {    

    //创建MyRunnable类的对象        

MyRunnable my = new MyRunnable();       

  //创建Thread类的对象,把MyRunnable对象作为构造方法的参数  

       Thread t1 = new Thread(my);//    

  Thread t2 = new Thread(my);    

//Thread(Runnable target, String name)    

   Thread t1 = new Thread(my,"高铁");        

Thread t2 = new Thread(my,"飞机");         

//启动线程        

t1.start();        t2.start();

}}

 

1.6.3   多线程的实现方案有两种:

继承Thread类;

实现Runnable接口.

相比继承Thread类,实现Runnable接口的好处:

避免了Java单继承的局限性适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

 

 

1.7 线程的生命周期

 

2 线程同步

2.1 Synchronized

卖票案例

某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票。

public class SellTicket implements Runnable{

private int tickets=100;

//三个窗口共用一把锁

private Object obj=new Object();

@override

public void run(){

// 没票了,也一直有人问

     whiletrue{

// Synchronizednew Object

错误每个线程创建一把锁

synchronizedobj{

  iftickets>0{

       try{

        Thread.sleep(100);//出票时间

}catch()

   System…(Thread.current.getname()+”正在卖第”+tickets+”张票”);

tickets--

}}}}

  Public ….main(){

   SellTicket st=new SellTicket();

Thread t1=new Thread();

Thread t2=new Thread();

Thread t3=new Thread();

t1.start(); t2.start(); t3.start();

}

 

同步代码块格式:synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端好处:解决了多线程的数据安全问题弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

同步方法:

格式:把synchronized加到方法上

修饰符staticsynchronized返回类型方法名

同步方法的锁对象是this

同步静态方法的锁对象是类名.class

线程安全的类:StringBuffer/Vector/Hashtable

线程不安全的类:StringBuilder/ArrayList/HashMapb

 

2.2 lock

lock锁提供了获取锁和释放锁的方法。

Lock锁提供了比使用synchronized方法可以获得更广泛的锁操作对象

代码演示:

public class SellTicket implements Runnable

{  private int tickets=100

private Lock lock=new ReenTrantLock();

@override

public void run(){

while(true){

try{

 lock.lock();

if(tickets>0){

try{Thread.sleep(100);}catch{}

   

System……(“ 正卖几张票”);

Tickets--;

}

}finally{lock.unlock();}

}

}

 

}

 

上一篇:继承 Thread类创建线程


下一篇:20.线程池