Java多线程

目录

 

1. 多线程基础概念介绍

1.1、一个线程的生命周期

1.2、线程的状态转换图

2、创建java多线程

2.1、扩展java.lang.Thread类

start()方法和run()方法的区别

2.2、实现java.lang.Runnable接口

2.3、Thread和Runnable的区别

2.4、实现Callable接口通过FutureTask包装器来创建Thread线程

3.线程调度

3.1、Java 线程的正确停止

4.线程同步

## 同步问题提出

## 同步和锁定

## 静态方法同步 

## 线程同步小结

线程池


1. 多线程基础概念介绍

进程是程序(任务)的执行过程,它持有资源(共享内存,共享文件)和线程。

分析:

①     执行过程 动态性的,你放在电脑磁盘上的某个eclipse或者QQ文件并不是我们的进程,只有当你双击运行可执行文件,使eclipse或者QQ运行之后,这才称为进程。它是一个执行过程,是一个动态的概念。

②     它持有资源(共享内存,共享文件)和线程:我们说进程是资源的载体,也是线程的载体。这里的资源可以理解为内存。我们知道程序是要从内存中读取数据进行运行的,所以每个进程获得执行的时候会被分配一个内存。

③     线程是什么?

 

如果我们把进程比作一个班级,那么班级中的每个学生可以将它视作一个线程。学生是班级中的最小单元,构成了班级中的最小单位。一个班级有可以多个学生,这些学生都使用共同的桌椅、书籍以及黑板等等进行学习和生活。

在这个意义上我们说:

线程是系统中最小的执行单元;同一进程中可以有多个线程;线程共享进程的资源。

④     线程是如何交互?

就如同一个班级中的多个学生一样,我们说多个线程需要通信才能正确的工作,这种通信,我们称作线程的交互

⑤     交互的方式:互斥、同步

类比班级,就是在同一班级之内,同学之间通过相互的协作才能完成某些任务,有时这种协作是需要竞争的,比如学习,班级之内公共的学习资料是有限的,爱学习的同学需要抢占它,需要竞争,当一个同学使用完了之后另一个同学才可以使用;如果一个同学正在使用,那么其他新来的同学只能等待;另一方面需要同步协作,就好比班级六一需要排演节目,同学需要齐心协力相互配合才能将节目演好,这就是进程交互。

1.1、一个线程的生命周期

线程经过其生命周期的各个阶段。下图显示了一个线程完整的生命周期。

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。

  • 死亡状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

1.2、线程的状态转换图

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

1.3、多线程的优缺点

多线程的优点:

(1)发挥多核CPU的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。

(2)防止阻塞

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

(3)便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

多线程的代价:

1)设计更复杂
虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往往非常复杂。不正确的线程同步产生的错误非常难以被发现,并且重现以修复。

2)上下文切换的开销
当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。

2、创建java多线程

2.1、扩展java.lang.Thread类

这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread.(笔者推荐使用Runable,后头会说明为什么)。下面来看一个简单的实例:

class MyThreat extends Thread{

	private String name;

	public MyThreat(String str) {

		this.name=str;

	}

	public void run() {

		for(int i=0;i<100;i++) {

			System.out.println(this.name+"="+i);

		}

	}

}

public class Demo {

	public static void main(String[] args) {

		MyThreat mta=new MyThreat("线程a");

		MyThreat mtb=new MyThreat("线程b");

		MyThreat mtc=new MyThreat("线程c");

		mta.run();

		mtb.run();

		mtc.run();

	}

}

使用run方法后发现三个线程没有发生抢占,顺序发生,此不是真正的线程,只是仅仅调用run方法。

使用start方法调用,方能发生抢占现象。。。

class MyThreat extends Thread{

	private String name;

	public MyThreat(String str) {

		this.name=str;

	}

	public void run() {

		for(int i=0;i<100;i++) {

			System.out.println(this.name+"="+i);

		}

	}

}

public class Demo {

	public static void main(String[] args) {

		MyThreat mta=new MyThreat("线程a");

		MyThreat mtb=new MyThreat("线程b");

		MyThreat mtc=new MyThreat("线程c");

		mta.start();

		mtb.start();

		mtc.start();

	}

}

说明:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。

从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。

Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。

实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。

但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

  1. Thread1 mTh1=new Thread1("A");  
  2. Thread1 mTh2=mTh1;  
  3. mTh1.start();  
  4. mTh2.start();  

