Java 多线程 synchronized同步

一、实现线程的方式有两种:

1、继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。

2、实现java.lang.Runnable接口,实现它的run()方法,并将线程的执行主体放入其中。

==多线程的执行逻辑: ================================================

当主线程被挂起时, 其它就绪的线程会根据选择最高优先级的来执行;
当主线程的挂起时间 > 子线程的执行时间时,子线程执行完后回到主线程,等待主线程醒来.
当主线程的挂起时间 < 子线程的执行时间时,主线程挂起时间到的,自动醒来,回到主线程,此时可以判断子线程是否存在,若有,可stop之.

=================================================================

二、两种方式的区别:
1、继承Thread类的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。

2、Runnable这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种方式。

3、两种实现线程的方式在启动时会有所不同。

              ThreadTest tt = new ThreadTest();   
              // 启动线程   
              tt.start(); 

              // 创建一个线程实例   
              Thread t = new Thread(new RunnableTest());   
              // 启动线程   
              t.start();   

 

注:run()方法中包含的是线程的主体,也就是这个线程被启动后将要运行的代码,它跟线程的启动没有任何关系。)

三、线程的状态:

在Java 1.4及以下的版本中,每个线程都具有新建、可运行、阻塞、死亡四种状态,但是在Java 5.0及以上版本中,线程的状态被扩充为新建、可运行、阻塞、等待、定时等待、死亡六种。线程的状态完全包含了一个线程从新建到运行,最后到结束的整个生命周期。

线程状态的具体信息如下:

1. NEW(新建状态、初始化状态):

线程对象已经被创建,但是还没有被启动时的状态。这段时间就是在我们调用new命令之后,调用start()方法之前。


2. RUNNABLE(可运行状态、就绪状态):

在我们调用了线程的start()方法之后线程所处的状态。处于RUNNABLE状态的线程在JAVA虚拟机(JVM)上是运行着的,但是它可能还正在等待操作系统分配给它相应的运行资源以得以运行。


3. BLOCKED(阻塞状态、被中断运行):

线程正在等待其它的线程释放同步锁,以进入一个同步块或者同步方法继续运行;或者它已经进入了某个同步块或同步方法,在运行的过程中它调用了某个对象继承自java.lang.Object的wait()方法,正在等待重新返回这个同步块或同步方法。


4. WAITING(等待状态):

当前线程调用了java.lang.Object.wait()、 java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三个中的任意一个方法,正在等待另外一个线程执行某个操作。比如一个线程调用了某个对象的wait()方法,正在等待其它线程调用这个对象的notify() 或者notifyAll()(这两个方法同样是继承自Object类)方法来唤醒它;或者一个线程调用了另一个线程的join()(这个方法属于 Thread类)方法,正在等待这个方法运行结束。


5. TIMED_WAITING(定时等待状态):

当前线程调用了 java.lang.Object.wait(long timeout)、java.lang.Thread.join(long millis)、java.util.concurrent.locks.LockSupport.packNanos(long nanos)、java.util.concurrent.locks.LockSupport.packUntil(long deadline)四个方法中的任意一个,进入等待状态,但是与WAITING状态不同的是,它有一个最大等待时间,即使等待的条件仍然没有满足,只要到了这个时间它就会自动醒来。


6. TERMINATED(死亡状态、终止状态):

线程完成执行后的状态。线程执行完run()方法中的全部代码,从该方法中退出,进入TERMINATED状态。还有一种情况是run()在运行过程中抛出了一个异常,而这个异常没有被程序捕获,导致这个线程异常终止进入TERMINATED状态。

 

四、如下实例:

项目结构:

 

MultiThread,java

package com;

import java.io.IOException;


//多线程编程
public class MultiThread {
	
        public static void main(String args[]){
        	
                System.out.println("我是主线程!");
                
                //下面创建线程实例thread1
                ThreadUseExtends thread1=new ThreadUseExtends();
                
                //创建thread2时以实现了Runnable接口的THhreadUseRunnable类实例为参数
                Thread thread2=new Thread(new ThreadUseRunnable(),"SecondThread");
                
                //启动线程thread1使之处于就绪状态
                thread1.start();
                
                //优先级将决定cpu空出时,处于就绪状态的线程谁先占领cpu开始运行
                //优先级范围1到10,MIN_PRIORITY,MAX_PRIORITY,NORM_PAIORITY
                //新线程继承创建她的父线程优先级,父线程通常有普通优先级即5NORM_PRIORITY
                //设置thread1的优先级为6
                //thread1.setPriority(6);
            
                System.out.println("主线程将挂起7秒!");
                try {
                	//主线程挂起7秒
                    Thread.sleep(7000);
                }
                catch (InterruptedException e){
                    return;
                }
                System.out.println("又回到了主线程!");
                if(thread1.isAlive()){    
                	//如果thread1还存在则杀掉他
                    thread1.stop();
                    System.out.println("thread1休眠过长,主线程杀掉了thread1!");
                }else{
                    System.out.println("主线程没发现thread1,thread1已醒顺序执行结束了!");
                }
                
                //启动thread2
                thread2.start();
                
                System.out.println("主线程又将挂起7秒!");
                try {
                    Thread.sleep(7000);//主线程挂起7秒
                }
                catch (InterruptedException e){
                    return;
                }
                System.out.println("又回到了主线程!");
                if(thread2.isAlive()){    
                	//如果thread2还存在则杀掉他
                    thread2.stop();
                    System.out.println("thread2休眠过长,主线程杀掉了thread2!");
                }else{
                    System.out.println("主线程没发现thread2,thread2已醒顺序执行结束了!");
                }
                System.out.println("程序结束按任意键继续!");
                try {
                    System.in.read();
                }catch (IOException e){
                    System.out.println(e.toString());
                }             
        }
}


ThreadUseExtends.java

package com;

public class ThreadUseExtends extends Thread {
	
	/*通过继承Thread类,并实现它的抽象方法run()
	 * 适当时候创建这一Thread子类的实例来实现多线程机制
	 *一个线程启动后(也即进入就绪状态)一旦获得CPU将自动调用它的run()方法
	 */
	
	
    //构造函数
    ThreadUseExtends(){
    	
    }

    //如果该run()方法顺序执行完了,线程将自动结束,而不会被主线程杀掉
    //但如果休眠时间过长,则线程还存活,可能被stop()杀掉
    public void run(){
        System.out.println("我是Thread子类的线程实例!");
        System.out.println("我将挂起10秒!");
        System.out.println("回到主线程,请稍等,刚才主线程挂起可能还没醒过来!");
        
        try{
            sleep(10000);//挂起5秒
        }
        catch (InterruptedException e){
            return;
        }
    }
}


ThreadUseRunnable.java

package com;


public class ThreadUseRunnable implements Runnable {

	/*通过实现Runnable接口中的run()方法,再以这个实现了run()方法的类
	 * 为参数创建Thread的线程实例 
	 */
	
	
	// 以这个实现了Runnable接口中run()方法的类为参数创建Thread类的线程实例
	// Thread thread2=new Thread(this);	
	
	// 构造函数
	ThreadUseRunnable() {
	}

	
	// 如果该run()方法顺序执行完了,线程将自动结束,而不会被主线程杀掉
	// 但如果休眠时间过长,则线程还存活,可能被stop()杀掉
	public void run() {
		
		System.out.println("我是Thread类的线程实例并以实现了Runnable接口的类为参数!");
		System.out.println("我将挂起1秒!");
		System.out.println("回到主线程,请稍等,刚才主线程挂起可能还没醒过来!");
		
		try {
			Thread.sleep(1000);// 挂起5秒
		} catch (InterruptedException e) {
			return;
		}	
	}
}//该程序可做的修改如改休眠时间或优先级setPriority() 

 

五、程序结果

 

七、多线程安全问题

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

                  要想解决“脏数据”的问题,最简单的方法就是使用synchronized关键字来使run方法同步。

1、synchronized的使用:

public synchronized void run(){
}

从上面的代码可以看出,只要在void和public之间加上synchronized关键字,就可以使run方法同步,也就是说,对于同一个Java类的对象实例,run方法同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。

 

2、sychronized关键字只和一个对象实例绑定

class Test {
        public synchronized void method(){
            
       }
  }
   
  public class Sync implements Runnable{
       private Test test;
       public void run(){
            test.method();
       }
       public Sync(Test test){
           this.test = test;
       }
       public static void main(String[] args) throws Exception{
           Test test1 =  new Test();
           Test test2 =  new Test();
           Sync sync1 = new Sync(test1);
           Sync sync2 = new Sync(test2);
           new Thread(sync1).start();
           new Thread(sync2).start(); 
       }
   }


从上面的代码可以看出,在Test类中的method方法是同步的。但上面的代码建立了两个Test类的实例,因此,test1和test2的method方法是分别执行的。要想让method同步,必须在建立Sync类的实例时向它的构造方法中传入同一个Test类的实例。

如下代码所示:

Sync sync1 = new Sync(test1);

 

3、不仅可以使用synchronized来同步非静态方法,也可以使用synchronized来同步静态方法。

     若用以下方式来定义method方法:

class Test {
    public static synchronized void method() {   
    }
} 

     若用以下方式来建立Test类的对象:

Test test = new Test();

从上面的代码可以看出,对于静态方法来说,只要加上了synchronized关键字,这个方法就是同步的,无论是使用test.method(),还是使用Test.method()来调用method方法,method都是同步的,并不存在非静态方法的多个实例的问题。

4、在23种设计模式中的单件(Singleton)模式如果按传统的方法设计,也是线程不安全的。

      下面的代码是一个线程安全的单件模式:

Singleton.java

package com.SingletonMulti;

//线程安全的Singleton模式
public class Singleton {
	
    private static Singleton sample;

    private Singleton() {
    	
    }
    public static Singleton getInstance() {
    	
        if (sample == null) {
        	
        	// 为了放大Singleton模式的线程不安全性
            Thread.yield(); 
            
            sample = new Singleton();
        }
        
        return sample;
        
    }
    
}

MyThread.java

