java多线程

来源:互联网 发布:剑灵明星捏脸数据 编辑:程序博客网 时间:2024/05/21 10:39

可以使用 Runable 实现类来创建线程,可以共享一个 target 。 

不太常用继承Thread 类,虽然这样写稍微简单些。

可以使用 Callable 实现类来创建线程,可以接受并返回运行体的返回值,可以共享一个 target 。 

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. public class ThirdThread implements Callable<Integer>  
  2. {  
  3.     // 实现call方法,作为线程执行体  
  4.     public Integer call()  
  5.     {  
  6.         int i = 0;  
  7.         for ( ; i < 100 ; i++ )  
  8.         {  
  9.             System.out.println(Thread.currentThread().getName()  
  10.                 + " 的循环变量i的值:" + i);  
  11.         }  
  12.         // call()方法可以有返回值  
  13.         return i;  
  14.     }  
  15.   
  16.     public static void main(String[] args)   
  17.     {  
  18.         // 创建Callable对象  
  19.         ThirdThread rt = new ThirdThread();  
  20.         // 使用FutureTask来包装Callable对象  
  21.         FutureTask<Integer> task = new FutureTask<Integer>(rt);  
  22.         for (int i = 0 ; i < 100 ; i++)  
  23.         {  
  24.             System.out.println(Thread.currentThread().getName()  
  25.                 + " 的循环变量i的值:" + i);  
  26.             if (i == 20)  
  27.             {  
  28.                 // 实质还是以Callable对象来创建、并启动线程  
  29.                 new Thread(task , "有返回值的线程").start();  
  30.             }  
  31.         }  
  32.         try  
  33.         {  
  34.             // 获取线程返回值  
  35.             System.out.println("子线程的返回值:" + task.get());  
  36.         }  
  37.         catch (Exception ex)  
  38.         {  
  39.             ex.printStackTrace();  
  40.         }  
  41.     }  
  42. }  
suspend() 方法可以将线程挂起,由运行状态切换到阻塞状态。

resume() 方法可以使线程重新开始,即由阻塞的状态切换到就绪的状态。

注:已经死亡的线程不可重新开始。


Join 线程:一个线程中,有另一个线程调用join()方法,则原来的线程必须等到新加入的线程结束后才可执行。

还有限制等待时间的方法  join(long mills); 若是等了这么久还没有结束,那么就不等了。


若需要给线程设置为后台线程,而且一定要在start之前就设置,调用方法 setDaemon(true);  主线程死亡,则后台线程也会死亡。

前台线程创建的线程默认为前台线程,后台创建的线程默认为后台线程。


yield()方法会暂时让步给优先级更高的线程(设置线程优先级 setPriority(1~10) )由执行状态切换到就绪状态,若没有优先级更高的,相当于啥也没做。

这个不太常用,常用的是sleep()


同步代码块

synchronized(obj) { //里面写代码,表示给obj加锁,后执行此代码块 }

或者修饰方法,表示同一个对象,此方法不会同时执行。


同步锁

和同步方法有点类似,类中Field 定义锁对象private final ReentrantLock lock = new ReentrantLock();  可重入锁的实现类,同一时间只能有一个对象可以进入临界区,ReentrantReadWriteLock 读写锁实现类。

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1.               // 加锁  
  2. lock.lock();  
  3. try  
  4. {  
  5.     // 账户余额大于取钱数目  
  6.     if (balance >= drawAmount)  
  7.     {  
  8.         // 吐出钞票  
  9.         System.out.println(Thread.currentThread().getName()  
  10.             + "取钱成功!吐出钞票:" + drawAmount);  
  11.         try  
  12.         {  
  13.             Thread.sleep(1);  
  14.         }  
  15.         catch (InterruptedException ex)  
  16.         {  
  17.             ex.printStackTrace();  
  18.         }  
  19.         // 修改余额  
  20.         balance -= drawAmount;  
  21.         System.out.println("\t余额为: " + balance);  
  22.     }  
  23.     else  
  24.     {  
  25.         System.out.println(Thread.currentThread().getName()  
  26.             + "取钱失败!余额不足!");  
  27.     }  
  28. }  
  29. finally  
  30. {  
  31.     // 修改完成,必须释放锁  
  32.     lock.unlock();  
  33. }         
一段被锁保护的代码可以调用另一个被相同锁保护的方法。

线程间通信:

传统间线程通信

1.wait()方法:当前线程等待,直到其它线程调用该同步监视器的 notify() 方法或 notifyAll() 来唤醒该线程。

2.notify()方法,唤醒一个正在等待的线程。

3.notifyAll()方法,唤醒全部正在等待的线程。

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. try  
  2. {  
  3.     // 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞  
  4.     if (!flag)  
  5.     {  
  6.         wait();  
  7.     }  
  8.     else  
  9.     {  
  10.         // 执行取钱  
  11.         System.out.println(Thread.currentThread().getName()   
  12.             + " 取钱:" +  drawAmount);  
  13.         balance -= drawAmount;  
  14.         System.out.println("账户余额为:" + balance);  
  15.         // 将标识账户是否已有存款的旗标设为false。  
  16.         flag = false;  
  17.         // 唤醒其他线程  
  18.         notifyAll();  
  19.     }  
  20. }  
  21. catch (InterruptedException ex)  
  22. {  
  23.     ex.printStackTrace();  
  24. }  

但若是不使用synchronized来保证同步,而直接用Lock对象,系统中不存在隐式的同步监听器,也就不能使用 wait() , notify() , notifyAll() 方法进行线程通信了。
若是使用Lock对象,则需要引入Condition,使用方法condition.await(), condition.singalAll();

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. // 显式定义Lock对象  
  2. private final Lock lock = new ReentrantLock();  
  3. // 获得指定Lock对象对应的Condition  
  4. private final Condition cond  = lock.newCondition();   
[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. // 加锁  
  2. lock.lock();  
  3. try  
  4. {  
  5.     // 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞  
  6.     if (!flag)  
  7.     {  
  8.         cond.wait();  
  9.     }  
  10.     else  
  11.     {  
  12.         // 执行取钱  
  13.         System.out.println(Thread.currentThread().getName()   
  14.             + " 取钱:" +  drawAmount);  
  15.         balance -= drawAmount;  
  16.         System.out.println("账户余额为:" + balance);  
  17.         // 将标识账户是否已有存款的旗标设为false。  
  18.         flag = false;  
  19.         // 唤醒其他线程  
  20.         cond.signalAll();  
  21.     }  
  22. }  
  23. catch (InterruptedException ex)  
  24. {  
  25.     ex.printStackTrace();  
  26. }  
  27. // 使用finally块来释放锁  
  28. finally  
  29. {  
  30.     lock.unlock();  
  31. }  
以BlockingQueue对象来实现线程间通信。

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. // 定义一个长度为2的阻塞队列  
  2. BlockingQueue<String> bq = new ArrayBlockingQueue<>(2);  
  3. bq.put("Java");//与bq.add("Java"、bq.offer("Java")相同  
  4. bq.put("Java");//与bq.add("Java"、bq.offer("Java")相同  
  5. bq.put("Java");//① 阻塞线程。  


线程组和未处理的异常

一些未处理的异常可以由这个线程所在的线程组来统一处理。

要构造一个属于某个线程组的线程,调用构造器:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Thread(ThreadGroup group, Runnable target) ;  
  2. Thread(ThreadGroup group, Runnable target, String name)  ;  
获得一个线程的线程组,直接调用 getThreadGroup() 方法。

线程组类ThreadGroup

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class MyExHandler implements Thread.UncaughtExceptionHandler   
  2. {  
  3.     //实现uncaughtException方法,该方法将处理线程的未处理异常  
  4.     public void uncaughtException(Thread t, Throwable e)  
  5.     {  
  6.         System.out.println(t + " 线程出现了异常:" + e);  
  7.     }   
  8. }  
  9. public class ExHandler  
  10. {  
  11.     public static void main(String[] args)   
  12.     {  
  13.         // 设置主线程的异常处理器  
  14.         Thread.currentThread().setUncaughtExceptionHandler  
  15.             (new MyExHandler());  
  16.         int a = 5 / 0;     //①  
  17.         System.out.println("程序正常结束!");  
  18.     }  
  19. }  

线程池:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. public class ThreadPoolTest  
  2. {  
  3.     public static void main(String[]args)  
  4.     {  
  5.         ExecutorService pool =Executors.newFixedThreadPool(10);  
  6.         pool.submit(new MyThread());  
  7.         pool.submit(new MyThread());  
  8.         pool.submit(new MyThread());  
  9.         pool.shutdown();  
  10.     }  
  11. }  
  12. class MyThread implements Runnable  
  13. {  
  14.     int i=0;  
  15.     public void run()  
  16.     {  
  17.         for(;i<100;++i)  
  18.         {  
  19.             System.out.println(Thread.currentThread().getName()+" "+i);  
  20.         }  
  21.     }  
  22.   
  23. }  

ForkJoinPool 是为了更好地利用多核处理器资源而设计的多线程类。

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class PrintTask extends RecursiveAction  
  2. {  
  3.     // 每个“小任务”只最多只打印50个数  
  4.     private static final int THRESHOLD = 50;  
  5.     private int start;  
  6.     private int end;  
  7.     // 打印从start到end的任务  
  8.     public PrintTask(int start, int end)  
  9.     {  
  10.         this.start = start;  
  11.         this.end = end;  
  12.     }  
  13.     @Override  
  14.     protected void compute()   
  15.     {  
  16.         // 当end与start之间的差小于THRESHOLD时,开始打印  
  17.         if(end - start < THRESHOLD)  
  18.         {  
  19.             for (int i = start ; i < end ; i++ )  
  20.             {  
  21.                 System.out.println(Thread.currentThread().getName()  
  22.                     + "的i值:" + i);  
  23.             }  
  24.         }  
  25.         else  
  26.         {  
  27.             // 如果当end与start之间的差大于THRESHOLD时,即要打印的数超过50个  
  28.             // 将大任务分解成两个小任务。  
  29.             int middle = (start + end) /2;  
  30.             PrintTask left = new PrintTask(start, middle);  
  31.             PrintTask right = new PrintTask(middle, end);  
  32.             // 并行执行两个“小任务”  
  33.             left.fork();  
  34.             right.fork();  
  35.         }  
  36.     }  
  37. }  
  38. public class ForkJoinPoolTest  
  39. {  
  40.     public static void main(String[] args)   
  41.         throws Exception  
  42.     {  
  43.         ForkJoinPool pool = new ForkJoinPool();  
  44.         // 提交可分解的PrintTask任务  
  45.         pool.submit(new PrintTask(0 , 300));  
  46.         pool.awaitTermination(2, TimeUnit.SECONDS);  
  47.         // 关闭线程池  
  48.         pool.shutdown();  
  49.     }  
  50. }  
如果想要返回运算结果,则继承:RecursiveAction<Integer> 就好,然后覆盖父类的compute()时,用返回值Integer替换void。

线程相关类:ThreadLocal类,隔离多个线程的数据共享,每个线程都拥有一份资源。

它可以保留线程局部变量,即在不同的线程,就算是同一个对象,也会持有不同的值。

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class Account  
  2. {  
  3.     /* 定义一个ThreadLocal类型的变量,该变量将是一个线程局部变量 
  4.     每个线程都会保留该变量的一个副本 */  
  5.     private ThreadLocal<String> name = new ThreadLocal<>();  
  6.     // 定义一个初始化name属性的构造器  
  7.     public Account(String str)  
  8.     {  
  9.         this.name.set(str);  
  10.         // 下面代码用于访问当前线程的name副本的值  
  11.         System.out.println("---" + this.name.get());  
  12.     }  
  13.     // name的setter和getter方法  
  14.     public String getName()  
  15.     {  
  16.         return name.get();  
  17.     }  
  18.     public void setName(String str)  
  19.     {  
  20.         this.name.set(str);  
  21.     }  
  22. }  
  23. class MyTest extends Thread  
  24. {  
  25.     // 定义一个Account属性  
  26.     private Account account;  
  27.     public MyTest(Account account, String name)  
  28.     {  
  29.         super(name);  
  30.         this.account = account;  
  31.     }  
  32.     public void run()  
  33.     {  
  34.         // 循环10次  
  35.         for (int i = 0 ; i < 10 ; i++)  
  36.         {  
  37.             // 当i == 6时输出将账户名替换成当前线程名  
  38.             if (i == 6)  
  39.             {  
  40.                 account.setName(getName());  
  41.             }  
  42.             // 输出同一个账户的账户名和循环变量  
  43.             System.out.println(account.getName()  
  44.                 + " 账户的i值:" + i);  
  45.         }  
  46.     }  
  47. }  
  48. public class ThreadLocalTest  
  49. {  
  50.     public static void main(String[] args)   
  51.     {  
  52.         // 启动两条线程,两条线程共享同一个Account  
  53.         Account at = new Account("初始名");  
  54.         /* 
  55.         虽然两条线程共享同一个账户,即只有一个账户名 
  56.         但由于账户名是ThreadLocal类型的,所以每条线程 
  57.         都完全拥有各自的账户名副本,所以从i == 6之后,将看到两条 
  58.         线程访问同一个账户时看到不同的账户名。 
  59.         */  
  60.         new MyTest(at , "线程甲").start();  
  61.         new MyTest(at , "线程乙").start ();  
  62.     }  
  63. }  

为了解决线程不安全的集合,只需要包装下。

调用Collections中的一系列静态方法即可 ,举个例子:便可以返回线程安全的 List<T>,应该在创建后立刻包装。

  • public static <T> List<T> synchronizedList(List<T> list)

还有在 Java.util.concurrent 包下提供了大量支持高效并发访问的集合接口和实现类。

类名以 Concurrent 开头的集合类,在并发写入时有较好的性能

类名以CopyOnWriter开头的集合类,在并发读取时有较好的性能




转载自:http://blog.csdn.net/qq_33665647/article/details/54575486?locationNum=4&fps=1

0 0
原创粉丝点击