黑马程序员_多线程编程

一、线程的状态

注意:

.(1)、阻塞状态:具备运行资格,但是没有执行权,必须等到cpu的执行权,才转到运行状态,。

(2)、冻结状态:放弃了cpu的执行资格,cpu不会将执行权分配给这个状态下的线程,必须被唤醒后,此线程要先转换到阻塞状态,等待cpu的执行权后,才有机会被执行到。

二、线程的创建

1、第一种方法:将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例         

(1)、定义类继承 Thread          

(2)、复写 Thread 类中的 run 方法            

  目的:将自定义代码存储在 run 方法周中,让线程运行        

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

2、 第二种方法: 实现Runnable接口

     (1)、定义类实现 Runnable接口
       (2)、覆盖 Runnable 接口中的 run 方法,将线程要运行的代码存放在该 run 方法中
       (3)、通过 Thread 类建立线程对象
       (4)、将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数
          [1]、为什么要将 Runnable 接口的子类对象传递给 Thread 的构造函数,因为自定义的 run 方法所属的对象是 Runnable 接口的子类对象,所以要让线程去指定对象的 run 方法
       (5)、调用 Thread 类的 start 方法开启线程并调用 Runnable 接口子类的 run 方法

3、两种方式的区别:

(1)、实现方式好处:避免了单继承的局限性
       (2)、在定义线程时:建议使用实现方式
       (3)、两种方式区别:
           [1]、继承 Thread:线程代码存放在 Thread 子类 run 方法中
           [2]、实现 Runnable:线程代码存放在接口的子类的 run 方法中

4、示例:

package Demo;
public class CThreadDemo {

	/**
	 * @线程的创建
	 * 1、利用两种方式获取当前线程名称
	 * 2、利用父类构造方法完成的自定义名称,实现自定义线程名称
	 */
	public static void main(String[] args) {
		/*
		 * 方法一、用set方法自定义线程名称
		 */
		Person p=new Person();
		p.setName("One");
		// 方法二、父类完成了自定义线程名称的方法,调用即可
		//Person p=new Person("Thread_one");
		p.start();
	}
}
class Person extends Thread{
	private String name;
	/*
	 方法二、父类完成了自定义线程名称的方法,调用即可
	 */
	/*Person(String name){
		super(name);
	}*/
	public void run(){
		System.out.println(this.getName());//利用this关键字获取当前线程名称
		System.out.println(Thread.currentThread().getName());//利用currentThread()方法获取当前线程名称
	}
}

三、获取线程对象及名称

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

   2、currentThread():返回对当前正在执行的线程对象的引用。

3、利用this关键字获取当前线程引用

   4、getName():获取线程名称

   5、setName() 或者 构造函数:设置线程的名称

6、使用java自带的自定义线程名称方法


四、多线程安全问题-同步代码块和同步函数

1、对多条操作共享数据的语句,只能让一个线程都执行完,再执行过程中,不可以参与执行。
   2、java对于多线程的安全,提供了专业解决方式:
       (1)、就是同步代码块
       (2)、synchronized(){}

3、同步代码块:

(1)写同步的前提:

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

          [2]、必须是多个线程使用同一个锁。

(2)同步的好处:解决了多线程的安全问题;

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

(4)、同步代码块:synchronized(对象的引用),用在只让一个线程执行的多线程中

synchronized (对象的引用)

                          {

                                 需要被同步的代码。

                          }

      持有锁的线程可以在同步代码块中执行,没有持有锁的线程即使获取cpu的执行权,也进不去。要等到持有锁的线程释放锁的所有权后,才可以让别的线程执行被同步的代码!

4、同步函数:同步函数使用的锁是this对象;当同步函数被静态static 修饰时,锁不再是this而是该方法所在类的字节码对象,即类 .class。因为静态进内存的时候没有对象,只有一个字节码对 象,静态中不能有this对象。

5、示例:

public class Thread_Ticket {
	/**
 * @简单的多窗口卖票程序
 */
	public static void main(String[] args) {
		Ticket t=new Ticket();
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		Thread t3=new Thread(t);
		Thread t4=new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Ticket implements Runnable{
	private int ticket=100;
	Object obj=new Object();
	@Override
	public void run() {
		while(true){
			synchronized(obj){//同步代码块
				if(ticket>0){
					System.out.println(Thread.currentThread().getName()+"----"+ticket--);
				}
			}	
		}
	}	
}


五、多线程的锁锁对应的对象

1、同步函数使用的锁是this对象

2、当同步函数被静态static修饰时,锁不再是this而是该方法所在类的字节码对象,即类.class

六、多线程-单利模式

1、饿汉式饿汉式不会存在安全隐患,并且代码简单,开发常用。

class Single
{
    private static final Single s = new Single();
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }
}
2、懒汉式:

class Single
{
    private static Single s = null;
    private Single(){}
    public static  Single getInstance()
    {
        if(s==null)
            s = new Single();
        return s;
    }
}

多线程在执行上述语句时,由于cpu切换执行的特点,有可能某个线程执行了if(s==null)之后被挂起,这时cpu切换到另一个线程执行,最终会创建两个对象。从而不能实现最初目的。

懒汉式优化1:

class Single
{
    private static Single s = null;
    private Single(){}
    public static  Single getInstance()
    {
            synchronized(Single.class)
            {
                if(s==null)
                    s = new Single();
            }
        return s;
    }
}

优化后的懒汉式,虽然解决了多线程的安全问题,但是每一个线程执行时都要判断“锁”,这是很浪费时间的。

懒汉式优化2:

class Single{  
	private static Single s = null; 
	private Single(){};  
	public static  Single getInstance() {       
		if(s==null)     {           
			synchronized(Single.class) {               
				if(s==null)                 
					s = new Single();           
				}       
			}       
		return s;   
		}
	}

这时 懒汉式不再需要多次判断“锁”,而是改为判断一条语句(s==null), 整个过程只需要判断一次锁,一旦A线程建立对象后,s便不等于null,此后的线程只需要判断if语句就知道对象已建立,便不再进入内部判断“锁”,节省了资源和时间。



七、多线程-等待唤醒机制

1、显式锁机制和等待唤醒机制:

在JDK 1.5中,提供了改进synchronized的升级解决方案。将同步synchronized替换为显式的Lock操作,将Object中的wait,notify,notifyAll替换成Condition对象,该对象可对Lock锁进行获取。这就实现了本方唤醒对方的操作。在这里说明几点:

1)、对于wait,notify和notifyAll这些方法都是用在同步中,也就是等待唤醒机制,这是因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

2)、而这些方法都定义在Object中,是因为这些方法操作同步中的线程时,都必须表示自己所操作的线程的锁,就是说,等待和唤醒的必须是同一把锁。不可对不同锁中的线程进行唤醒。所以这就使得程序是不良的,因此,通过对锁机制的改良,使得程序得到优化。

3)、等待唤醒机制中,等待的线程处于冻结状态,是被放在线程池中,线程池中的线程已经放弃了执行资格,需要被唤醒后,才有被执行的资格。

2、对于上面的程序,有两点要说明:

1)、为何定义while判断标记:

原因是让被唤醒的线程再判断一次。避免未经判断,线程不知是否应该执行,就执行本方的上一个已经执行的语句。如果用if,消费者在等着,两个生成着一起判断完flag后,cpu切换到其中一个如t1,另一个t3在wait,当t1唤醒冻结中的一个,是t3(因为它先被冻结的,就会先被唤醒),所以t3未经判断,又生产了一个。而没消费。

2)这里使用的是signal方法,而不是signalAll方法。是因为通过Condition的两个对象,分别唤醒对方,这就体现了Lock锁机制的灵活性。可以通过Contidition对象调用Lock接口中的方法,就可以保证多线程间通信的流畅性了。


八、多线程间的通信:

/* 
线程间通信: 
生产者消费者  多个 
 
*/  
import java.util.concurrent.locks.*;  
  
class ProducerConsumerDemo{  
    public static void main(String[] args){  
        Resouse r = new Resouse();  
        Producer p = new Producer(r);  
        Consumer c = new Consumer(r);  
        Thread t1 = new Thread(p);  
        Thread t2 = new Thread(c);  
        Thread t3 = new Thread(p);  
        Thread t4 = new Thread(c);  
        t1.start();  
        t2.start();  
        t3.start();  
        t4.start();  
    }  
}  
  
class Resouse{  
    private String name;  
    private int count = 1;  
    private boolean flag =  false;   
    private Lock lock = new ReentrantLock();  
    private Condition condition_P = lock.newCondition();  
    private Condition condition_C = lock.newCondition();  
//要唤醒全部,否则都可能处于冻结状态,那么程序就会停止。这和死锁有区别的。  
    public void set(String name)throws InterruptedException{  
        lock.lock();  
        try{  
            while(flag)//循环判断,防止都冻结状态  
                condition_P.await();  
            this.name = name + "--" + count++;  
            System.out.println(Thread.currentThread().getName() + "..生成者--" + this.name);  
            flag = true;  
            condition_C.signal();  
        }finally{  
            lock.unlock();//释放锁的机制一定要执行  
        }         
    }  
    public void out()throws InterruptedException{  
        lock.lock();  
        try{  
            while(!flag)//循环判断,防止都冻结状态  
                condition_C.await();  
            System.out.println(Thread.currentThread().getName() + "..消费者." + this.name);  
            flag = false;  
            condition_P.signal();//唤醒全部  
        }finally{  
            lock.unlock();  
        }  
    }  
}  
  
class Producer implements Runnable{  
    private Resouse r;  
    Producer(Resouse r){  
        this.r = r;  
    }  
    public void run(){  
        while(true){  
            try{  
                r.set("--商品--");  
            }catch (InterruptedException e){}  
        }  
    }  
}  
  
class Consumer implements Runnable{  
    private Resouse r;  
    Consumer(Resouse r){  
        this.r = r;  
    }  
    public void run(){  
        while(true){  
            try{  
                r.out();  
            }catch (InterruptedException e){}  
        }  
    }  
}  

九、停止线程

  1、stop方法已经过时。多线程的运行代码通常是循环结构。只要控制循环,就可以让run方法结束,也就是线程结束。

    · 2、 当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

3、示例:

class Demo{  
    public static void main(String args[]){  
        StopThread st = new StopThread();  
        Thread t1 = new Thread(st);  
        Thread t2 = new Thread(st);  
        t1.start();  
        t2.start();  
        int num = 0;  
        while(true){  
            if(num++ == 60){  
                st.changeFlag();  
                t1.interrupt();  
                t2.interrupt();  
                break;  
            }  
            System.out.println(Thread.currentThread().getName()+num);  
        }  
    }  
}  
class StopThread implements Runnable{  
    private boolean flag = true;  
    public void run(){  
        while(flag){  
            System.out.println(Thread.currentThread().getName()+"....run");  
        }  
    }  
    public void changeFlag(){  
        flag = false;  
    }  
}  

十、守护线程

  注意: 如果主线程结束,则这个线程自动结束

十一、jion方法

  作用:抢夺主线程行权,注意,只抢夺主线程
    当a线程执行到了b线程的 .join() 方法时,a就会等待,等b线程都执行完,a才会执行

十二、优先级&yield方法

1、在Thread中,存在着1~10这十个执行级别,最高的是 MAX_PRIORITY 为10,最低是 MIN_PRIORITY 为1,默认优先级是 NORM_PRIORITY 为5

  2、setPriority  设置优先级

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值