package com.SingletonMulti;

public class MyThread extends Thread{
	
	public void run() {
		
        Singleton singleton = Singleton.getInstance();
        
        System.out.println(singleton.hashCode());
        
    }
	
    public static void main(String[] args) {
    	
        Thread threads[] = new Thread[5];
        
        for (int i = 0; i < threads.length; i++)
        	
            threads[i] = new MyThread();
        
        for (int i = 0; i < threads.length; i++)
        	
            threads[i].start();
    }

}


在上面的代码调用yield方法是为了使单件模式的线程不安全性表现出来,如果将这行去掉,上面的实现仍然是线程不安全的,只是出现的可能性小得多。

程序的运行结果如下:
 
上面的运行结果可能在不同的运行环境上有所有同,但一般这五行输出不会完全相同。
从这个输出结果可以看出,通过getInstance方法得到的对象实例是五个,而不是我们期望的一个。这是因为当一个线程执行了Thread.yield()后,就将CPU资源交给了另外一个线程。由于在线程之间切换时并未执行到创建Singleton对象实例的语句,因此,这几个线程都通过了if判断,所以,就会产生了建立五个对象实例的情况(可能创建的是四个或三个对象实例,这取决于有多少个线程在创建Singleton对象之前通过了if判断,每次运行时可能结果会不一样)。
要想使上面的单件模式变成线程安全的,只要为getInstance加上synchronized关键字即可。
代码如下:
public static synchronized Singleton getInstance() {   
}

当然,还有更简单的方法,就是在定义Singleton变量时就建立Singleton对象,代码如下:

private static final Singleton sample = new Singleton(); 


然后在getInstance方法中直接将sample返回即可。这种方式虽然简单,但不知在getInstance方法中创建Singleton对象灵活。读者可以根据具体的需求选择使用不同的方法来实现单件模式。

在使用synchronized关键字时有以下四点需要注意:
    1.  synchronized关键字不能继承。
      虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
    在子类方法中加上synchronized关键字
class Parent{
    public synchronized void method(){   
    }
}
class Child extends Parent{
    public synchronized void method(){  
    }
}
    在子类方法中调用父类的同步方法
class Parent{
    public synchronized void method(){   
    }
}
class Child extends Parent{
    public void method() {
       super.method();   
    }
}

2.  在定义接口方法时不能使用synchronized关键字。
3.  构造方法不能使用synchronized关键字,但可以使用下节要讨论的synchronized块来进行同步。
4.  synchronized可以自由放置。
      在前面的例子中使用都是将synchronized关键字放在方法的返回类型前面。但这并不是synchronized可放置唯一位置。在非静态方法中, synchronized还可以放在方法定义的最前面,在静态方法中,synchronized可以放在static的前面。
代码如下:
public synchronized void method();
synchronized public void method();
public static synchronized void method();
public synchronized static void method();
synchronized public static void method();


但要注意,synchronized不能放在方法返回类型的后面,如下面的代码是错误的:

public void synchronized method();
public static void synchronized method();


synchronized关键字只能用来同步方法,不能用来同步类变量,如下面的代码也是错误的。

<span style="color:#000000;">public synchronized int n = 0;
public static synchronized int n = 0;</span>

虽然使用synchronized关键字同步方法是最安全的同步方式,但大量使用synchronized关键字会造成不必要的资源消耗以及性能损失。虽然从表面上看synchronized锁定的是一个方法,但实际上synchronized锁定的是一个类。也就是说,如果在非静态方法method1和 method2定义时都使用了synchronized,在method1未执行完之前,method2是不能执行的。静态方法和非静态方法的情况类似。但静态和非静态方法不会互相影响。

看看如下的代码:

<pre class="java" name="code">package com.SingletonMulti;

public class MyThread1 extends Thread{
	public String methodName;

    public static void method(String s){
        System.out.println(s);
        while (true);
    }
    public synchronized void method1(){
        method("非静态的method1方法");
    }
    public synchronized void method2(){
        method("非静态的method2方法");
    }
    public static synchronized void method3(){
        method("静态的method3方法");
    }
    public static synchronized void method4(){
        method("静态的method4方法");
    }
    public void run(){
        try{
            getClass().getMethod(methodName, null).invoke(this, null);
        }catch (Exception e){
         }
    }
    public static void main(String[] args) throws Exception{
        MyThread1 myThread1 = new MyThread();
        for (int i = 1; i <= 4; i++){
            myThread1.methodName = "method" + String.valueOf(i);
            new Thread(myThread1).start();
            sleep(100);
        }
    }

}
 

运行结果如下:

 

从上面的运行结果可以看出,method2和method4在method1和method3未结束之前不能运行。因此,我们可以得出一个结论,如果在类中使用synchronized关键字来定义非静态方法,那将影响这个中的所有使用synchronized关键字定义的非静态方法。如果定义的是静态方法,那么将影响类中所有使用synchronized关键字定义的静态方法。这有点象数据表中的表锁,当修改一条记录时,系统就将整个表都锁住了,因此,大量使用这种同步方式会使程序的性能大幅度下降。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值