`
insertyou
  • 浏览: 866600 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

java多线程

 
阅读更多
多线程在我操作系统的博客中也有涉及,而这里我们讲一下java中的多线程实现。

先回顾一下,

进程是运行中的应用程序,一个应用程序可以同时启动多个进程。

同一个进程的线程是共享一个地址空间的。


抢占式:随时能够中断另一个任务。

非抢占式:只有一个任务同意被中断时才能被中断。会导致死锁。

多线程:共享变量因此便于通信,创建开销少。

多进程:相互独立,通信有共享内存和消息传递,创建开销很大。

java的GUI用一个单独的线程在后台收集用户界面的事件。

因此repaint调用时,会把这个事件发送到事件请求队列中,执行完前面的事件,才会执行repaint事件。

java有一个线程在后台垃圾回收。

Thread.sleep(int i); 线程睡眠i毫秒。


java多线程实现方法:

实现Runnable接口,实现run方法

(1)继承Runnable并实现public void run();

(2)Thread t=new Thread(Runnable r);

(3)t.start();自动调用run方法,执行完就返回。

注意点:

(1)覆盖的run方法不能声明抛出异常。

(2)不要随便覆写start方法,如果要覆写,则调用super.start().

(3)一个线程只能被启动一次。

(4)两个线程之间没有任何关系,一个线程抛出异常终止,另一个线程没有任何影响。

以下程序说明多个线程没有任何影响:



继承Thread方法,覆写run方法

runnable能够实现资源共享。


常用方法:

(1)Thread cur = Thread.currentThread();

(2)String name = cur.getName();

(3)cur.setName(String name);


t.interrupt()请求中断一个线程,线程的中断状态位置位.当线程在阻塞时被请求中断,则抛出异常并被终止,特别在sleep()这个阻塞调用中,如果有请求中断,则会清除中断状态位。

Thread.currentThread().isInterrupted()判断中断状态位是否被置位。


线程状态:

1.new。Thread t=new Thread()时。

在堆中创建一个线程对象,这个对象和一般的JAVA对象一样。

2.runnable。t.start()时。注意只是可运行的,因此可以运行也可以没有运行,因为在线程运行时会有中断发生。

JVM创建了方法调用栈、程序计数器,这个线程放在可运行池中,等待CPU授权。

3.running.执行中。

4.blocked。当(1)sleep()(2)I/O中断(3)申请锁但是没有得到。当被阻塞时,CPU可以运行另外的线程。

5.dead。当(1)run正常返回(2)run由于异常返回。

但是都不会对其他线程造成影响。

t.isAlive()判断是否是可运行的或阻塞的。

优先级:

线程调度器只是把优先级看作一个参考因素,而不是全部。

t.setPriority(int );

static yield() 当前线程让步,选择其他线程中优先级最高的。

t.setDaemon(boolean) 标记为守护线程。

守护线程:为其他线程提供服务,当只有守护线程时,则程序退出。

后台线程特性:
(1)如果前台线程全部结束,则JVM会终止后台线程。



线程调度

(1)设置优先级.

(2)sleep(); 当前线程阻塞,睡眠好后,则回到可运行状态,等待CPU。

(3)yield();让步,即把当前线程放回可运行池中,并选取一个优先级最高的获得CPU使用权。

(4)join();如果A线程是当前运行线程,则A调用B的join方法,则A进入阻塞状态,直到B执行完毕,A才恢复可运行。



sleep和yield的区别:

sleep:把机会留给其他线程,即使其他线程比当前睡眠的线程优先级低也不要紧。

yield:只会给相同优先级的线程给予机会,yield是直接进入可运行状态。而sleep则是先进入阻塞状态。

sleep比yield具有更好的移植性。

Timer和TimerTask(定时器和定时任务

Timer timer = new Timer(boolean daemon);创建一个定时器,参数是是否为后台线程。

TimerTask是一个抽象类,必须继承并覆写run方法。

timer.schedule(task,long delay,long period);delay是运行task的延迟,period是运行task的周期。




线程组:ThreadGroup。根据功能进行分类,比如有多个线程是用来下载图片的,当用户请求中断时,这些线程是一起中断的,为了操作方便,可以把他们归在一个线程组。

如果线程A创建了线程B,并且线程B在构造方法中没有指定线程组,则B加入A所属的线程组。

不能中途改变线程组。

默认有一个main线程组。

ThreadGroup g=new ThreadGroup("....");

Thread t1=new Thread(g,"...");

Thread t2=new Thread(g,"...");

g.interrupt();中断t1和t2

g.activeCount();g这个线程组中可运行状态的线程数量

t1.getThreadGroup();返回t1所属的线程组。

g.enumerate(Thread[] threads);将当前还活着的线程的引用复制到threads中。


未捕获异常处理器:

当在run方法发生异常时,如果你try-catch了,则会捕获了,如果你throws了,则会抛出,但是如果发生了那些你没有预料到的异常,则会发送给未捕获异常处理器,然后线程终止。

(1)Thread.UncaughtExceptionHandler接口是未捕获异常处理器,必须实现uncaughtException(Thread,Throwable);

(2)------设置:t.setUncaughtExceptionHandler(handler)

-------设置默认:调用静态方法 Thread.setDefaultUncaughtExceptionHandler(handler);

-------如果没有为线程设置未捕获异常处理器,则默认为ThreadGroup的未捕获异常处理器,因为ThreadGroup实现了这个接口。




同步

在同步块中,如果执行Thread.sleep()或Thread.yield(),并不会释放锁,而是把执行的权利给了其他线程。

synchronized声明不会被继承。

建议:

为了尽可能地并发,我们应该只把对资源竞争的代码同步。

释放锁:

(1)正常执行完。

(2)因为异常而导致线程终止,则释放。


锁的使用方式:

(1)ReentrantLock

特性:锁是可重入的,即如果一个线程得到了某个对象的锁,则可以随便怎么使用锁,即可以多次使用锁。

(2)ReentrantReadWriteLock

ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

Lock readLock=lock.readLock(); 从读写锁中抽取读锁

Lock writeLock=lock.writeLock(); 从读写锁中抽取写锁

条件变量的使用:

条件变量是对于锁功能的补充,比如我们营造一个如下结构的程序:

当多个线程同时访问这个transfer函数,总会有一个串行化顺序,比如Person1得到了这个函数的使用权,但是他现有资金不足以转出,则会停止,等待别人转入等到有足够的钱转出时才继续执行,但是他却没有放弃这个函数的使用权,因此其他人没有办法使用这个函数,因此也没有办法转给Person1钱,因此程序就无法继续。唯一的解决办法就是使用条件变量。

一个锁能有多个条件变量。

试图获得锁:tryLock()

lock.tryLock()如果能获得锁则返回true并获得锁,如果不能则不会阻塞,能够去做其他事。

lock.tryLock(long s,TimeUnit.MILLISECONDS) 试图获得锁,如果不能获得,则阻塞s秒后去做其他事。在阻塞时中断则抛出异常。

Condition c=lock.newCondition();

java中默认每个对象都有一把隐式锁,一把隐式锁只有一个条件变量。因此老式的java程序中使用synchronized关键字进行同步。

老式同步方法:synchronized 新式同步方法:锁+条件变量 Lock lock=new ReentrantLock();Condition c=lock.newCondition(); 初始化
wait() c.await() 放入条件变量的等待队列,并且释放c对应的锁,使得其他人能用这把锁。【阻塞状态】阻塞时发生中断则抛出异常。
wait(long milli) c.await(long s,TimeUnit.MILLISECONDS) 相比较上面的方法,如果milli毫秒后会自动变回可运行状态。
notifyAll() c.signalAll() 把条件变量的等待队列的线程全部放回。【可运行状态】
notify() c.notify() 随机抽取条件变量等待队列中的一个线程放回变成可运行状态。

比较而言:synchronized比较方便。

缺点:以上的同步方法需要我们自己写代码进行同步,不是自动同步。

监视器:一种OO的同步方法,不需要考虑如何加锁。

同步块:

synchronized(obj)

critical section

即利用对象的锁进行同步。

volatile变量:

对一个变量使用volatile,则会使得虚拟机和编译器知道这个变量会被并发访问。


死锁:线程T1,T2,T1有锁L1,T2有锁L2,如果T1想要L2的同时T2想要L1,则会发生死锁。




生产者-消费者问题


wait()、notify()

每个对象都有一把锁,这个锁对应有一个等待队列,一个对象还有一个等待池(调用wait后的效果)

s.wait():当前线程释放s对象的锁,并且进入该对象的等待池。 一般放在while中。

s.notify():当前线程调用s的notify,把在s等待池中的一个线程放入锁队列。



废弃方法

suspend和resume:

suspend:暂停一个线程,不会放弃持有的锁.
resume:运行一个线程。
废弃原因:
(1)容易导致死锁
(2)一个线程能随时暂停另一个线程,不合理


stop:

stop终止这个线程.
废弃原因:
如果终止时该线程正在执行一个原子操作,则会产生逻辑不一致.



线程安全数据结构

阻塞队列:BlockingQueue适合用于生产者-消费者模型

Queue是BlockingQueue的父类:


阻塞队列提供的方法如下:

因此多个阻塞的两个方法。

(1)LinkedBlockingQueue<T>:基于链表,容量无限阻塞队列。

(2)ArrayBlockingQueue<T>:一个由数组支持的有界阻塞队列。有界缓冲区

(3)PriorityBlockingQueue<T>:无界阻塞优先级队列。

(4)DelayQueue<T>:队列中的元素都需要实现Delayed接口和Comparable接口。当元素的延迟用完(小于0)才能从队列中删除。

并发队列ConcurrentLinkedQueue:一个基于链接节点的无界线程安全队列。

1.offer

2.poll

3.peek

并发散列映射表ConcurrentHashMap<K,V>:支持多个读写器

下面是原子性操作:

1.putIfAbsent(key,value):添加

  •    if (!map.containsKey(key)) 
          return map.put(key, value);
      else
           return map.get(key);
    
    
  • 如果key原来没有,则插入。
  • 如果key原来有,则返回旧值。
3.remove(key,value):移除key-value对,如果不在映射中,则不执行任何操作 
  • if (map.containsKey(key) && map.get(key).equals(value)) {
    map.remove(key);
    return true;
    } else return false;
4.replace(key,oldvalue,newValue):替换
  • if (map.containsKey(key) && map.get(key).equals(oldValue)) {
    map.put(key, newValue);
    return true;
    } else return false;

写时复制数组CopyOnWriteArryaList<T>:每个修改他的线程都有这个数组的一份拷贝。

任何collection类通过包装器能够变为线程安全。


Callable<T>:类似Runnable,唯一的区别就是前者有返回值,后者无返回值,因此前者可以用于异步计算。

Future<T>用于保存Callable异步计算的结果。

1.get() 计算完之前阻塞,计算好了则返回结果。

2.isDone()返回是否计算完毕

多线程实现另外几种方法:

1.FutureTask类

结合以上两个接口,FutureTask是实现了以上两个接口的类,能够把Callable作为参数传进去,可以利用FutureTask得到Callable计算的结果。

FutureTask<T> implements Runnable,Future<T>

构造:FutureTask(Callable<T>c);

放入线程:Thread t=new Thread(ft);

开始执行:t.start();

取得结果:ft.get();

2.Executors类线程池类

线程池的优点:

(1)线程可以重用。

(2)限制线程个数。

线程池的变种:

1.创建线程池

(1)ExecutorService pool=Executors.newCachedThreadPool():在必要的时候能创建线程

(2)ExecutorService pool=Executors.newFixedThreadPool(int n);在线程池中创建固定数量的线程

(3)ExecutorService pool=Executors.newSingleThreadExecutor();线程池中只有一个线程,顺序执行线程。

2.提交作业:

(1)提交一个任务

  • Future<T> result=pool.submit(Callable<T>task)
  • Future<T>result=pool.submit(Runnable task)

(2)提交多个任务

  • ArrayList<Callable<T>>task;
  • List<Future<T>>results=pool.invokeAll(task); 提交所有任务。

Future<T>result=pool.invokeAny(task); 任意提交其中一个已完成任务。

3.result.get()可得结果

4.pool.sutdown()在用完线程池后关闭。

预定时间执行线程池:

ScheduledExecutorService pool=Executors.newScheduledThreadPool(int threads);线程池中只有一个线程,顺序执行线程。

pool.schedule(Callable<T>c,long delay,TimeUnit); 在delay秒后启动任务。

pool.scheduleAtFixedRate(Runnable com,long initialDelay,long period,TimeUnit); 周期性的启动任务。


控制线程组:ExecutorCompletionService<T> 内含阻塞队列

构造:ExecutorCompletionService<T> ecs=new ExecutorCompletionService(Executor e);
提交:ecs.submit(Callable<T>c);
取得结果是一个阻塞队列,队列中的元素是Future<T>:ecs.take().get();
一些同步方法:
1.CyclicBarrier 栅栏:顾名思义,就是如果在代码某处设个栅栏,则线程会执行到那停止,等到所有线程都到了,再一起执行。
CyclicBarrier cb=new CyclicBarrier(int n,Runnable action); 规定n个线程,如果n个线程到齐,则执行action。
cb.await(); 
cb.await(int n,TimeUnit);
2.倒计时门栓CountDownLatch 等到count变为0才开始执行。
CountDownLatch cdl=new CountDownLatch(int count);
cdl.await();等待直到count=0;
countDown();
3.同步队列SynchronousQueue<T>:put后阻塞等待take,take时阻塞等待put
  • 例:put(1);后则会阻塞,直到调用take()获取1为止。
  • 例:take()后会阻塞直到放入一个元素为止。

4.semaphore:

  • 构造:Semaphore(int n); 初始为n的信号量
  • acquire() n<=0时阻塞,否则n--;
  • release() n++,释放acquire阻塞的线程。


Swing与线程:

Swing中最基本的线程:

  • main线程
  • 实现分派线程 接收actionPerformed或paintComponent
  • gc垃圾回收线程

Swing不是线程安全的。

设置组件属性一定要在组件实现之前。

setVisible(),pack,add称为组件实现。

在Java中,键盘输入、鼠标点击或者应用程序本身产生的请求会被封装成一个事件,放进一个事件队列中,java.awt.EventQueue对象负责从这个队列中取出事件并派发它们。而EventQueue的派发有一个单独的线程管理,这个线程叫做事件派发线程(Event Dispatch Thread),也就是EDT。此外,Swing的绘制请求也是通过EDT来派发的。

EventQueue.invokeLater(Runnable r);把r任务放到事件队列中等待事件分派线程执行。

当需要更新Swing的内容时,则需要将这段代码放入r类中。

native关键字由java调用本机的系统函数。


分享到:
评论

相关推荐

    Java多线程设计模式上传文件

    Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式上传文件Java多线程设计模式...

    java多线程读取文件

    Java多线程读大文件 java多线程写文件:多线程往队列中写入数据

    java多线程ppt

    java多线程PPT 多线程基本概念 创建线程的方式 线程的挂起与唤醒 多线程问题

    java 多线程操作数据库

    一个java 多线程操作数据库应用程序!!!

    java多线程经典案例

    java多线程经典案例,线程同步、线程通信、线程阻塞等经典案例

    Java多线程编程技术

    《Java多线程编程核心技术》建议猿友们读两遍,因为其写得没有那么抽象,第一遍有些概念不是很理解,可以先跳过并记录起来,第一遍阅读的目的主要是了解整个架构。第二遍再慢慢品味,并贯穿全部是指点来思考,并将...

    Java多线程编程实战指南(核心篇)

    Java多线程编程实战指南(核心篇) 高清pdf带目录 随着现代处理器的生产工艺从提升处理器主频频率转向多核化,即在一块芯片上集成多个处理器内核(Core),多核处理器(Multicore Processor)离我们越来越近了――如今...

    Java多线程知识点总结

    该文档总结了Java多线程相关的知识点,分享给大家,简单易懂!

    java多线程的讲解和实战

    详细的讲解了java多线程的原理,并配有代码进行实战,适合java初学者和想对多线程有进一步了解的人。

    java多线程通信图解

    一张图方便理解和掌握java 多线程之间通信的实质 java 多线程 其实就是每个线程都拥有自己的内存空间,多线程之间的通信,比例A线程修改了主内存(main方法的线程)变量,需要把A线程修改的结果同步到主线程中,...

    java多线程处理数据库数据

    java多线程处理数据库数据,使用并发包,无框架,可批量处数据库数据,进行增删改。。等等操作。

    java多线程,对多线程,线程池进行封装,方便使用

    java多线程,对多线程,线程池进行封装,方便使用

    Java多线程编程经验

    现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。 线程是指进程中的一个执行流程,一个进程中可以运行多个线程。...本文档提供Java多线程编程经验,方便广大Java爱好者研究学习Java多线程

    java多线程处理大数据

    java多线程处理大数据,可根据配置的线程数,任务去调度处理

    java多线程并发

    java多线程并发的在新窗口

    Java多线程机制(讲述java里面与多线程有关的函数)

    Java多线程机制 9.1 Java中的线程 9.2 Thread的子类创建线程 9.3 使用Runable接口 9.4 线程的常用方法 9.5 GUI线程 9.6 线程同步 9.7 在同步方法中使用wait()、notify 和notifyAll()方法 9.8 挂起、恢复和终止线程 ...

    java多线程核心技术

    资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 Java多线程无处不在,如...

    java多线程实现大批量数据导入源码

    java多线程实现大批量数据切分成指定份数的数据,然后多线程处理入库或者导出,线程的个数和每份数据的数量都可以控制

    java多线程查询数据库

    java多线程并发查询数据库,使用线程池控制分页,并发查询。

    java多线程模拟队列实现排队叫号

    java多线程模拟队列实现排队叫号,多线程模拟排队叫号取号 java多线程模拟队列实现排队叫号,多线程模拟排队叫号取号

Global site tag (gtag.js) - Google Analytics