Java多线程(二)(java多线程用法)

  本篇文章为你整理了Java多线程(二)(java多线程用法)的详细内容,包含有java多线程简单实例 java多线程用法 java多线程实现原理 java多线程基础知识 Java多线程(二),希望能帮助你了解 Java多线程(二)。

  Java多线程(二)

  目录Java多线程(二)四、线程的同步4.1 线程同步的引入:4.2 线程同步的方式之一:同步代码块4.3 线程同步的方式之二:同步方法4.4 同步的优势与局限:4.5 线程安全的单例模式之懒汉式4.6 同步锁机制:4.7 释放锁的操作:4.8 不会释放锁的操作:4.9 线程的死锁问题4.10 线程同步的方式之三:Lock锁4.11 (简单介绍)公平锁和非公平锁

  四、线程的同步

  4.1 线程同步的引入:

  多线程出现了安全问题。

  问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。例如:买票问题、银行卡消费问题等等。

  解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

  ​ 所以,Java 对于多线程的安全问题提供了专业的解决方式:同步机制。

  4.2 线程同步的方式之一:同步代码块

  语法格式:

  

synchronized (对象/同步监视器){ //得到对象的锁,才能操作同步代码

 

   // 需要被同步的代码

  

 

  说明:

  ​ (1)操作共享数据的代码,即为需要被同步的代码。 -- 不能包含代码多了,也不能包含代码少了。

  ​ (2)共享数据:多个线程共同操作的变量。

  ​ (3)同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

  ​ (4)要求:多个线程必须要共用同一把锁。

  使用同步代码块解决在实现 Runnable 接口的方式创建多线程的线程安全问题

  

// 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式

 

  class Window1 implements Runnable{

   private int ticket = 100;

  // Object obj = new Object();

   @Override

   public void run() {

  // Object obj = new Object();

   while(true){

   // 同步代码块---begin

   synchronized (this){ // 此时的this:唯一的 Window1 的对象w //方式二:synchronized (object) {

   if (ticket 0) {

   try {

   Thread.sleep(100);

   } catch (InterruptedException e) {

   e.printStackTrace();

   System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

   ticket--;

   } else {

   break;

   // 同步代码块---end

  public class WindowTest1 {

   public static void main(String[] args) {

   Window1 w = new Window1();

   Thread t1 = new Thread(w);

   Thread t2 = new Thread(w);

   Thread t3 = new Thread(w);

   t1.setName("窗口1");

   t2.setName("窗口2");

   t3.setName("窗口3");

   t1.start();

   t2.start();

   t3.start();

  

 

  使用同步代码块解决继承 Thread 类的方式创建多线程的线程安全问题

  

class Window2 extends Thread{

 

   private static int ticket = 100;

  // private static Object obj = new Object();

   @Override

   public void run() {

   while(true){

   synchronized (Window2.class){// Window2.class表示window2这一个类,只会加载一次

  // 方式二:synchronized (obj){

  // synchronized (this){ 错误的方式:this分别代表着t1,t2,t3三个对象

   if(ticket 0){

   try {

   Thread.sleep(100);

   } catch (InterruptedException e) {

   e.printStackTrace();

   System.out.println(getName() + ":卖票,票号为:" + ticket);

   ticket--;

   }else{

   break;

  public class WindowTest2 {

   public static void main(String[] args) {

   Window2 t1 = new Window2();

   Window2 t2 = new Window2();

   Window2 t3 = new Window2();

   t1.setName("窗口1");

   t2.setName("窗口2");

   t3.setName("窗口3");

   t1.start();

   t2.start();

   t3.start();

  

 

  4.3 线程同步的方式之二:同步方法

  
同步方法:即将操作共享数据的代码完整的声明在一个方法中,将该方法声明为同步方法。

  


// 将synchronized放在方法声明中,一般放在权限符和返回类型之间,表示整个方法为同步方法

 

  public synchronized void 方法名 (String name){

   // 需要被同步的代码

  

 

  说明:

  ​ (1)同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

  ​ (2)非静态的同步方法,同步监视器是:this。

  ​ (3)静态的同步方法,同步监视器是:当前类本身。

  使用同步方法解决在实现 Runnable 接口的方式创建多线程的线程安全问题

  

class Window3 implements Runnable {

 

   private int ticket = 100;

   @Override

   public void run() {

   while (true) {

   show();

   private synchronized void show(){ //同步监视器:this

   if (ticket 0) {

   try {

   Thread.sleep(100);

   } catch (InterruptedException e) {

   e.printStackTrace();

   System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

   ticket--;

  public class WindowTest3 {

   public static void main(String[] args) {

   Window3 w = new Window3();

   Thread t1 = new Thread(w);

   Thread t2 = new Thread(w);

   Thread t3 = new Thread(w);

   t1.setName("窗口1");

   t2.setName("窗口2");

   t3.setName("窗口3");

   t1.start();

   t2.start();

   t3.start();

  

 

  使用同步方法解决继承 Thread 类的方式创建多线程的线程安全问题

  

class Window4 extends Thread {

 

   private static int ticket = 100;

   @Override

   public void run() {

   while (true) {

   show();

   private static synchronized void show(){ //同步监视器:Window4.class

   //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的

   if (ticket 0) {

   try {

   Thread.sleep(100);

   } catch (InterruptedException e) {

   e.printStackTrace();

   System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

   ticket--;

  
通过上面四个售票例子可以看出:

  在实现 Runnable 接口创建多线程的方式中,我们可以考虑使用 this 充当同步监视器。

  而在继承Thread类创建多线程的方式一般不使用 this 充当同步监视器,因为每个线程的 this 为该线程的实例对象,不满足多个线程共用一把锁,所以一般考虑用当前类本身充当。

  4.4 同步的优势与局限:

  
局限: 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。也可能会造成死锁问题。

  
方式二效率更高的原因:

   不需要每个线程都要进去同步方法里面去,可能只有最前面几个进程进入同步方法。

   而方式一是所有线程都要进入同步方法导致效率较低。

  

 

  4.6 同步锁机制:

  
同步机制中的锁在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

  
必须确保使用同一个资源的多个线程共用一把锁,否则就无法保证共享资源的安全 。

  一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)。

  
当前线程的同步方法、同步代码块执行结束。

  当前线程在同步代码块、同步方法中遇到 break、return 终止了该代码块、该方法的继续执行。

  当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束。

  当前线程在同步代码块、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁。

  4.8 不会释放锁的操作:

  
线程执行同步代码块或同步方法时,程序调用 Thread.sleep()、 Thread.yield() 方法暂停当前线程的执行。

  
线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁(同步监视器)。

  应尽量避免使用suspend()和resume()来控制线程

  
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

  出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

  
public synchronized void foo(B b) { //同步监视器:A类的对象:a

   System.out.println("当前线程名: " + Thread.currentThread().getName()

   + " 进入了A实例的foo方法"); // ①

  // try {

  // Thread.sleep(200);

  // } catch (InterruptedException ex) {

  // ex.printStackTrace();

   System.out.println("当前线程名: " + Thread.currentThread().getName()

   + " 企图调用B实例的last方法"); // ③

   b.last();

   public synchronized void last() {//同步监视器:A类的对象:a

   System.out.println("进入了A类的last方法内部");

  class B {

   public synchronized void bar(A a) {//同步监视器:b

   System.out.println("当前线程名: " + Thread.currentThread().getName()

   + " 进入了B实例的bar方法"); // ②

  // try {

  // Thread.sleep(200);

  // } catch (InterruptedException ex) {

  // ex.printStackTrace();

   System.out.println("当前线程名: " + Thread.currentThread().getName()

   + " 企图调用A实例的last方法"); // ④

   a.last();

   public synchronized void last() {//同步监视器:b

   System.out.println("进入了B类的last方法内部");

  public class DeadLock implements Runnable {

   A a = new A();

   B b = new B();

   public void init() {

   Thread.currentThread().setName("主线程");

   // 调用a对象的foo方法

   a.foo(b);

   System.out.println("进入了主线程之后");

   public void run() {

   Thread.currentThread().setName("副线程");

   // 调用b对象的bar方法

   b.bar(a);

   System.out.println("进入了副线程之后");

   public static void main(String[] args) {

   DeadLock dl = new DeadLock();

   new Thread(dl).start();

   dl.init();

  

 

 

  
注意:

  不是程序运行成功就说明程序里面没有死锁问题,很多时候出现死锁是一个概率问题。在开发中应尽量避免死锁情况。

  
从 JDK 5.0 开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用 Lock 对象充当。

  
java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象。

  
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

  
Lock 是显式锁(手动开启和关闭锁),synchronized 是隐式锁,出了作用域自动释放。

  Lock 只有代码块锁,synchronized 有代码块锁和方法锁。

  使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。

  
Lock —— 同步代码块(已经进入了方法体,分配了相应资源) —— 同步方法(在方法体之外)

  4.11 (简单介绍)公平锁和非公平锁

  
公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。

  非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。

  
// ReentrantLock当中的lock()方法,是通过static内部类sync来进行锁操作

  public void lock()

   sync.lock();

  -------------------------------------------------------------------

  //定义成final型的成员变量,在构造方法中进行初始化

  private final Sync sync;

  //无参数默认非公平锁

  public ReentrantLock()

   sync = new NonfairSync();

  //根据参数初始化为公平锁或者非公平锁

  public ReentrantLock(boolean fair)

   sync = fair ? new FairSync() : new NonfairSync();

  

 

 

  以上就是Java多线程(二)(java多线程用法)的详细内容,想要了解更多 Java多线程(二)的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: