java 多线程详解

关于线程的概念

1、程序一般是指一段静态代码,静态对象;进程是程序的一次执行过程或正在运行的程序动态的的过程,有它的产生、存在和消亡的过程;线程是指一个程序内部的一条执行路径,也就是说每一个任务就代表一个线程。进程与线程相互联系然而又有区别,从本质上来说每个进程都有自己的一整套变量,而线程间可以共享变量,共享变量使得线程之间的通信比进行之间通信更有效、更容易。
2、并不是每种情况都需要多线程,当程序需要同时执行两个或多个任务;当程序需要实现一些等待任务,如用户的输入,文件读写,网络操作,搜索;当程序需要运行一些后台程序。在这些情况下我们需要使用多线程。
3、线程的生命周期:每个线程都有新建、就绪、运行、阻塞、死亡这五种状态


创建线程的两种方式

1、继承Thread类:
a.创建一个类继承Thread;b.重写run()方法,将要执行的业务逻辑代码放在run()方法体中;c.在主类中创建子线程类的实例;d.调用线程方法start(),将此线程变为就绪状态。
package com.thread;
public class TestThread {
	public static void main(String [] args){
	       //3、创建一个子类对象
		SubThread st=new SubThread();
		//4、调用线程的start()方法,开始启动线程;调用相应的run()方法
		st.start();
		//这个是主线程的方法 
		for(int i=1;i<=100;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
			
		}
	}
}
//1、创建一个继承Thread的子类
class SubThread extends Thread{
	//2、重写run()方法
	@Override
	public void run() {
            for(int i=1;i<=100;i=i+2){
		 System.out.println(Thread.currentThread().getName()+":"+i);
	    }
	}
}
2、实现Runnable接口
a.创建一个类实现Runnable接口;b.重写run()方法,将业务逻辑代码放入在run()方法中;c.创建一个Runbable接口实现类的对象的实例 d.将这个对象作为形参传递给Thread类的构造器中,创建Thread类对象,然后再启动线程start()方法。
package com.thread;
public class ThreadTest {
    public static void main(String[] args) throws Exception{
    	MyRunnable r=new MyRunnable();//c.创建一个Runbable接口实现类的对象的实例 
    	Thread t=new Thread(r);//d.将这个对象作为形参传递给Thread类的构造器中,创建Thread类对象,然后再启动线程start()方法。
    	t.setName("自定义线程:");
    	t.start();
    	System.out.println(Thread.currentThread().getName());
	}
}
class MyRunnable implements Runnable{//a.创建一个类实现Runnable接口
	@Override
	public void run() {//b.重写run()方法,将业务逻辑代码放入在run()方法中
           for(int i=0;i<10;i++){
        	   
    	   System.out.println(Thread.currentThread().getName()+i);
       }
	}
}

3、这两种方式创建线程的异同在于a.Thread类其实也是实现Runnable接口;b.实现Runnable接口的方式要优于继承Thread类,首先实现的方式避免java中单继承的缺点,其次如果多个线程操作同一份数据资源,更加适合用实现的方式

线程中Thread类常用的方法

1、start():启动线程并执行相应的run方法
2、Thread.currentThread()   Thread.currentThread().getName()、Thread.currentThread().setName("设置的name")           获取当前线程/获取当前线程的名字/设置当前线程的名字
3、sleep(long time):显式的让线程休眠,其中time以毫秒为单位
4、yield():让调用这个方法的线程释放当前cpu的执行权利
5、线程通信的方法:wait():另当前线程挂起并放弃cpu、同步资源使别的线程可以访问并修改共享资源,而当前线程排队等待再一前次对资源的访问;
                                   notify():唤醒正在排队等待同步资源的线程中优先级别最高者结束等待;
                                   notifyAll():唤醒所有正在排队等待资源的线程结束等待。

6、join():在A线程中调用B线程的Join()方法,表示强制执行B线程,这个时候A线程停止执行,直到B执行完毕A才可以继续执行。

线程的安全问题

