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)两个线程之间没有任何关系,一个线程抛出异常终止,另一个线程没有任何影响。

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

public class MyThread2{ public static void main(String args[]){ Thread t1 = new Thread(new Thread1()); Thread t2 = new Thread(new Thread2()); t1.start(); t2.start(); } } class Thread1 implements Runnable{ public void run(){ try{ Thread.sleep(500); } catch(Exception e){} int a = 1/0; System.out.println("线程1结束"); } } class Thread2 implements Runnable{ public void run(){ for(int a=0;a<20;a++){ System.out.println("a = "+a ); try{ Thread.sleep(100); } catch(Exception e){} } System.out.println("线程2结束"); } } //线程相互不影响

继承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会终止后台线程。

public class MyThread5 extends Thread{ private static int a = 0; public void run(){ while(true){ System.out.println(getName()+":"+a++); try{ Thread.sleep(100); } catch(Exception e){} if(a==100) break; yield(); } } public static void main(String args[]){ MyThread5 t1 = new MyThread5(); MyDaemon t2 = new MyDaemon(); t2.setDaemon(true); t1.start(); t2.start(); } static class MyDaemon extends Thread{ public void run(){ while(true){ a = 1; try{ Thread.sleep(1000); } catch(Exception e){} } } } }

线程调度

(1)设置优先级.

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

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

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

public class MyThread3 extends Thread{ private static StringBuffer buf = new StringBuffer(); public void run(){ for(int a=0;a<10;a++){ buf.append("当前线程:"+currentThread().getName()+"\n"); } } public static void main(String args[])throws Exception{ MyThread3 t1 = new MyThread3(); MyThread3 t2 = new MyThread3(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(MyThread3.buf); } }

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的周期。

package timer; import java.util.*; public class TimerDemo extends Thread{ private int a; private static int count; public void start(){ super.start(); Timer timer = new Timer(true); TimerTask task = new TimerTask(){ public void run(){ while(true){ a = 0; try{ sleep(1000); } catch(Exception e){} } } }; timer.schedule(task,10,500); } public void run(){ while(true){ System.out.println(getName()+":"+a++); try{ Thread.sleep(100); }catch(Exception e){} if(count++==100) break; } } public static void main(String args[]){ TimerDemo t1 = new TimerDemo(); t1.start(); } }


线程组: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实现了这个接口。

class MachineGroup extends ThreadGroup{ public MachineGroup(){ super("MachineGroup"); } public void uncaughtException(Thread t,Throwable e){ System.out.println(getName()+" catches an exception from "+ t.getName()); super.uncaughtException(t,e); } } class MachineHandler implements Thread.UncaughtExceptionHandler{ private String name; public MachineHandler(String name){ this.name = name; } public void uncaughtException(Thread t,Throwable e){ System.out.println(name+" catches an exception from "+ t.getName()); } } public class UncaughtDemo extends Thread{ public UncaughtDemo(ThreadGroup group,String name){ super(group,name); } public void run(){ int a = 1/0; } public static void main(String args[]){ ThreadGroup g = new MachineGroup(); UncaughtExceptionHandler defaultHandler = new MachineHandler("DefaultHandler"); UncaughtDemo.setDefaultUncaughtExceptionHandler(defaultHandler); UncaughtDemo m1 = new UncaughtDemo(g,"线程-1"); UncaughtDemo m2 = new UncaughtDemo(g,"线程-2"); UncaughtExceptionHandler curHandler = new MachineHandler("Machine2-Handler"); m2.setUncaughtExceptionHandler(curHandler); m1.start(); m2.start(); } }


同步

在同步块中,如果执行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,则会发生死锁。

public class DeadLockDemo extends Thread{ private Counter counter; public DeadLockDemo(Counter counter){ this.counter = counter; start(); } public void run(){ for(int i=0;i<1000;i++){ counter.add(); } } public static void main(String args[]){ Counter c1 = new Counter(); Counter c2 = new Counter(); c1.setFriend(c2); c2.setFriend(c1); DeadLockDemo m1 = new DeadLockDemo(c1); DeadLockDemo m2 = new DeadLockDemo(c2); } } class Counter{ private int a; private Counter friend; public void setFriend(Counter friend){ this.friend = friend; } public synchronized void add(){ a++; Thread.yield(); friend.delete(); System.out.println(Thread.currentThread().getName()+": add"); } public synchronized void delete(){ a--; System.out.println(Thread.currentThread().getName()+": delete"); } }


生产者-消费者问题

public class AppDemo{ public static void main(String args[]){ Stack s = new Stack("Stack"); Producer p = new Producer("Producer",s); Consumer c1 = new Consumer("Consumer1",s); Consumer c2 = new Consumer("Consumer2",s); } } class Consumer extends Thread{ private Stack stack; public Consumer(String name,Stack s){ super(name); stack = s; start(); } public void run(){ for(int i=0;i<100;i++){ synchronized(stack){ String good = stack.pop(); System.out.println(getName() +" pop "+good); } try{ Thread.sleep(1000); } catch(Exception e){} } } } class Producer extends Thread{ private Stack stack; public Producer(String name,Stack s){ super(name); stack = s; start(); } public void run(){ for(int i=0;i<200;i++){ synchronized(stack){ String good = "good "+(stack.getPoint()+1); stack.push(good); System.out.println(getName() +" push "+good); } } } } class Stack{ public final static int LEN = 100; private int point = -1; private String name; private String goods[] = new String[LEN]; public Stack(String name){ this.name = name; } public String getName(){ return name; } public synchronized int getPoint(){ return point; } public synchronized String pop(){ if(point==-1) return null; //如果消费者不能拿到 String tmp = goods[point]; goods[point] = null; point--; return tmp; } public synchronized void push(String name){ if(point==LEN-1) return; point++; goods[point] = name; } }

wait()、notify()

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

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

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



废弃方法

suspend和resume:

suspend:暂停一个线程,不会放弃持有的锁.
resume:运行一个线程。
废弃原因:
(1)容易导致死锁
(2)一个线程能随时暂停另一个线程,不合理
public class SuspendDemo extends Thread{ private int a; public void run(){ for(int i=0;i<1000;i++){ synchronized(this){ a+=i; Thread.yield(); a-=i; } } } public synchronized void reset(){ a = 0; } public static void main(String args[])throws Exception{ SuspendDemo s = new SuspendDemo(); //s获得SuspendDemo的锁 s.start(); yield(); s.suspend(); s.reset(); //main调用reset方法之前先要获得SuspendDemo的锁,但是此时s获得,并且暂停。 s.resume(); } }

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调用本机的系统函数。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值