start()方法和run()方法的区别

      通常,系统通过调用线程类的start()方法启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以别JVM调用执行,执行的过程中,JVM通过调用想成类的run()方法来完成实际的操作,当run()方法结束后,线程也就会终止。
    如果直接调用线程类的run()方法,就会被当做一个普通函数调用,程序中仍然只有一个主程序,也就是说start()方法能够异步调用run()方法,但是直接调用run()方法却是同步的,也就无法达到多线程的目的。

2.2、实现java.lang.Runnable接口

class MyThreat implements Runnable{

	private String name;

	public MyThreat(String str) {

		this.name=str;

	}

	public void run() {

		for(int i=0;i<100;i++) {

			System.out.println(this.name+"="+i);

		}

	}

}

public class Demo {

	public static void main(String[] args) {

		MyThreat mta=new MyThreat("线程a");

		MyThreat mtb=new MyThreat("线程b");

		MyThreat mtc=new MyThreat("线程c");

		mta.start();

		mtb.start();

		mtc.start();

	}

}

start方法报错,

原因:因为Runnable接口中没有start方法,start方法只存在于Thread类中,要想启动多线程就必须调用Thread类

由于:public class Thread extends Object implements Runnable,

Thread实现了Runnalbe,而且Thread类中有一个构造方法为:Thread(Runnable target) 分配新的 Thread 对象。

class MyThreat implements Runnable{

private String name;

public MyThreat(String str) {

this.name=str;

}

public void run() {

for(int i=0;i<100;i++) {

System.out.println(this.name+"="+i);

}

}

}



public class Demo {

public static void main(String[] args) {

MyThreat mta=new MyThreat("线程a");

MyThreat mtb=new MyThreat("线程b");

MyThreat mtc=new MyThreat("线程c");

new Thread(mta).start();

new Thread(mtb).start();

new Thread(mtc).start();

}

}

说明:

Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

2.3、Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

2.4、实现Callable接口通过FutureTask包装器来创建Thread线程

引入原因:虽然Runnable完爆Thread,但是它没有返回结果

初步:

class MyThreat implements Callable<String>{

	private int ticket=10;

	public String call() throws Exception{

		for(int i=0;i<100;i++) {

			if(this.ticket>0) {

				System.out.println("卖票 ="+this.ticket);

			}

		}

		return "票已经卖光了";

	}

}

public class Demo {

	public static void main(String[] args) {

		MyThreat mt=new MyThreat();

		new Thread(mt).start();

		new Thread(mt).start();

		new Thread(mt).start();

		

	}

}

提示错误,没有此种方式

解决方法:由于FutureTask类里面有一个构造方法

FutureTask(Callable<V> callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable,接受的目的只有一个,就是取得call()方法的返回值

public interface RunnableFuture<V>extends Runnable, Future<V>

class MyThreat implements Callable<String>{

	private int ticket=10;

	public String call() throws Exception{

		for(int i=0;i<100;i++) {

			if(this.ticket>0) {

				System.out.println("卖票 ="+this.ticket--);

			}

		}

		return "票已经卖光了";

	}

}

public class Demo {

	public static void main(String[] args) {

		MyThreat mta=new MyThreat();

		MyThreat mtb=new MyThreat();

		FutureTask<String> task1=new FutureTask<>(mta);

		FutureTask<String> task2=new FutureTask<>(mtb);

		//因为FutureTask是Runnable接口的子类,所以可以使用Thread类的构造来接受Task对象

		new Thread(task1).start();

		new Thread(task2).start();

		

	}

}

3.线程调度

线程的调度

1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。

Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

static int MAX_PRIORITY

          线程可以具有的最高优先级,取值为10。

static int MIN_PRIORITY

          线程可以具有的最低优先级,取值为1。

static int NORM_PRIORITY

          分配给线程的默认优先级,取值为5。

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。

JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

7、获取线程名称:对于线程名字一般而言在其启动之前进行定义,不建议对已经启动的线程进行更改名字,还活着为不同的线程进行重名的情况

     1:设置名字:public final void setName(String name);

     2:取得名字:public final String getName();

     对于线程的名字的操作会出现一个问题,这些方法都是于Thread类里面的,可是如果换回到类(Runnable)子类,这个类并没有继承Thread类,如果想要去的名字,那么能够取得的就是当前执行本方法的线程名字,所以在Thread类里面提供了一个方法:

     取得当前线程对象:public static Thread currentThread();;

     注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

3.1、Java 线程的正确停止

有三种方法可以使终止线程。 

    1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 

    2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。 

    3.  使用interrupt方法中断线程。 
1. 使用退出标志终止线程 

    当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。 在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使 while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环 是否退出。下面给出了一个利用退出标志终止线程的例子。 

package chapter2; 

public class ThreadFlag extends Thread 

    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    } 
    public static void main(String[] args) throws Exception 
    { 
        ThreadFlag thread = new ThreadFlag(); 
        thread.start(); 
        sleep(5000); // 主线程延迟5秒 
        thread.exit = true;  // 终止线程thread 
        thread.join(); 
        System.out.println("线程退出!"); 
    } 


    在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个 Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值, 

    2. 使用stop方法终止线程 

    使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程: 