一、线程安全问题的原因:由于一个线程正在操作共享数据,未执行完毕的情况下,另外一个线程参与进来,导致共享数据在安全上出了问题。如银行的转账存款业务,卖票业务等等。
二、解决线程安全的办法 
1、同步块synchronized:将需要同步的业务逻辑代码放在synchronized(this){ ...}这个同步块中
package com.thread;
public class Demo {
	public static void main(String[] args) {
		sell_Window sell=new sell_Window();
		Thread t1=new Thread(sell);//开启三个窗口同时卖票
		Thread t2=new Thread(sell);
		Thread t3=new Thread(sell);
		t1.start();
		t2.start();
		t3.start();
	}
}
class sell_Window implements Runnable{//实现Runnable接口,把买票的业务逻辑放在run()方法中
    int num=100; 
    @Override
    public void run(){
      while(true){
	 synchronized(this) {//利用同步块来实现线程安全,this表示当前类,我们也可以使用一个对象来做锁对象,其中利用</span><span style="font-size:12px;">Demo.class字节码也是比较好的 
	    if(num>0){
                   try{
			Thread.sleep(100);
     	                System.out.println("窗口 "+Thread.currentThread().getName()+":售第"+(num--)+"张票");
		      }catch (Exception e) {
			e.printStackTrace();
		      }  
	    }else{
		 break;
	    }
        }
     } 
  }
}

2、同步方法 synchronized:将需要同步的方法加上关键字:synchronized如publicsynchronized void set(String name){...}
package com.thread;
public class Demo {
	public static void main(String[] args) {
		sell_Window sell=new sell_Window(new SellTicket());
		Thread t1=new Thread(sell);//开启三个窗口同时卖票
		Thread t2=new Thread(sell);
		Thread t3=new Thread(sell);
		t1.start();
		t2.start();
		t3.start();
	}
}
class SellTicket{//共享数据区,以及买票的方法
	int num=100;
	public synchronized void sell(){//同步方法
		if(num>0){
			System.out.println("窗口 "+Thread.currentThread().getName()+":售第"+(num--)+"张票");
		}else{
			Thread.currentThread().stop();
		}
		
	}
}
class sell_Window implements Runnable{//实现Runnable接口,把买票的业务逻辑放在run()方法中
	SellTicket sellTicket;
	public sell_Window(SellTicket sellTicket) {
		this.sellTicket=sellTicket;
	}
	@Override
	public void run(){
		  while(true){
			  try{
				  Thread.sleep(100);
				  sellTicket.sell();
			  }catch (Exception e) {
				e.printStackTrace();
			}
			 
		  }
	}
}
3、锁Lock,在线程同步中的Lock其实功能上和synchronized有着异曲同工的效果,不过Lock可以提供更加细度的控制,在java中使用Lock锁的方式如下:
package com.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*线程同步中的锁Lock */
public class ThreadLock {
	public static void main(String[] args) {
	     new ThreadLock().init();
	}
	public void init(){
	   final Outputer outputer=new Outputer();
	   new Thread(new Runnable() {//匿名线程
         	@Override
		public void run() {//run()方法
		     while(true){
   			try {
			       Thread.sleep(10);
                              outputer.output("zhangxiaoxiang");//执行的任务
                } catch (InterruptedException e) { 
                            e.printStackTrace(); 
                        }
               }
             }
          }).start();
            new Thread(new Runnable() {
                  @Override
                 public void run() {
                      while(true){
                           try {
                                  Thread.sleep(10);
                                        outputer.output("admin");
                                } catch (InterruptedException e) {
                                       e.printStackTrace();
                               }
                       }
                 }
           }).start();
        }
   static class Outputer{
          Lock lock=new ReentrantLock();//获取锁
          public void output(String name) {
              int len=name.length();
              lock.lock();//加锁
              try{
                  for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                  }
                  System.out.println();
              }catch(Exception e){
                  e.printStackTrace();
              }finally{   
                  lock.unlock();//释放锁
              }
            }
       }
   }

 
 
 在Lock中存在比较有用的两种锁,读写锁(ReadWriteLock),使用这样的锁可以保证读文件的时候可以并发,读写和写文件不可以并发。下面用一个比较实用的例子来说明读写锁的优点。 
 
设计一个简易的cache系统:
package com.thread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*设置缓存系统:
 * 多个人进行读取数据时,可以并行,所以这时候只需要读锁;当有人发现没有数据时,这时候需释放掉
 * 读锁加上一个写锁,在写的时候不能被读取,写完之后就还原成读锁让其它人可以读取
 *  */
