一、实现线程的方式有两种:
1、继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。
2、实现java.lang.Runnable接口,实现它的run()方法,并将线程的执行主体放入其中。
==多线程的执行逻辑: ================================================
当主线程被挂起时, 其它就绪的线程会根据选择最高优先级的来执行;
当主线程的挂起时间 > 子线程的执行时间时,子线程执行完后回到主线程,等待主线程醒来.
当主线程的挂起时间 < 子线程的执行时间时,主线程挂起时间到的,自动醒来,回到主线程,此时可以判断子线程是否存在,若有,可stop之.
=================================================================
二、两种方式的区别:
1、继承Thread类的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。
2、Runnable这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种方式。
3、两种实现线程的方式在启动时会有所不同。
#
#
#
#
#
#
(注: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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71package 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34package 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的使用:
1
2public synchronized void run(){ }
从上面的代码可以看出,只要在void和public之间加上synchronized关键字,就可以使run方法同步,也就是说,对于同一个Java类的对象实例,run方法同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。
2、sychronized关键字只和一个对象实例绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class 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类的实例。
如下代码所示:
1Sync sync1 = new Sync(test1);
3、不仅可以使用synchronized来同步非静态方法,也可以使用synchronized来同步静态方法。
若用以下方式来定义method方法:
1
2
3
4
5class Test { public static synchronized void method() { } }
若用以下方式来建立Test类的对象:
1Test test = new Test();
从上面的代码可以看出,对于静态方法来说,只要加上了synchronized关键字,这个方法就是同步的,无论是使用test.method(),还是使用Test.method()来调用method方法,method都是同步的,并不存在非静态方法的多个实例的问题。
4、在23种设计模式中的单件(Singleton)模式如果按传统的方法设计,也是线程不安全的。
下面的代码是一个线程不安全的单件模式:
Singleton.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package 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方法是为了使单件模式的线程不安全性表现出来,如果将这行去掉,上面的实现仍然是线程不安全的,只是出现的可能性小得多。

1
2public static synchronized Singleton getInstance() { }
当然,还有更简单的方法,就是在定义Singleton变量时就建立Singleton对象,代码如下:
1
2private static final Singleton sample = new Singleton();
然后在getInstance方法中直接将sample返回即可。这种方式虽然简单,但不知在getInstance方法中创建Singleton对象灵活。读者可以根据具体的需求选择使用不同的方法来实现单件模式。
1
2
3
4
5
6
7
8class Parent{ public synchronized void method(){ } } class Child extends Parent{ public synchronized void method(){ } }
1
2
3
4
5
6
7
8
9class Parent{ public synchronized void method(){ } } class Child extends Parent{ public void method() { super.method(); } }
2.
1
2
3
4
5public synchronized void method(); synchronized public void method(); public static synchronized void method(); public synchronized static void method(); synchronized public static void method();
但要注意,synchronized不能放在方法返回类型的后面,如下面的代码是错误的:
1
2public void synchronized method(); public static void synchronized method();
synchronized关键字只能用来同步方法,不能用来同步类变量,如下面的代码也是错误的。
1
2<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是不能执行的。静态方法和非静态方法的情况类似。但静态和非静态方法不会互相影响。
看看如下的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<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); } } }
1
运行结果如下:
从上面的运行结果可以看出,method2和method4在method1和method3未结束之前不能运行。因此,我们可以得出一个结论,如果在类中使用synchronized关键字来定义非静态方法,那将影响这个中的所有使用synchronized关键字定义的非静态方法。如果定义的是静态方法,那么将影响类中所有使用synchronized关键字定义的静态方法。这有点象数据表中的表锁,当修改一条记录时,系统就将整个表都锁住了,因此,大量使用这种同步方式会使程序的性能大幅度下降。
最后
以上就是外向裙子最近收集整理的关于Java 多线程 synchronized同步的全部内容,更多相关Java内容请搜索靠谱客的其他文章。
发表评论 取消回复