线程创建方式和线程同步

一:创建线程方式:
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,多打印日志,方便后期对于线程池的分析和系统优化分析.





























































































猜你喜欢

转载自blog.csdn.net/qq_18757557/article/details/79094672