线程创建方式和线程同步
一:创建线程方式:
1. 继承Thread,然后重写run()方法
例如:
public class ThreadOne extends Thread {
@Override
public void run() {
Log.i("TAG","创建线程方式1");
}
}
启动线程方式start
ThreadOne one = new ThreadOne();
one.start();
2.实现runable接口
例如:
public class ThreadTwo implements Runnable {
@Override
public void run() {
Log.i("TAG","创建线程方式2");
}
}
Thread thread = new Thread(new ThreadTwo());
thread.start();
3.通过Callable和Future创建线程
public class FetureThread implements Callable<Integer> {
int a = 0;
@Override
public Integer call() throws Exception {
for(int i = 0 ; i < 8; i++ ){
a++;
}
return a;
}
}
public class Future {
FetureThread fetureThread = new FetureThread();
FutureTask<Integer> task = new FutureTask<Integer>(fetureThread);
public void run(){
Thread t = new Thread(task);
t.start();
}
public Integer getResult(){
try {
return task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return 0;
}
}
Future future = new Future();
future.run();
Log.i("HomeActivity 线程执行结果",future.getResult()+"");
以上三种创建线程的方式比较:
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程
线程类已经继承了Thread类,所以不能再继承其他父类
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类,同时使用Callable
还可以获取线程执行的结果
二:线程同步方式:
1.synchronized,
当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
public class MoneyThread implements Runnable {
@Override
public void run() {
synchronized (this){
for(int i = 0; i < 10 ;i++){
Log.i("HomeActivity " +"执行结果"+ Thread.currentThread().getName(),(++i)+"");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
MoneyThread moneyThread = new MoneyThread();
Thread t1 = new Thread(moneyThread,"线程1");
t1.start();
Thread t2 = new Thread(moneyThread,"线程2");
t2.start();
执行结果:
执行结果线程1: 1
01-14 22:22:25.957 21985-22005/? I/HomeActivity 执行结果线程1: 3
01-14 22:22:25.967 21985-22005/? I/HomeActivity 执行结果线程1: 5
01-14 22:22:25.967 21985-21985/? D/InputTransport: Input channel constructed: name='440c7c98 com.examply (client)', fd=57
01-14 22:22:25.977 21985-22005/? I/HomeActivity 执行结果线程1: 7
01-14 22:22:25.987 21985-22005/? I/HomeActivity 执行结果线程1: 9
01-14 22:22:26.007 21985-22006/? I/HomeActivity 执行结果线程2: 1
01-14 22:22:26.017 21985-22006/? I/HomeActivity 执行结果线程2: 3
01-14 22:22:26.027 21985-22006/? I/HomeActivity 执行结果线程2: 5
01-14 22:22:26.037 21985-22006/? I/HomeActivity 执行结果线程2: 7
01-14 22:22:26.052 21985-22006/? I/HomeActivity 执行结果线程2: 9
当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,
在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码
块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会
锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象
我们稍作修改:
Thread t1 = new Thread(new MoneyThread(),"线程1");
t1.start();
Thread t2 = new Thread(new MoneyThread(),"线程2");
t2.start();
执行结果:
01-14 22:28:35.572 28140-28159/? I/HomeActivity 执行结果线程1: 1
01-14 22:28:35.572 28140-28160/? I/HomeActivity 执行结果线程2: 1
01-14 22:28:35.582 28140-28160/? I/HomeActivity 执行结果线程2: 3
01-14 22:28:35.582 28140-28159/? I/HomeActivity 执行结果线程1: 3
01-14 22:28:35.587 28140-28140/? D/InputTransport: Input channel constructed: name='45044e78 com.example.yybj.myapplication/com.example.yybj.myapplication.HomeActivity (client)', fd=57
01-14 22:28:35.592 28140-28160/? I/HomeActivity 执行结果线程2: 5
01-14 22:28:35.592 28140-28159/? I/HomeActivity 执行结果线程1: 5
01-14 22:28:35.602 28140-28160/? I/HomeActivity 执行结果线程2: 7
01-14 22:28:35.602 28140-28159/? I/HomeActivity 执行结果线程1: 7
01-14 22:28:35.612 28140-28160/? I/HomeActivity 执行结果线程2: 9
01-14 22:28:35.612 28140-28159/? I/HomeActivity 执行结果线程1: 9
不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为什么上面的
例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,
每个对象只有一个锁(lock)与之相关联
这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是
syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象
中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定
syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,
所以两个线程可以同时执行
2.给指定的对象加锁:
public class OpeateThread implements Runnable {
Money money;
public OpeateThread(Money money) {
this.money = money;
}
@Override
public void run() {
try {
synchronized (money){
money.save(500);
money.getMoney(500);
Log.i("HomeActivity " +"执行结果"+ Thread.currentThread().getName(),
"现在的账户余额"+money.getResult()+"");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Money account = new Money(10000,"李四");
OpeateThread accountOperator = new OpeateThread(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "线程" + i);
threads[i].start();
}
执行结果:
96-9540/com.example.yybj.myapplication I/HomeActivity 执行结果线程0: 现在的账户余额10000
01-14 22:42:03.917 9496-9544/com.example.yybj.myapplication I/HomeActivity 执行结果线程4: 现在的账户余额10000
01-14 22:42:03.937 9496-9542/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 现在的账户余额10000
01-14 22:42:03.957 9496-9541/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 现在的账户余额10000
01-14 22:42:03.977 9496-9543/com.example.yybj.myapplication I/HomeActivity 执行结果线程3: 现在的账户余额10000
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。
public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:
生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
修饰一个方法
Synchronized作用于整个方法的写法。
写法一:
public synchronized void method()
{
// todo
}
写法二:
public void method()
{
synchronized(this) {
// todo
}
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,
但写法一与写法二是等价的,都是锁定了整个方法时的内容
修饰一个静态的方法
Synchronized也可修饰一个静态方法,用法如下:
public synchronized static void method() {
// todo
}
我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方
法锁定的是这个类的所有对象。
package com.example.yybj.myapplication;
import android.util.Log;
/**
* Created by YYBJ on 2018/1/14.
*/
public class StaticMoneyThread implements Runnable {
private static int a = 0;
@Override
public synchronized void run() {
test();
}
private synchronized static void test() {
for (int i = 0; i < 5; i++) {
Log.i("HomeActivity " + "执行结果" +
Thread.currentThread().getName(), (a += i) + "");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:01-14 23:00:59.877 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 0
01-14 23:00:59.887 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 1
01-14 23:00:59.887 28433-28433/com.example.yybj.myapplication D/InputTransport: Input channel constructed: name='44083f28 com.example.yybj.myapplication/com.example.yybj.myapplication.HomeActivity (client)', fd=58
01-14 23:00:59.897 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 3
01-14 23:00:59.907 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 6
01-14 23:00:59.917 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 10
01-14 23:00:59.957 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 10
01-14 23:00:59.967 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 11
[ 01-14 23:00:59.972 28433:28433 E/ ]
mali: REVISION=Linux-r3p1-01rel1 BUILD_DATE=Tue Jul 2 15:06:24 KST 2013
01-14 23:00:59.982 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 13
01-14 23:00:59.992 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 16
01-14 23:01:00.007 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 20
syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。
这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁
总结:
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;
如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制
在多线程的情况下,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。
Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
wait与notify是java同步机制中重要的组成部分
在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能。
因为都个对像都有锁,锁是每个对像的基础
通常,多线程之间需要协调工作:如果条件不满足,则等待;当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依
于wait/notify。等待机制与锁机制是密切关联的。最常见的就是生产者消费者模型
例如:
synchronized(obj) {
while(!condition) {
obj.wait();
}
obj.doSomething();
}
当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。
在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A :
synchronized(obj) {
condition = true;
obj.notify();
}
需要注意的概念是:
# 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj){...} 代码段内。
# 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj){...} 代码段内唤醒A。
# 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
#如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
线程同步方式2:Lock
ReentrantLock类
作用跟synchronized代码块差不多,都是用于实现互斥,当然两者是有区别的。
Condition接口
它提供了await()、signal()、signalAll()等方法,作用于Object类的wait()、signal()和signalAll()方法一样。
注意由于它是一个接口,因此是不能new出来的,我们必须通过ReentrantLock类的newCondition方法得到一个
Condition对象(实际上是ConditionObject类对象),这个对象与该ReentrantLock对象相关联。
同样,Condition的await()、signal()、signalAll()都必须在获得相对应的ReentrantLock锁才能进行调用
注意这里我们可以使用同一个ReentrantLock对象多次调用newCondition获得多个Condition对象,以实现更加
复杂的同步关系,而使用Object类的相当于只能有一个Condition对象。当然,在不同的Condition对象中,
等待和唤醒要相对应,比如说,我们两次调用newCondition方法,得到了两个Condition对象condition1和condition2,
假如我在线程A调用condition1.await(),然后线程B调用了condition2.signal(),那么线程A是一定不会因此被唤醒的,
而应该调用condition1.signal()线程A才可能会被唤醒,为什么说可能而不是一定呢?原因是signal()同Object类的
notify()是一样的,系统会随机唤醒等待集中的一个线程,而我们的线程A不一定会被选到。
每次lock完都必须要在finally块中执行unLock。
线程池:
线程池+ 阻塞队列综合使用
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new Runnable() {
@Override
public void run() {
Log.i("HomeActivity","线程池使用----------");
}
1. 继承Thread,然后重写run()方法
例如:
public class ThreadOne extends Thread {
@Override
public void run() {
Log.i("TAG","创建线程方式1");
}
}
启动线程方式start
ThreadOne one = new ThreadOne();
one.start();
2.实现runable接口
例如:
public class ThreadTwo implements Runnable {
@Override
public void run() {
Log.i("TAG","创建线程方式2");
}
}
Thread thread = new Thread(new ThreadTwo());
thread.start();
3.通过Callable和Future创建线程
public class FetureThread implements Callable<Integer> {
int a = 0;
@Override
public Integer call() throws Exception {
for(int i = 0 ; i < 8; i++ ){
a++;
}
return a;
}
}
public class Future {
FetureThread fetureThread = new FetureThread();
FutureTask<Integer> task = new FutureTask<Integer>(fetureThread);
public void run(){
Thread t = new Thread(task);
t.start();
}
public Integer getResult(){
try {
return task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return 0;
}
}
Future future = new Future();
future.run();
Log.i("HomeActivity 线程执行结果",future.getResult()+"");
以上三种创建线程的方式比较:
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程
线程类已经继承了Thread类,所以不能再继承其他父类
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类,同时使用Callable
还可以获取线程执行的结果
二:线程同步方式:
1.synchronized,
当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
public class MoneyThread implements Runnable {
@Override
public void run() {
synchronized (this){
for(int i = 0; i < 10 ;i++){
Log.i("HomeActivity " +"执行结果"+ Thread.currentThread().getName(),(++i)+"");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
MoneyThread moneyThread = new MoneyThread();
Thread t1 = new Thread(moneyThread,"线程1");
t1.start();
Thread t2 = new Thread(moneyThread,"线程2");
t2.start();
执行结果:
执行结果线程1: 1
01-14 22:22:25.957 21985-22005/? I/HomeActivity 执行结果线程1: 3
01-14 22:22:25.967 21985-22005/? I/HomeActivity 执行结果线程1: 5
01-14 22:22:25.967 21985-21985/? D/InputTransport: Input channel constructed: name='440c7c98 com.examply (client)', fd=57
01-14 22:22:25.977 21985-22005/? I/HomeActivity 执行结果线程1: 7
01-14 22:22:25.987 21985-22005/? I/HomeActivity 执行结果线程1: 9
01-14 22:22:26.007 21985-22006/? I/HomeActivity 执行结果线程2: 1
01-14 22:22:26.017 21985-22006/? I/HomeActivity 执行结果线程2: 3
01-14 22:22:26.027 21985-22006/? I/HomeActivity 执行结果线程2: 5
01-14 22:22:26.037 21985-22006/? I/HomeActivity 执行结果线程2: 7
01-14 22:22:26.052 21985-22006/? I/HomeActivity 执行结果线程2: 9
当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,
在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码
块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会
锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象
我们稍作修改:
Thread t1 = new Thread(new MoneyThread(),"线程1");
t1.start();
Thread t2 = new Thread(new MoneyThread(),"线程2");
t2.start();
执行结果:
01-14 22:28:35.572 28140-28159/? I/HomeActivity 执行结果线程1: 1
01-14 22:28:35.572 28140-28160/? I/HomeActivity 执行结果线程2: 1
01-14 22:28:35.582 28140-28160/? I/HomeActivity 执行结果线程2: 3
01-14 22:28:35.582 28140-28159/? I/HomeActivity 执行结果线程1: 3
01-14 22:28:35.587 28140-28140/? D/InputTransport: Input channel constructed: name='45044e78 com.example.yybj.myapplication/com.example.yybj.myapplication.HomeActivity (client)', fd=57
01-14 22:28:35.592 28140-28160/? I/HomeActivity 执行结果线程2: 5
01-14 22:28:35.592 28140-28159/? I/HomeActivity 执行结果线程1: 5
01-14 22:28:35.602 28140-28160/? I/HomeActivity 执行结果线程2: 7
01-14 22:28:35.602 28140-28159/? I/HomeActivity 执行结果线程1: 7
01-14 22:28:35.612 28140-28160/? I/HomeActivity 执行结果线程2: 9
01-14 22:28:35.612 28140-28159/? I/HomeActivity 执行结果线程1: 9
不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为什么上面的
例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,
每个对象只有一个锁(lock)与之相关联
这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是
syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象
中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定
syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,
所以两个线程可以同时执行
2.给指定的对象加锁:
public class OpeateThread implements Runnable {
Money money;
public OpeateThread(Money money) {
this.money = money;
}
@Override
public void run() {
try {
synchronized (money){
money.save(500);
money.getMoney(500);
Log.i("HomeActivity " +"执行结果"+ Thread.currentThread().getName(),
"现在的账户余额"+money.getResult()+"");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Money account = new Money(10000,"李四");
OpeateThread accountOperator = new OpeateThread(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "线程" + i);
threads[i].start();
}
执行结果:
96-9540/com.example.yybj.myapplication I/HomeActivity 执行结果线程0: 现在的账户余额10000
01-14 22:42:03.917 9496-9544/com.example.yybj.myapplication I/HomeActivity 执行结果线程4: 现在的账户余额10000
01-14 22:42:03.937 9496-9542/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 现在的账户余额10000
01-14 22:42:03.957 9496-9541/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 现在的账户余额10000
01-14 22:42:03.977 9496-9543/com.example.yybj.myapplication I/HomeActivity 执行结果线程3: 现在的账户余额10000
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。
public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:
生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
修饰一个方法
Synchronized作用于整个方法的写法。
写法一:
public synchronized void method()
{
// todo
}
写法二:
public void method()
{
synchronized(this) {
// todo
}
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,
但写法一与写法二是等价的,都是锁定了整个方法时的内容
修饰一个静态的方法
Synchronized也可修饰一个静态方法,用法如下:
public synchronized static void method() {
// todo
}
我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方
法锁定的是这个类的所有对象。
package com.example.yybj.myapplication;
import android.util.Log;
/**
* Created by YYBJ on 2018/1/14.
*/
public class StaticMoneyThread implements Runnable {
private static int a = 0;
@Override
public synchronized void run() {
test();
}
private synchronized static void test() {
for (int i = 0; i < 5; i++) {
Log.i("HomeActivity " + "执行结果" +
Thread.currentThread().getName(), (a += i) + "");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:01-14 23:00:59.877 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 0
01-14 23:00:59.887 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 1
01-14 23:00:59.887 28433-28433/com.example.yybj.myapplication D/InputTransport: Input channel constructed: name='44083f28 com.example.yybj.myapplication/com.example.yybj.myapplication.HomeActivity (client)', fd=58
01-14 23:00:59.897 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 3
01-14 23:00:59.907 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 6
01-14 23:00:59.917 28433-28467/com.example.yybj.myapplication I/HomeActivity 执行结果线程1: 10
01-14 23:00:59.957 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 10
01-14 23:00:59.967 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 11
[ 01-14 23:00:59.972 28433:28433 E/ ]
mali: REVISION=Linux-r3p1-01rel1 BUILD_DATE=Tue Jul 2 15:06:24 KST 2013
01-14 23:00:59.982 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 13
01-14 23:00:59.992 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 16
01-14 23:01:00.007 28433-28468/com.example.yybj.myapplication I/HomeActivity 执行结果线程2: 20
syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。
这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁
总结:
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;
如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制
在多线程的情况下,由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。
Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
wait与notify是java同步机制中重要的组成部分
在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能。
因为都个对像都有锁,锁是每个对像的基础
通常,多线程之间需要协调工作:如果条件不满足,则等待;当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依
于wait/notify。等待机制与锁机制是密切关联的。最常见的就是生产者消费者模型
例如:
synchronized(obj) {
while(!condition) {
obj.wait();
}
obj.doSomething();
}
当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。
在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A :
synchronized(obj) {
condition = true;
obj.notify();
}
需要注意的概念是:
# 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj){...} 代码段内。
# 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj){...} 代码段内唤醒A。
# 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
#如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
线程同步方式2:Lock
ReentrantLock类
作用跟synchronized代码块差不多,都是用于实现互斥,当然两者是有区别的。
Condition接口
它提供了await()、signal()、signalAll()等方法,作用于Object类的wait()、signal()和signalAll()方法一样。
注意由于它是一个接口,因此是不能new出来的,我们必须通过ReentrantLock类的newCondition方法得到一个
Condition对象(实际上是ConditionObject类对象),这个对象与该ReentrantLock对象相关联。
同样,Condition的await()、signal()、signalAll()都必须在获得相对应的ReentrantLock锁才能进行调用
注意这里我们可以使用同一个ReentrantLock对象多次调用newCondition获得多个Condition对象,以实现更加
复杂的同步关系,而使用Object类的相当于只能有一个Condition对象。当然,在不同的Condition对象中,
等待和唤醒要相对应,比如说,我们两次调用newCondition方法,得到了两个Condition对象condition1和condition2,
假如我在线程A调用condition1.await(),然后线程B调用了condition2.signal(),那么线程A是一定不会因此被唤醒的,
而应该调用condition1.signal()线程A才可能会被唤醒,为什么说可能而不是一定呢?原因是signal()同Object类的
notify()是一样的,系统会随机唤醒等待集中的一个线程,而我们的线程A不一定会被选到。
每次lock完都必须要在finally块中执行unLock。
线程池:
线程池+ 阻塞队列综合使用
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new Runnable() {
@Override
public void run() {
Log.i("HomeActivity","线程池使用----------");
}
});
这个是线程池的核心:
重点看下Worker;
所以在向线程池里面添加任务的时候,如果小于核心线程数的话,那就线程池会继续创建线程,如果超过了核心线程数
那么就会把新到来的任务添加到阻塞队列,当阻塞队列添加满了的时候(ArrayBlockingQueue因为有容量),还有新的任务
到来的时候就会继续创新新的线程,如果此时线程池里面线程数量小于最大线程数的话,如果还有新的任务到来,就会拒绝
所以在线程池里面创建线程的时候,线程数量和核心线程数,最大线程数,阻塞队列都有关系.
所以我们在创建线程池的时候,推荐使用ThreadPoolExecutor()这种方式来创建线程池,这样子开发者可以很清楚的知道线程池里面每一个参数的含义,对于Executors这种方式创建线程池的时候,不推荐,其中newFixedThreadPool,newSingleThreadExecutor,这两种创建线程池的时候在任务量很大的时候会导致OOM,根本原因就是阻塞队列采用的是LinkedBlockingQueue,而LinkedBlockingQueue的大小为Integer.MAX_VALUE,对于,newCachedThreadPool和ScheduledThreadPoolExecutor他们的阻塞队列都不大(这句话不严谨,读者可以看源码就明白了),这种方式创建的线程池,在任务数据多的话,频繁的创建线程,可能会出现OOM,在创建线程的时候要设置线程的名字和group,多打印日志,方便后期对于线程池的分析和系统优化分析.