多线程



一、初期理解

1.进程

    是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

2.线程

    就是进程中的一个独立的控制单元,它在控制着进程的执行,一个进程中至少有一个线程。

3.JVM启动时的线程情况

    JVM启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,称之为主线程。

扩展:其实jvm启动不止一个线程,还有负责垃圾回收机制的线程。

二、多线程

1.方法

    setPriority(int newPriority) 更改线程的优先级。

    start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

    sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

    yield() 暂停当前正在执行的线程对象,并执行其他线程。

2.如何在自定义的代码中,自定义一个线程呢?

    通过对API的查找,java已经提供了对线程这类事物的描述。如:Thread类。

    创建线程的第一种方式:继承Thread类。

步骤:

    (1)定义类继承Thread。

    (2)复写Thread类中的run方法。目的是将自定义代码存储在run方法,让线程运行。

    (3)调用线程的start方法。该方法两个作用:启动线程,调用run方法。

示例:

//定义一个类继承Thread
class Demo extends Thread
{
	public void run()//复写run方法
	{
		for(int x=0; x<60; x++)
			System.out.println("demo run----"+x);
	}
}

class ThreadDemo 
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();//创建好一个线程。
		d.start();//开启线程并执行该线程的run方法。
		//d.run();//此方法仅仅是对象调用方法。而线程创建了,并没有运行。	
		for(int x=0; x<60; x++)
			System.out.println("Hello World!--"+x);
	}
}
     创建线程的第二种方式:实现Runable接口