public class Thread_CacheDemo {
	private Map<String,Object> cache=new HashMap<String,Object>();
	public static void main(String[] args) {
	}
	//得到数据
	private ReadWriteLock rwl=new ReentrantReadWriteLock();
	public Object getData(String key){//多线程读取时
		rwl.readLock().lock();//读锁
		Object value=null;
		try{
			value=cache.get(key);
			if(value == null){
				rwl.readLock().unlock();//释放读锁
				rwl.writeLock().lock();//写锁
				try {
				    if(value == null){
				    	value="aaaa";//这里实际上是查询数据库
				    }
				} catch (Exception e) {
					
				}finally{
					rwl.writeLock().unlock();//释放写锁
					rwl.readLock().lock();//加上读锁
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			rwl.readLock().unlock();//释放读锁
		}
	    return value;
	}
}

线程间通信

1、线程通信的概念:线程通信是指线程间相互交流的过程,一个线程可以释放自己的cpu权利来给其它被唤醒的线程使用。

2、线程通信的方法,线程通信主要用到了wait(),notif(),notifyAll(),线程通信的一个经典案例就是生产者和消费者调度算法。下面是生产者和消费者调度算法的实现过程:

package com.thread;
/*生产者与消费者的问题
 *     生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品
 *   ,店员一次只能持有固定数量的产品(比如20),如果生产者试图生 产更多的产品,店员会叫生产者停一下,
 *   如果店中有空位放产品了再通知生产者继续生产;如果店中没产品了,店员会告诉消费者等一下,如果店中有了产品再
 *   通知消费者。
 *    1、多线程:生产者,消费者
 *    2、共享数据(资源):产品的数量 
 *    3、需要线程安全
 *    4、需要线程通信:生产者与消费者通信
 * */
public class Produce_consume {
	public static void main(String[] args) {
	Clerk clerk=new Clerk();//共享数据
	Producer p=new Producer(clerk);//生产者
	Consumer c=new Consumer(clerk);//消费者
	Thread t1=new Thread(p);//生产者线程
	Thread t3=new Thread(p);//生产者线程
	Thread t2=new Thread(c);//消费者线程
	t1.setName("生产者1");
	t2.setName("消费者");
	t3.setName("生产者2");
	t1.start();
	t2.start();
	t3.start();
	}
}
//生产者
class Producer implements Runnable{
	Clerk clerk;
	public Producer(Clerk clerk) {
		this.clerk=clerk;
	}
	@Override
	public void run() {
		System.out.println("生产者开始生产产品");
		while(true){
		  try{
			Thread.currentThread().sleep(10);
                       clerk.addProduct();//生产者生产产品
                  } catch (InterruptedException e) {
                       e.printStackTrace(); 
                  }
               }
             }
           }

//消费者
class Consumer implements Runnable{
    Clerk clerk;
    public Consumer(Clerk clerk){
         this.clerk=clerk;
    }
    @Override
    public void run(){
        System.out.println("消费者消费产品"); 
        while(true){ 
            try {  
                   Thread.currentThread().sleep(10);
                   clerk.consumeProduct(); //消费者消费产品
            } catch(InterruptedException e) {
                   e.printStackTrace();
            } 
        }
   }
}
//店员,这是一个共享的数据,供生产者和消费者使用
class Clerk{
    int product;
    public synchronized void addProduct(){
             //生产产品
            if(product>=20){
              try{
               //产品数大于或等于20,暂停生产
               wait();//释放cpu资源
               } catch (InterruptedException e){
                e.printStackTrace();
              }
            }else{
                product++;
                System.out.println(Thread.currentThread().getName()+":生产了第"+product+"个产品");
                notifyAll();//一旦有产品生产,去唤醒消费者进行消费,notifyAll()唤醒其它线程
            }
           }
   public synchronized void consumeProduct(){
    //消费产品
     if(product<=0){ 
           try {
               wait();//产品数少于或等于0,停止消费
            } catch (InterruptedException e) { 
               e.printStackTrace();
           }
    }else{
       System.out.println(Thread.currentThread().getName()+":消费了第"+product+"个产品");
       product--;
       notifyAll();//有消费,就去唤醒生产者进行生产
    }
   }
}

 

线程范围的共享数据ThreadLocal类

1、ThreadLocal类表示可以存放当前线程的变量,同时这个变量可以让这个线程中的所有对象共享。这个类的底层实现方式和HashMap()大致相同,它分别有get(),set(),remove(),setInitialValue()四个方法,这四个方法分别表示得到当前线程相关的变量、设置当前线程的相关变量、把与当前线程有关的变量全部remve、设置初始化的值。当我们遇到需要在一个线程中放置多个变量的情况时,可以把这些变量封装成一个对象来存储。
package com.thread;
import java.util.Random;
public class ThreadLocal_fun {
	   private static ThreadLocal<Integer> x=new ThreadLocal<Integer>();//表示放置一个变量
	   private static ThreadLocal<MyThreadScopeDate> xObj=new ThreadLocal<MyThreadScopeDate>();//放置一个变量对象
	   public static void main(String[] args) {
		  for(int i=0;i<2;i++){//开启两个线程
			 new Thread(new Runnable() {
			    @Override
			    public void run() {
		                int data=new Random().nextInt();
		                System.out.println(Thread.currentThread().getName()+" has put data:"+data);
		                x.set(data);
		                MyThreadScopeDate.getThreadInstance().setName("admin");//为每个线程放置变量
		                MyThreadScopeDate.getThreadInstance().setAge(data);
		                new A().get();
			        new B().get();
			     }
			   }).start();
			}
	      }		
		static class A{
			public void get(){
				 int data=x.get();
				 System.out.println("A:"+Thread.currentThread().getName()+" has put data:"+data);
				 MyThreadScopeDate obj=MyThreadScopeDate.getThreadInstance();
				 System.out.println("A"+Thread.currentThread().getName()+":"+obj);
			}
		}
		static class B{
			public void get(){
				 int data=x.get();
				 System.out.println("B:"+Thread.currentThread().getName()+" has put data:"+data);
				 MyThreadScopeDate obj=MyThreadScopeDate.getThreadInstance();
				 System.out.println("A:"+Thread.currentThread().getName()+":"+obj);
			}
		}
	}
//封装的变量对象 ,单例
class MyThreadScopeDate{
	private MyThreadScopeDate(){}//私有化的构造函数不允许实例化
	private static ThreadLocal<MyThreadScopeDate> map=new ThreadLocal<MyThreadScopeDate>();
	public static  MyThreadScopeDate getThreadInstance(){
		MyThreadScopeDate instance=map.get();
		if(instance == null){
			instance=new MyThreadScopeDate();
			map.set(instance);
		}
		return instance;
	}
	private String name;
	private  int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "MyThreadScopeDate [name=" + name + ", age=" + age + "]";
	}
}

线程池

多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力;一般来说可以创建三种不同类型的线程池,同时线程池也为我们提供了关闭的方法:
a.创建固定大小 的线程池:一次只能执行三个任务,其他任务等待。 Executors.newFixedThreadPool(3)
b.创建缓存线程池:线程数动态量化,空闲线程自动回收。  Executors.newCachedThreadPool()
c.创建单一线程池:池中只有一个线程,如果线程意外终止就新产生一个。Executors.newSingleThreadExecutor()
d.shutdown();//当所有的任务都运行完后,就关闭线程池
e.shutdownNow();//立刻关闭线程池
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
	public static void main(String[] args) {
           //ExecutorService threadPool=Executors.newFixedThreadPool(3);//创建固定大小的线程池,这里表明只能同时运行3个线程。
	   //ExecutorService threadPool=Executors.newCachedThreadPool();//创建缓存线程池,为所有的任务都分配一个线程,自动关闭空闲的线程
	     ExecutorService threadPool=Executors.newSingleThreadExecutor();//创建单一线程池。
	     for(int i=1;i<10;i++){//循环往线程池中放10个任务
    	        final int task=i;
    	        threadPool.execute(new Runnable() {//在线程池中放任务
    			@Override
    			public void run() {
    				for(int j=5;j<10;j++){
    					 try {
							Thread.sleep(20);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
    					 System.out.println("线程"+Thread.currentThread().getName()+" loop of"+j+"次,在执行第 "+task+"个任务!");
    				}		
    			}
    		});  
       }
   				 System.out.println("提交了10个任务");
   				//threadPool.shutdown();//当所有的任务都运行完后,就关闭线程池
   			        //threadPool.shutdownNow();//立刻关闭线程池
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值