thread.stop(); 

    虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。 

    3. 使用interrupt方法终止线程 

    使用interrupt方法来终端线程可分为两种情况: 

    (1)线程处于阻塞状态,如使用了sleep方法。 

    (2)使用while(!isInterrupted()){……}来判断线程是否被中断。 

    在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种情况下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。 

package chapter2; 

public class ThreadInterrupt extends Thread 

    public void run() 
    { 
        try 
        { 
            sleep(50000);  // 延迟50秒 
        } 
        catch (InterruptedException e) 
        { 
            System.out.println(e.getMessage()); 
        } 
    } 
    public static void main(String[] args) throws Exception 
    { 
        Thread thread = new ThreadInterrupt(); 
        thread.start(); 
        System.out.println("在50秒之内按任意键中断线程!"); 
        System.in.read(); 
        thread.interrupt(); 
        thread.join(); 
        System.out.println("线程已经退出!"); 
    } 

   上面代码的运行结果如下: 
    在50秒之内按任意键中断线程! 
    sleep interrupted 
    线程已经退出! 
    在调用interrupt方法后, sleep方法抛出异常,然后输出错误信息:sleep interrupted. 
    注意:在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方 法isInterrupted(),这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted可以用来判断其他 线程是否被中断。因此,while (!isInterrupted())也可以换成while (!Thread.interrupted())。 

4.线程同步

## 同步问题提出

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。

 1 public class Foo { 
 2     private int x = 100; 
 3 
 4     public int getX() { 
 5         return x; 
 6     } 
 7 
 8     public int fix(int y) { 
 9         x = x - y; 
10         return x; 
11     } 
12 }




 1 public class MyRunnable implements Runnable { 
 2     private Foo foo = new Foo(); 
 3 
 4     public static void main(String[] args) { 
 5         MyRunnable r = new MyRunnable(); 
 6         Thread ta = new Thread(r, "Thread-A"); 
 7         Thread tb = new Thread(r, "Thread-B"); 
 8         ta.start(); 
 9         tb.start(); 
10     } 
11 
12     public void run() { 
13         for (int i = 0; i < 3; i++) { 
14             this.fix(30); 
15             try { 
16                 Thread.sleep(1); 
17             } catch (InterruptedException e) { 
18                 e.printStackTrace(); 
19             } 
20             System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX()); 
21         } 
22     } 
23 
24     public int fix(int y) { 
25         return foo.fix(y); 
26     } 
27 }

运行结果:

Thread-A : 当前foo对象的x值= 40 
Thread-B : 当前foo对象的x值= 40 
Thread-B : 当前foo对象的x值= -20 
Thread-A : 当前foo对象的x值= -50 
Thread-A : 当前foo对象的x值= -80 
Thread-B : 当前foo对象的x值= -80 

从结果发现,这样的输出值明显是不合理的。原因是两个线程不加控制的访问Foo对象并修改其数据所致。

如果要保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。

使用关键字synchronized关键字

1:一种是同步代码块

class MThread implements Runnable{

	private int tickets=5;//五张票,由五个贩子售出

	public void run() {

		for(int i=0;i<5;i++) {

			synchronized(this) {//一般锁的都是当前对象

				if(this.tickets>0) {

					try {

						Thread.sleep(1000);

					} catch (InterruptedException e) {

						e.printStackTrace();

					}

					System.out.println(Thread.currentThread().getName()+"售出第"+this.tickets--+"张票");

				}

			}

		}

	}

}

public class TongBu {