步骤:

    (1)定义类实现Runnable接口

    (2)覆盖Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。

    (3)通过Thread类建立线程对象。

    (4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

         因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法,就必须明确该run方法所属对象。

    (5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

示例:

class Ticket implements Runnable//定义类实现Runnable接口
{
	private  int tick = 100;
	public void run()//覆盖Runnable接口中的run方法。
	{
		while(true)
		{
			if(tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
			}
		}
	}
}

class  TicketDemo
{
	public static void main(String[] args) 
	{

		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);//通过Thread类建立线程对象。将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
		t1.start();//调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
		t2.start();
	}
}

3.实现方式和继承方式有什么区别呢?

    继承Thread:线程代码存放Thread子类run方法中。

    实现Runnable,线程代码存在接口的子类的run方法。

    实现Runnable方法避免了单继承的局限性,在定义线程时,建立使用实现方式。

4.多线程之间怎样运行?

    因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。

    明确一点,在某一个时刻,只能有一个程序在运行(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。

    这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

5.为什么要覆盖run方法?

    Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。因此要复写run方法。

6.线程对象及名称的获取

    线程都有自己默认的名称:Thread-编号;该编号从0开始。

    static Thread currentThread():获取当前线程对象。

    getName(): 获取线程名称。

    setName或者构造函数:设置线程名称。

三、多线程的安全问题

1.问题存在的原因

    当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与了进来开始执行。导致共享数据的错误。

2.解决方法

    对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

    Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。格式如下:

synchronized(对象)
{
	需要被同步的代码;
}

    对象如同锁。持有锁的线程可以在同步中执行;没有持有锁的线程即使获取cpu的执行权,也进不去。

3.同步代码块

   (1)同步的前提

     [1]必须要有两个或者两个以上的线程。

     [2]必须是多个线程使用同一个锁,因为必须保证同步中只能有一个线程在运行。

   (2)同步代码块的优缺点

    好处:解决了多线程的安全问题。

    弊端:多个线程需要判断锁,较为消耗资源。

4.同步函数

   (1)如何找处多线程中寻在的安全问题

       [1]明确哪些代码是多线程运行代码。

       [2]明确共享数据。

       [3]明确多线程运行代码中哪些语句是操作共享数据的。

    (2)同步函数

      如果整个函数都存在安全问题,可将同步代码块中的synchronized作为修饰词放在函数上,构成同步函数。

    

   (3)同步函数用的是哪一个锁呢?

    函数需要被对象调用,那么函数都有一个所属对象引用,就是this。所以同步函数使用的锁是this。

   (4)静态同步函数用的锁是什么?

    通过验证发现,静态同步函数用的锁不是this,因为静态方法中也不可以定义this。

    静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。该对象的类型是Class。

    静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class

5.死锁

    死锁是一个错误的代码,产生的原因是同步中嵌套同步,它会导致程序一直被锁,等不到运行结果,直至崩溃。开发时一定注意要避免死锁。

示例:

class Ticket implements Runnable{   //创建类实现Runnable接口  
    private int tick = 10000;  
    Object obj = new Object();  
    boolean flag = true;            //标记,初始化为true  
    public void run(){              //复写Runnable的run方法  
        if(flag){                   //如果标记为true,执行同步代码块  
            while(true){  
                synchronized(obj){  //加同步代码块,形成嵌套同步--死锁  
                    show();         //调用同步函数show  
                }  
            }         
        }  
        else                        //否则执行同步函数  
            while(true)  
                show();  
    }  
    public synchronized void show(){//同步函数  
        synchronized(obj){          //同步代码块  
            if(tick>0){  
                try{Thread.sleep(1);}catch(Exception e){}  
                String aa = Thread.currentThread().getName();  
                System.out.println(aa+".."+tick--);  
            }  
        }  
    }     
}  
class DeadLockDemo{  
    public static void main(String[] args){  
        Ticket t = new Ticket();  
        Thread t1 = new Thread(t);  
        Thread t2 = new Thread(t);  
        t1.start();  
        try{Thread.sleep(10);}catch(Exception e){}  
        t.flag = false;  
        t2.start();  
    }  
}  

三、线程间通信--等待唤醒机制

1.定义

     其实就是多个线程在操作同一个资源,但是操作的动作不同。

2.方法

    wait():等待

    notify():唤醒

    notifyAll():唤醒线程池中所有线程

    这三个方法都使用在同步中。因为要对持有监视器(锁)的线程操作,所以要使用在同步中,只有同步才具有锁。

3.为什么这些操作线程的方法要定义Object类中呢?

    因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁。只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以将可以被任意对象调用的方法定义Object类中。 

4.wait()和sleep()的区别

    wait() 释放资源,释放锁。

    sleep() 释放资源,不释放锁。

5.JDK1.5新特性

    JDK1.5中提供了多线程升级解决方案。

    将同步Synchronized替换成现实Lock操作。将Object中的wait,notify notifyAll,替换成Condition对象。该对象可以Lock锁进行获取。

示例:

import java.util.concurrent.locks.*;  
class Resource  
{  
    private String name;  
    private int count = 1;          //编号  
    private boolean flag = false;   //设置标记  
    private Lock lock = new ReentrantLock();  
    private Condition condition_set = lock.newCondition();   
    private Condition condition_out = lock.newCondition();   
    //生产      
    public void set(String name)throws InterruptedException {  
        lock.lock();//获取锁  
        try{  
            while(flag)  
                condition_set.await();  
            this.name  = name+"--"+count++;  
            String aa = Thread.currentThread().getName();  
            System.out.println(aa+"..生产者.."+this.name);  
            flag = true;  
            condition_out.signalAll();  //唤醒所有消费线程        
        }          
        finally{  
            lock.unlock();//一定要释放锁  
        }  
    }  
    //消费  
    public void out()throws InterruptedException{  
        lock.lock();//获取锁  
        try{  
            while(!flag)  
                condition_out.await();  
            String aa = Thread.currentThread().getName();  
            System.out.println(aa+".......消费者.."+this.name);  
            flag = false;  
            condition_set.signalAll();  //唤醒所有生产线程  
        }  
        finally{  
            lock.unlock();//一定要释放锁  
        }  
    }  
}  
  
//生产者类实现Runnable接口  
class Producer implements Runnable{  
    private Resource res;  
    Producer(Resource res){  
        this.res = res;  
    }  
    public void run(){  
        while(true){  
            try{  
                res.set("+商品+");  
            }catch (InterruptedException e){}  
        }  
    }  
}  
//消费者实现Runnable接口  
class Consumer implements Runnable{  
    private Resource res;  
    Consumer(Resource res){  
        this.res = res;  
    }  
    public void run(){  
        while(true){  
            try{  
                res.out();  
            }catch (InterruptedException e){}  
        }  
    }  
}  
class  ProducerConsumerDemo2{  
    public static void main(String[] args){  
        Resource r = new Resource();  
        new Thread(new Producer(r)).start();  
        new Thread(new Producer(r)).start();  
        new Thread(new Consumer(r)).start();  
        new Thread(new Consumer(r)).start();  
    }  
}  

四、线程扩展

1.停止线程

    stop方法已经过时。

 (1)如何停止线程?

    只有一种,run方法结束。

    开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

 (2)特殊情况:

    当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。

    当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

    Thread类提供该方法 interrupt()来结束线程

2.守护线程

    守护线程 setDaemon

    特点:开启后和前台线程共同争夺CPU的执行权

    public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

    该方法必须在启动线程前调用。

    首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。

3.join方法

   当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完,A才会执行。

   join可以用来临时加入线程执行。

4.优先级

   yield() 暂停当前正在执行的线程对象,并执行其他线程。
   setPriority(int newPriority) 更改线程的优先级。newPriority用1-10表示
java已经给出了常用的三个等级:
    MAX_PRIORITY 线程可以具有的最高优先级。最高是10
    MIN_PRIORITY 线程可以具有的最低优先级。最低是1
    NORM_PRIORITY 分配给线程的默认优先级。默认是5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值