Java多线程使用

https://www.cnblogs.com/wxd0108/p/5479442.html

多线程:指的是这个程序(一个进程)运行时产生了不止一个线程;


并行与并发:

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。

并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:

void transferMoney(User from, User to, float amount){ to.setMoney(to.getBalance() + amount); 

 from.setMoney(from.getBalance() - amount); 

}

同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。


synchronized单独使用

代码块:如下,在多线程环境下,synchronized块中的方法获取了lock实例的monitor,如果实例相同,那么只有一个线程能执行该块内容

public class Thread1 implements Runnable { 

 Object lock; 

 public void run() { 

 synchronized(lock){

 ..do something 

                                   } 

                                }

 }


直接用于方法: 相当于上面代码中用lock来锁定的效果,实际获取的是Thread1类的monitor。更进一步,如果修饰的是static方法,则锁定该类所有实例。

public class Thread1 implements Runnable 

        public synchronized void run() {

 ..do something 

         }

 }


volatile关键字

针对多线程使用的变量如果不是volatile或者final修饰的,

很有可能产生不可预知的结果(另一个线程修改了这个值,

但是之后在某线程看到的是修改之前的值)。

其实道理上讲同一实例的同一属性本身只有一个副本。

但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。


线程管理类 

管理类的概念比较广泛, 用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。

了解到的值得一提的管理类:ThreadPoolExecutor

ThreadPoolExecutor 

如果不了解这个类,应该了解前面提到的ExecutorService,开一个自己的线程池非常方便:

ThreadPoolExecutor参数解释

corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。 maximumPoolSize:线程最大值,线程的增长始终不会超过该值。 keepAliveTime:当池内线程数高于corePoolSize时,经过多少时间多余的空闲线程才会被回收。回收前处于wait状态 unit: 时间单位,可以使用TimeUnit的实例,如TimeUnit.MILLISECONDS  workQueue:待入任务(Runnable)的等待场所,该参数主要影响调度策略,如公平与否,是否产生饿死(starving) threadFactory:线程工厂类,有默认实现,如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。


sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 

wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

 sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 所以sleep()和wait()方法的最大区别是:     sleep()睡眠时,保持对象锁,仍然占有该锁;     而wait()睡眠时,释放对象锁。

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。

在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 

wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;   wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。


线程同步

1、synchronized关键字的作用域有二种: 

1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 所以说要保持同一个对象使用单例模式;

2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象; 

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法; 

Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized关键字的作用进行深入了解才可定论。总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。在进一步阐述之前,我们需要明确几点:

A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。B.每个对象只有一个锁(lock)与之相关联。C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 接着来讨论synchronized用到不同地方对代码产生的影响: 假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。 1.  把synchronized当作函数修饰符时,示例代码如下:Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:public void methodAAA(){synchronized (this)      //  (1){       //…..}} (1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(2.同步块,示例代码如下:            public void method3(SomeObject so)              {                     synchronized(so){       //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:class Foo implements Runnable{       private byte[] lock = new byte[0];  // 特殊的instance变量    Public void methodA(){       synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。3.将synchronized作用于static 函数,示例代码如下:      Class Foo{public synchronized static void methodAAA()   // 同步的static 函数{//….}public void methodBBB(){       synchronized(Foo.class)   //  class literal(类名称字面常量)}       }   代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。  1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。4、对于同步,要时刻清醒在哪个对象上同步,这是关键。5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。  九、线程数据传递在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。9.1、通过构造方法传递数据  在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:  [java] view plain copy     package mythread;   public class MyThread1 extends Thread   {   private String name;   public MyThread1(String name)   {   this.name = name;   }   public void run()   {   System.out.println("hello " + name);   }   public static void main(String[] args)   {   Thread thread = new MyThread1("world");   thread.start();   }   }   由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。  9.2、通过变量和方法传递数据  向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置 name变量:  [java] view plain copy     package mythread;   public class MyThread2 implements Runnable   {   private String name;   public void setName(String name)   {   this.name = name;   }   public void run()   {   System.out.println("hello " + name);   }   public static void main(String[] args)   {   MyThread2 myThread = new MyThread2();   myThread.setName("world");   Thread thread = new Thread(myThread);   thread.start();   }   }   9.3、通过回调函数传递数据  上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个 value是无法事先就传入线程类的。  [java] view plain copy     package mythread;   class Data   {   public int value = 0;   }   class Work   {   public void process(Data data, Integer numbers)   {   for (int n : numbers)   {   data.value += n;   }   }   }   public class MyThread3 extends Thread   {   private Work work;   public MyThread3(Work work)   {   this.work = work;   }   public void run()   {   java.util.Random random = new java.util.Random();   Data data = new Data();   int n1 = random.nextInt(1000);   int n2 = random.nextInt(2000);   int n3 = random.nextInt(3000);   work.process(data, n1, n2, n3); // 使用回调函数   System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+"   + String.valueOf(n3) + "=" + data.value);   }   public static void main(String[] args)   {   Thread thread = new MyThread3(new Work());   thread.start();   }   }      好了,Java多线程的基础知识就讲到这里了,有兴趣研究多线程的推荐直接看java的源码,你将会得到很大的提升!林炳文Evankaka原创作品。转载请注明出处http://blog.csdn.net/evankaka from: http://blog.csdn.net/evankaka/article/details/44153709























































































你可能感兴趣的:(Java多线程使用)