	public static void main(String[] args) {

		MThread mta=new MThread();

		//因为不是Thread类,因此需要Thread类的构造方法来调用start方法

		new Thread(mta,"A").start();//五个线程代表五个人贩子

		new Thread(mta,"B").start();

		new Thread(mta,"C").start();

		new Thread(mta,"D").start();

		new Thread(mta,"E").start();

	}

}

2:另外一种时同步方法

class myhread implements Runnable{

	private int tickets=5;//五张票,由五个贩子售出

	public void run() {

		for(int i=0;i<5;i++) {

			sale();

		}    

	}

	public synchronized void sale() {

		synchronized(this) {//一般锁的都是当前对象

			if(this.tickets>0) {

				try {

					Thread.sleep(1000);

				} catch (InterruptedException e) {

					e.printStackTrace();

				}

				System.out.println(Thread.currentThread().getName()+"售出第"+this.tickets--+"张票");

			}

		}

	}

}

public class TongBu1 {

	public static void main(String[] args) {

		myhread mta=new myhread();

		//因为不是Thread类,因此需要Thread类的构造方法来调用start方法

		new Thread(mta,"A").start();//五个线程代表五个人贩子

		new Thread(mta,"B").start();

		new Thread(mta,"C").start();

		new Thread(mta,"D").start();

		new Thread(mta,"E").start();

	}

}

3、lock()

JDK5新增加Lock接口以及它的一个实现类ReentrantLock(重入锁),也可以实现多线程的同步;
    lock():以阻塞的方式获取锁,也就是说,如果获取到了锁,就会执行,其他线程需要等待,unlock()锁后别的线程才能执行,如果别的线程持有锁,当前线程等待,直到获取锁后返回。

public int consume(){
        int m = 0;
        try {
            lock.lock();
            while(ProdLine.size() == 0){
                System.out.println("队列是空的,请稍候");
                empty.await();
            }
            m = ProdLine.removeFirst();
            full.signal(); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
            return m;
        }
    }

在java里面有四种代块:普通代块,构造代码块,静态代码块,同步代码块

同步操作与异步操作相比,异步操作速度高于同步操作吗,但是同步操作时数据安全性较高,

## 同步和锁定

1、锁的原理

Java中每个对象都有一个内置锁

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

 

关于锁和同步,有一下几个要点:

1)、只能同步方法,而不能同步变量和类;

2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?

3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放。

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:

  

public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }

当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:

   public synchronized int getX() {
        return x++;
    }

 public int getX() {
        synchronized (this) {
            return x;
        }
    }

效果是完全一样的。

## 静态方法同步 

要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。

例如:

public static synchronized int setName(String name){
      Xxx.name = name;
}

等价于

public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

## 线程同步小结

1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。

2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。

3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。

4、对于同步,要时刻清醒在哪个对象上同步,这是关键。

5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。

6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。

7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

线程池

       第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 
  第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 
  第三:提高线程的可管理性。

sleep()方法与wait()方法有什么区别?
    sleep()是使线程暂停执行一段时间的方法。wait()也是一种使线程暂停执行的方法,直到被唤醒或等待时间超时。
    区别:1)、原理不同:sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,它会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到时间一到,此线程会自动“苏醒”。
    wait()方法是Object类的方法,用于线程间通讯,这个方法会使当前线程拥有该对象锁的进程等待,直到其他线程调用notify()方法(或notifyAll方法)时才“醒”来,不过开发人员可可以给它指定一个时间,自动“醒”来。与wait()方法配套的方法还有notify()和notifyAll()方法。
    2)、对锁的处理机制不同。由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通讯,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,调用后会释放掉他所占用的锁,从而使线程所在对象中的其他synchronized数据可被别的线程使用。
    3)、使用区域不同,由于wait()的特殊意义,因此它必须放在同步控制方法或者同步代码块中使用,而sleep()则可以放在任何地方使用。
    4)、sleep()方法 必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用它的interrupt(),产生InterruptedException异常。
    sleep不会释放“锁标志”,容易导致死锁问题的发生,因此,一般情况下,不推荐使用sleep()方法。而推荐使用wait()方法

参考:

https://www.cnblogs.com/Qian123/p/5670304.html(与其说参考不如说原文,写的是在太详细了,我也没什么加的^_^,感谢)

https://www.cnblogs.com/luxiaoxun/p/3870265.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值