1.创建线程
方法有两种:继承Thread实现run()方法,实现runnable实run()方法,其实thread类也是实现了runnable接口的,
TestThread t=new TestThread();
启动线程t.start();
同一个线程对象只能启动一次,调用多次start()是无效的,出现异常。
实现Runnable接口相对于继承Thread类来说,有如下显著的优势:
(1)、适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。
(2)、可以避免由于Java的单继承特性带来的局限。开发中经常碰到这样一种情况,即:当要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么就只能采用实现Runnable接口的方式了。
(3)、增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程可以操作相同的数据,与它们的代码无关。当共享访问相同的对象时,即共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
2 线程的状态和生命周期
1.七种状态
i.刚创建,尚未启动
ii.Runnable 可执行,start(),yield()
iii.Blocked 受阻塞的线程,notify(),notifyall,synchronized(),join()
iv.Waiting 等待线程无期限wait()
v.Timed_waiting的有期限等待,sleep()
vi.Running正在执行的线程
vii.Terminated 已退出,interrupt()
线程的几个重要的方法:
1.sleep():很常见的,让当前线程让出cpu时间片,同时进入睡眠状态,该方法不会释放对象的机锁,如果在synchroized()代码块或者方法内,其他线程依然是无法访问被同步的对象。
2.wait(),当前线程进入被同步对象的等待池内,同时释放对象的机锁,其他线程可以访问被锁对象,可以指定睡眠时间或者调用notify()或者notifyAll()唤醒在等待池的线程。
wait(),notify()和notifyAll()必须在synchronized 块内调用,否则出现异常。
3.join(),在当前线程使用t.join(),即使当前线程在t线程执行完之后继续执行。
4.yield(),当前线程让出时间片,和其他线程一起竞争下一次执行时间。
5.Interrupt(),线程被阻塞时,用t.interrupt(),向线程抛出一个异常,让其捕获从而提早将线程阻塞状态结束
6.终止线程,线程终止后不能再次start(),会出现异常
2.1线程的属性
1.Set,线程名称,优先级,是否为daemon
2.Get,线程id,名称,优先级,状态,线程池,是否为活动,是否daemon,是否中断
3.currentThread()获取当前线程
3. daemon线程和用户线程
线程优先级和调度
1.1,5,10三个等级,一般为5
2.优先级高,只是执行的概率高,不具有先后严格的顺序
3.setPriority,设置优先级
1.只有当所有的用户线程终止,进程才算终止。
2.Deamon线程是服务线程,优先级低,如服务器侦听,jvm垃圾回收,是个无限循环,所有用户线程都结束了,deamon自动结束
3.进程中只有后台线程运行时,进程就会结束。
4.线程池和线程组
线程组
1.线程组管理线程,设置优先级,等属性,安全控制。
2.线程组必须从属于其他线程组,默认是系统主线程组。
3.将线程加入到线程组需要先创建线程组对象,将其作为线程构造函数参数。
4.List()输出线程树,enumerate()复制线程组中所有线程到一个线程数组中
线程组:线程组存在的意义,首要原因是安全。java默认创建的线程都是属于系统线程组,而同一个线程组的线程是可以相互修改对方的数据的。但如果在不同的线程组中,那么就不能“跨线程组”修改数据,可以从一定程度上保证数据安全。
线程池
线程池:线程池存在的意义,首要作用是效率。线程的创建和结束都需要耗费一定的系统时间(特别是创建),不停创建和删除线程会浪费大量的时间。所以,在创建出一条线程并使其在执行完任务后不结束,而是使其进入休眠状态,在需要用时再唤醒,那么 就可以节省一定的时间。如果这样的线程比较多,那么就可以使用线程池来进行管理。保证效率。
==一般情况下,线程越多占用内存也越大,并发量也越大。==
线程组和线程池共有的特点:
1,都是管理一定数量的线程
2,都可以对线程进行控制—包括休眠,唤醒,结束,创建,中断(暂停)但并不一定包含全部这些操作。
线程池的相关类和方法
1.Callable
其功能和Runable类似,但是个泛型接口,有一个返回值的call()方法,
2.Future
future 是指定了线程管理规范的接口,具有取消,查询是否完成,获取执行结果,设置结果等操作,
3.Futuretask
futureTask 是future的实现类,同时也实现了Runnable接口,所以具备了管理线程的能力,还包装了Callable接口,其实Runnable最终也会被传为Callable接口,FutureTask执行的任务类型都是Callable,
FutureTask即可以有Thread来执行也可以提交给ExecuteService执行,
ExcutorService接口
线程池都实现了该接口包括submit()execute(),shutdown()等方法,线程池的声明周期包括:运行,关闭,终止。创建后便进入运行状态,调用shutdown()进入关闭状态,此时不在接受 新任务,但一进提及哦啊的任务继续运行。当所有已提交任务执行完进入终止状态。
一个实现类ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
解释一下BlockingQueue,阻塞队列即队列满时加入阻塞,有链式和数组实现的,优先级的,还有SynchronousQueue
其他参数解释参见另一文章。== ==
常用的线程池有四类:fixedThreadPool、ScheduledThreadPool、cachedThreadPool、singleThreadPool
Android的asyncTask默认使用的是singleThreadPool线程池
线程同步
线程安全:多个线程同时调用同一个对象的方法时,对另一线程产生负面影响,如果一个类不受这个影响则是线程安全类。
为了保证数据的一致性,同步保证同一时刻只有一个线程能访问数据,对其修改后将修改后的值更新到内存,是其他线程能获得新数据。
1.synchronized同步
java对象自带了对象锁属性,可以对其进行多线程的访问同步,获取指定对象(可以为object)的互斥锁,就可以进行锁定
1).同步方法synchronized void f(),锁的是该方法所在类的对象
2).synchronized 同步代码块(临界区),锁指定对象,如果是Class对象,则锁定整个类,
1.Synchronized同步时会产生死锁
2.当一个线程获得一个对象的同步锁,而等待另一个资源时可以用wait()将当前线程阻塞,释放其同步锁,然后调用notify()通知其他正在等待该对象锁的线程,然后另一个线程获得同步锁后提供前一个线程的资源,调用notify()通知其他等待线程
3.Notify()唤醒一个等待的咸亨,随机一个,notifyall()全部唤醒
4.生产消费者模型
a)同一个容器的get,和put方法,都进行同步
b)一个生产者,一个消费者线程使用同一个容器,但条件不满足时wait(),执行完后notify()通知其他的等待的线程
c)必须在类P中定义一个新的成员变量bFull来表示数据存储空间的状态,当Consumer线程取走数据后,bFull值为false,当Producer线程放入数据后,bFull值为true。只有bFull为true时,Consumer线程才能取走数据,否则就必须等待Producer线程放入新的数据后的通知;反之,只有bFull为false,Producer线程才能放入新的数据,否则就必须等待Consumer线程取走数据后的通知。
volatile 同步
是一种弱同步,保证每次更新都会更新到内存。当一个变量定义成volatile之后, 保证了此变量对所有线程的可见性,也就是说当一条线程修改了这个变量的值,新的值对于其它线程来说是可以立即得知的.此时,该变量的读写操作直接在主内存中完成.
==Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。==
原子性:即一个线程对变量操作时,另一个线程不能对该变量进行操作。
Volatile variables share the visibility features of synchronized, but none of the atomicity features.
虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由==读取-修改-写入==操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。
可重入锁–ReentrantLock和condition
和synchronized比较:
1.获取和释放的灵活性
2.轮训锁和定时锁
3.公平锁。
reentrantlock的相关方法:
lock() 获取锁;
tryLock(),tryLock(long timeout,TimeUnit )尝试获取锁,超时尝试获取锁;
unlock()解锁
newCondition()获取锁 的condition,作用类似object的wait()和notify()
lock和unlock必须成对出现,否则可能发生死锁,在finally代码块中unlock(),而Synchronized代码块不需要手动释放锁。因为ReentrantLock只是普通的java类。而synchronized则包括了锁定的信息。
信号量,Semaphore
信号量本质是ReetrantLock,信号量维护了一个信号量许可集的个数,规定了最大允许多少线程同时访问数据,通过acquire()获得许可,一个线程获得许可,信号量的可用许可减一,通过release()释放许可,释放之后可用许可加1。
如果acquire没有可用许可,那么线程会阻塞。
构造方法:Semaphore(int size)
例如:食堂的5个窗口,有10个人打饭菜,那么最大只有5人同时进行打饭菜。
循环栅栏 CyclicBarrier
是一个同步辅助类,允许一组线程等待,释放等待线程之后可重用。
CyclicBarrier(int size,Runnable runnable);
在线程内调用cyclicBarrier的await()方法,当调用该方法的线程数达到指定size个数时,等待的线程才会往下执行。
闭锁CountDownLatch
CountDownLatch(int size)
也是线程同步的辅助类,指定的一个或多个线程等待其他线程执行完成后执行。
在需要等待的线程内调用await(),该线程就会暂停执行,在被等待的线程调用countdown(),countDown计数减一,直到0,等待线程继续执行。
countDownLath无法被重置,循环栅栏可以。
循环栅栏是要执行的线程增加,countDownLatch是执行完成的线程增加。
同步集合
copyOnWriteArraylist,copyOncWriteArraySet
内部使用ReentrantLock进行同步,读时不需要枷锁,写入时加锁,将array中的元素复制,在新数组中添加,再将新数组写入到原数组中。
ConcurrentHashMap,HashTable
HashTable在高并发情况下,效率低下原因是,所有线程公用一把锁,get()也会被加锁。
concurrentHashMap使用锁分段技术,对数据分段枷锁,减少了锁的粒度,提高了效率。
BlockingQueue
阻塞队列满时添加元素线程将被阻塞,队空时取元素的线程也进入等待。当需要实现生产消费者模型问题时(还可以用wait()和ReentrantLock),使用BlockingQueue,避免了手动判断条件。