Java 线程(多线程)详解
查看了许多书籍,网上的博客,现在我来说一下有关于我对线程的详解,有不对的欢迎指正。
一. 线程的生命周期:
程序有自己的一个生命周期,线程也不例外,也有自己的生命周期。查看许多书籍或者网上资料,发现了一件很有趣的事情,那就是它们对线程的生命周期不是唯一。有两种或者以上的线程生命周期。
第一种线程生命周期线程状态转换图:一共5个状态:新建,就绪,运行,阻塞和结束
图 1
第二种生命周期图:一共6个状态:New,Runnable,Blocked,Waiting,Timed Waiting,Terminated
图 二
事实上,从java源码得知的是,线程的生命周期是属于第二种生命周期的,当然也不能说明除第二种生命周期,其余的生命周期是错的,只能说每种线程生命周期都是为了让学者更好的理解线程的状态。下面我说一下我对线程生命周期的理解,仅限参考:
1. 当Thread对象被创建时,该线程的状态便是新建状态
2. 当线程调用start()方法并调用时,该线程状态由新建状态进入了运行状态。
运行状态可以分为可运行状态和运行状态,对应图1的就绪状态和运行状态。当调用start()方法时,cpu要给予线程资源,当线程还未获得cpu资源,线程无法运行或者该线程获取了cpu资源,但未开始运行时,该线程状态称作为’可运行状态’,若线程获取cpu资源时,且线程运行线程执行体(run方法中的代码块),对应的状态便是’运行状态’。事实上,我们无法通过程序将线程’可运行状态’和’运行状态分离’出来,因此’可运行状态’对于开发者来说,是不存在的。
3. 当线程执行完毕后,线程便进入了结束的状态。当然,线程进入结束状态不仅仅只是线程执行完毕,还有线程执行过程中出现程序错误或者线程被程序强行结束(Thread.stop方法,可以让线程直接进入结束状态),这些都会让线程进入结束状态。
4. 除了上述说的3种状态,还有一种状态叫做阻塞状态。该状态是线程运行时,遇到指定的方法,该线程运行停止下来,那么该线程状态叫做阻塞状态。该状态是线程的重点,线程控制也是根据该状态而展开的。
二.线程控制:
说到线程控制,不得不提的便是多线程以及线程并发。何为多线程,当单一的线程运行时,称之为单线程,多条线程运行时,则称之为多线程。而线程并发,指的是同一时间,cpu同时处理多个线程。然而cpu并发线程的数量是(cpu物理线程数)有限的,而线程的创建则是无限的,当创建出来的线程数,已经超多了cpu物理线程数时,cpu本着雨露均沾的做法,先让给予对应cpu物理线程数的线程资源,并让这些线程运行一段时间,当运行时间到时,cpu会剥夺这些线程的资源,并给予另外一批对应cpu物理线程数的线程资源(该批线程中,可能有些线程和上一批是一样的),让其运行一段时间。周而复始,直到所有的线程运行结束。
class TestThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<100;i++){ System.out.println(Thread.currentThread()+" "+i); } } } public class Threads{ public static void main(String[] args){ TestThread testThread = new TestThread(); for(int i=0;i<5;i++){ Thread t = new Thread(testThread); t.start(); } } }
上述代码我们创建了3个线程(3个线程所运行的线程执行体是一致的),并同时运行
结果一:
Thread[Thread-0,5,main] 0 Thread[Thread-3,5,main] 0 Thread[Thread-4,5,main] 0 Thread[Thread-4,5,main] 1 Thread[Thread-2,5,main] 0 Thread[Thread-1,5,main] 0 Thread[Thread-2,5,main] 1 Thread[Thread-4,5,main] 2 Thread[Thread-3,5,main] 1 Thread[Thread-0,5,main] 1
结果二:
Thread[Thread-1,5,main] 0 Thread[Thread-0,5,main] 0 Thread[Thread-1,5,main] 1 Thread[Thread-0,5,main] 1 Thread[Thread-1,5,main] 2 Thread[Thread-2,5,main] 0 Thread[Thread-0,5,main] 2 Thread[Thread-2,5,main] 1 Thread[Thread-1,5,main] 3 Thread[Thread-2,5,main] 2
我们根据结果一与结果二做对比,我们不难发现,运行出来的结果是不一致的,而这导致运行出来的结果不一致的原因则是因为cpu给予线程资源是根据cpu的’喜好’,同时该线程不是立刻执行完毕(由于这里执行的内容比较多,线程不能立刻执行完),而是执行到了一部分时,换成的另外一个线程执行。这也说明了cpu本着雨露均沾的做法,来实现多线程并发。
由于多线程的运行结果的不唯一,多线程的启动线程随意,会让数据发生丢失的可能,在程序上,再对要求结果要唯一的情况下,这是不允许的事情,因此我们要控制线程的状态,让运行结果变得唯一,让线程变得安全。以下是线程控制的几种类型:
- 改变线程优先度
- 线程停止运行方法
- 线程同步
- 线程通信
改变线程优先度:
当线程优先度最高时,cpu越喜欢,会优先给予资源,并让线程运行,当然当线程优先度相同时,cpu也只能随机选线程。对应方法:Thread.setPriorty(int),int参数范围为0-10,0优先度最低,1优先度最高
我们根据上述代码进行优化,代码如下:
class TestThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<100;i++){ System.out.println(Thread.currentThread()+" "+i); } } } public class Threads{ public static void main(String[] args){ TestThread testThread = new TestThread(); /** * 线程优先级设置 * */ Thread t_1 = new Thread(testThread); Thread t_2 = new Thread(testThread); Thread t_3 = new Thread(testThread); Thread t_4 = new Thread(testThread); Thread t_5 = new Thread(testThread); t_5.setPriority(10); t_5.start();t_1.start();t_2.start();t_3.start();t_4.start(); } }
值得注意的是,尽管cpu会优先执行优先级别高的线程,但在程序中,代码会有执行的先后顺序,代码执行也需要时间,尽管这段时间我们不可能察觉,因为时间太短了,几毫秒的时间,但对于cpu来说,以及可以给予线程资源并让线程运行一段时间了,同时cpu虽然会优先执行优先级别高的线程,但也不是说一定会优先执行优先级别高的线程,只能说大概率的会优先执行,以下是运行结果前十行。
设置了优先级别高的线程,最大的优势在于,cpu会更倾向它,让它更快的完成运行
运行结果一:
Thread[Thread-4,10,main] 0 Thread[Thread-4,10,main] 1 Thread[Thread-0,5,main] 0 Thread[Thread-4,10,main] 2 Thread[Thread-0,5,main] 1 Thread[Thread-4,10,main] 3 Thread[Thread-0,5,main] 2 Thread[Thread-4,10,main] 4 Thread[Thread-0,5,main] 3 Thread[Thread-4,10,main] 5
运行结果二:
Thread[Thread-4,10,main] 0 Thread[Thread-1,5,main] 0 Thread[Thread-0,5,main] 0 Thread[Thread-3,5,main] 0 Thread[Thread-2,5,main] 0 Thread[Thread-1,5,main] 1 Thread[Thread-4,10,main] 1 Thread[Thread-1,5,main] 2 Thread[Thread-1,5,main] 3 Thread[Thread-2,5,main] 1
线程停止运行方法:
- Thread.sleep()方法,有Thread.sleep(long millis)和Thread.sleep(long millis,int nanos)选择,作用是让线程休息millis毫秒(+nanos毫微秒),并停止运行(剥夺cpu资源),当休息结束后回到线程可运行状态。该方法需要申明异常
- Thread.yield()方法,作用是让线程停一下,该线程从运行状态直接回到可运行状态,同时让同优先级别或以上的线程运行,并且cpu不取回该线程资源。由于cpu不取回资源,该线程会很快速进入运行状态。
- Thread.join()方法,该方法有Thread.join(),Thread.join(long millis)和Thread.join(long millis,int nanos)选择,作用是运行该程序的线程要等对应线程运行完毕后方可继续运行,有参数的情况是是等对应线程运行指定millis毫秒(+nanos毫微秒)后,便继续运行。
Ps这里说明一下sleep方法与yield方法的区别:
- sleep执行后cpu找另外的线程运行,而yield执行后,是找优先度相同或以上的线程运行。
- sleep执行后,会进入阻塞的状态,而yield则是直接进入可运行状态
- sleep需要申明异常而yield不用
- sleep可移植性比yield好,因此尽可能的选择sleep方法
class TestThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<100;i++){ System.out.println(Thread.currentThread()+" "+i); } } } public class Threads{ public static void main(String[] args){ /** * 线程停止运行的方法 * */ System.out.println(Thread.currentThread()+"开始运行"); TestThread test = new TestThread(); Thread t_1 = new Thread(test); Thread t_2 = new Thread(test); Thread t_3 = new Thread(test); try{ t_1.start();t_2.start();t_3.start(); t_1.join(); }catch(Exception e){} System.out.println(Thread.currentThread()+"停止运行"); } }
上述代码中创建了3个线程,以及使用mian方法(主线程)。设置了主线程需要等待线程1运行结束后方法继续运行。大家可以去查看以下运行结果,会发现System.out.println(Thread.currentThread()+"停止运行")都是等待线程1打印完后打印的。
线程同步:
先看一下下述代码以及运行结果
/** * 未同步线程 * */ class TestThreads implements Runnable{ int n = 100; @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<50;i++){ System.out.print(Thread.currentThread() +" "+ i); System.out.print("\t"+Thread.currentThread().getPriority()); System.out.print("\t当前n为:"+n); n--; System.out.println("\t售后n为"+n); } } } public class Threads{ public static void main(String[] args){ /** * 未同步线程 * */ TestThreads testThreads = new TestThreads(); Thread t_1 = new Thread(testThreads); Thread t_2 = new Thread(testThreads); Thread t_3 = new Thread(testThreads); t_1.start();t_2.start();t_3.start(); } }
我们创建了3个线程,这三个线程都执行同一对象中的run方法,在run方法中,由四个打印语句。运行结果如下:
Thread[Thread-0,5,main] 0Thread[Thread-2,5,main] 0 5Thread[Thread-1,5,main] 0 当前n为:100 5 售后n为99 5Thread[Thread-2,5,main] 1 5 当前n为:99 售后n为98 当前n为:99 售后n为97 Thread[Thread-0,5,main] 1 5 当前n为:97 售后n为96 Thread[Thread-0,5,main] 2 5 当前n为:96 售后n为95 Thread[Thread-0,5,main] 3 5 当前n为:95 售后n为94 Thread[Thread-0,5,main] 4 5 当前n为:94 售后n为93 Thread[Thread-2,5,main] 2 当前n为:99 5Thread[Thread-0,5,main] 5 当前n为:92 售后n为92 售后n为91 5 当前n为:91Thread[Thread-2,5,main] 3 5 当前n为:90 售后n为89
我们可以看到运行的结果,非常混乱,并且该线程充满了不安全性。为了让线程有序,并且充满安全性,就需要用到线程的同步。
这里的线程同步,并非指多个线程并发同步执行。这里的同步,是指当多线程使用同一资源时,为了保证资源不混乱,并且有序的让多线程进行。线程同步的原理是,当多线程执行同一资源时,cpu只会让其中一个线程执行,当该线程执行完同步资源后,cpu会让另一个线程执行,周而复始,直到资源被用完,或者所有线程结束。你可以理解为,多人买车票的时候,需要排队买车票,可以以多线程当作多人,以车票数当作资源。网上有个网友说,线程同步,就是线程排队,实际上确实如此。而在程序中,如何能让多线程排队呢,答案是让每个线程带有锁与钥匙,但线程运行资源时,第一步时将资源锁住,由于其他线程的钥匙都打不开该线程的锁。那么其他的线程就会等待资源解锁的时候,而这时就只有该线程一个运行资源。但该线程运行完后,便会解锁,释放资源给其余的线程。周而复始,直到结束。
知道了线程同步的原理后,如何实现线程同步呢,有以下的方法:
1.使用synchronized同步代码块或者synchronized同步方法
将上述代码使用synchronized进行线程同步
/** * 同步线程 * */ class SynThread implements Runnable{ int n = 100; @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<50;i++){ synchronized(this){ //将资源锁住,只有正确的锁才能开锁,这里的锁用的是对象 if(n<=0){ Thread.currentThread().stop(); } System.out.print(Thread.currentThread() +" "+ i); System.out.print("\t"+Thread.currentThread().getPriority()); System.out.print("\t当前n为:"+n); n--; System.out.println("\t售后n为"+n); } } } } public class Threads{ public static void main(String[] args){ SynThread ft = new SynThread(); for(int i=0 ;i<4;i++){ Thread t = new Thread(ft); t.start(); } } }
运行结果如下:
Thread[Thread-0,5,main] 0 5 当前n为:100 售后n为99 Thread[Thread-0,5,main] 1 5 当前n为:99 售后n为98 Thread[Thread-0,5,main] 2 5 当前n为:98 售后n为97 Thread[Thread-0,5,main] 3 5 当前n为:97 售后n为96 Thread[Thread-2,5,main] 0 5 当前n为:96 售后n为95 Thread[Thread-2,5,main] 1 5 当前n为:95 售后n为94 Thread[Thread-2,5,main] 2 5 当前n为:94 售后n为93 Thread[Thread-2,5,main] 3 5 当前n为:93 售后n为92 Thread[Thread-2,5,main] 4 5 当前n为:92 售后n为91 Thread[Thread-2,5,main] 5 5 当前n为:91 售后n为90 Thread[Thread-2,5,main] 6 5 当前n为:90 售后n为89 Thread[Thread-2,5,main] 7 5 当前n为:89 售后n为88 Thread[Thread-2,5,main] 8 5 当前n为:88 售后n为87 Thread[Thread-2,5,main] 9 5 当前n为:87 售后n为86 Thread[Thread-2,5,main] 10 5 当前n为:86 售后n为85 Thread[Thread-2,5,main] 11 5 当前n为:85 售后n为84 Thread[Thread-2,5,main] 12 5 当前n为:84 售后n为83 Thread[Thread-2,5,main] 13 5 当前n为:83 售后n为82 Thread[Thread-2,5,main] 14 5 当前n为:82 售后n为81 Thread[Thread-2,5,main] 15 5 当前n为:81 售后n为80 Thread[Thread-2,5,main] 16 5 当前n为:80 售后n为79 Thread[Thread-2,5,main] 17 5 当前n为:79 售后n为78 Thread[Thread-2,5,main] 18 5 当前n为:78 售后n为77 Thread[Thread-2,5,main] 19 5 当前n为:77 售后n为76 Thread[Thread-2,5,main] 20 5 当前n为:76 售后n为75 Thread[Thread-2,5,main] 21 5 当前n为:75 售后n为74 Thread[Thread-2,5,main] 22 5 当前n为:74 售后n为73 Thread[Thread-2,5,main] 23 5 当前n为:73 售后n为72 Thread[Thread-2,5,main] 24 5 当前n为:72 售后n为71 Thread[Thread-2,5,main] 25 5 当前n为:71 售后n为70 Thread[Thread-2,5,main] 26 5 当前n为:70 售后n为69 Thread[Thread-2,5,main] 27 5 当前n为:69 售后n为68 Thread[Thread-2,5,main] 28 5 当前n为:68 售后n为67 Thread[Thread-2,5,main] 29 5 当前n为:67 售后n为66 Thread[Thread-2,5,main] 30 5 当前n为:66 售后n为65 Thread[Thread-2,5,main] 31 5 当前n为:65 售后n为64 Thread[Thread-2,5,main] 32 5 当前n为:64 售后n为63 Thread[Thread-2,5,main] 33 5 当前n为:63 售后n为62 Thread[Thread-2,5,main] 34 5 当前n为:62 售后n为61 Thread[Thread-2,5,main] 35 5 当前n为:61 售后n为60 Thread[Thread-2,5,main] 36 5 当前n为:60 售后n为59 Thread[Thread-2,5,main] 37 5 当前n为:59 售后n为58 Thread[Thread-2,5,main] 38 5 当前n为:58 售后n为57 Thread[Thread-2,5,main] 39 5 当前n为:57 售后n为56 Thread[Thread-2,5,main] 40 5 当前n为:56 售后n为55 Thread[Thread-2,5,main] 41 5 当前n为:55 售后n为54 Thread[Thread-2,5,main] 42 5 当前n为:54 售后n为53 Thread[Thread-2,5,main] 43 5 当前n为:53 售后n为52 Thread[Thread-2,5,main] 44 5 当前n为:52 售后n为51 Thread[Thread-2,5,main] 45 5 当前n为:51 售后n为50 Thread[Thread-2,5,main] 46 5 当前n为:50 售后n为49 Thread[Thread-2,5,main] 47 5 当前n为:49 售后n为48 Thread[Thread-2,5,main] 48 5 当前n为:48 售后n为47 Thread[Thread-2,5,main] 49 5 当前n为:47 售后n为46 Thread[Thread-3,5,main] 0 5 当前n为:46 售后n为45 Thread[Thread-3,5,main] 1 5 当前n为:45 售后n为44 Thread[Thread-3,5,main] 2 5 当前n为:44 售后n为43 Thread[Thread-3,5,main] 3 5 当前n为:43 售后n为42 Thread[Thread-3,5,main] 4 5 当前n为:42 售后n为41 Thread[Thread-3,5,main] 5 5 当前n为:41 售后n为40 Thread[Thread-3,5,main] 6 5 当前n为:40 售后n为39 Thread[Thread-3,5,main] 7 5 当前n为:39 售后n为38 Thread[Thread-3,5,main] 8 5 当前n为:38 售后n为37 Thread[Thread-3,5,main] 9 5 当前n为:37 售后n为36 Thread[Thread-3,5,main] 10 5 当前n为:36 售后n为35 Thread[Thread-3,5,main] 11 5 当前n为:35 售后n为34 Thread[Thread-3,5,main] 12 5 当前n为:34 售后n为33 Thread[Thread-3,5,main] 13 5 当前n为:33 售后n为32 Thread[Thread-3,5,main] 14 5 当前n为:32 售后n为31 Thread[Thread-3,5,main] 15 5 当前n为:31 售后n为30 Thread[Thread-3,5,main] 16 5 当前n为:30 售后n为29 Thread[Thread-3,5,main] 17 5 当前n为:29 售后n为28 Thread[Thread-3,5,main] 18 5 当前n为:28 售后n为27 Thread[Thread-3,5,main] 19 5 当前n为:27 售后n为26 Thread[Thread-3,5,main] 20 5 当前n为:26 售后n为25 Thread[Thread-3,5,main] 21 5 当前n为:25 售后n为24 Thread[Thread-3,5,main] 22 5 当前n为:24 售后n为23 Thread[Thread-3,5,main] 23 5 当前n为:23 售后n为22 Thread[Thread-3,5,main] 24 5 当前n为:22 售后n为21 Thread[Thread-3,5,main] 25 5 当前n为:21 售后n为20 Thread[Thread-3,5,main] 26 5 当前n为:20 售后n为19 Thread[Thread-3,5,main] 27 5 当前n为:19 售后n为18 Thread[Thread-3,5,main] 28 5 当前n为:18 售后n为17 Thread[Thread-3,5,main] 29 5 当前n为:17 售后n为16 Thread[Thread-3,5,main] 30 5 当前n为:16 售后n为15 Thread[Thread-3,5,main] 31 5 当前n为:15 售后n为14 Thread[Thread-3,5,main] 32 5 当前n为:14 售后n为13 Thread[Thread-3,5,main] 33 5 当前n为:13 售后n为12 Thread[Thread-3,5,main] 34 5 当前n为:12 售后n为11 Thread[Thread-3,5,main] 35 5 当前n为:11 售后n为10 Thread[Thread-3,5,main] 36 5 当前n为:10 售后n为9 Thread[Thread-1,5,main] 0 5 当前n为:9 售后n为8 Thread[Thread-1,5,main] 1 5 当前n为:8 售后n为7 Thread[Thread-1,5,main] 2 5 当前n为:7 售后n为6 Thread[Thread-1,5,main] 3 5 当前n为:6 售后n为5 Thread[Thread-1,5,main] 4 5 当前n为:5 售后n为4 Thread[Thread-1,5,main] 5 5 当前n为:4 售后n为3 Thread[Thread-1,5,main] 6 5 当前n为:3 售后n为2 Thread[Thread-1,5,main] 7 5 当前n为:2 售后n为1 Thread[Thread-1,5,main] 8 5 当前n为:1 售后n为0
2. 使用lock锁进行同步
/** * lock同步锁 * */ class LockThread implements Runnable{ LockTest lockTest = null; public LockThread(LockTest lockTest){ this.lockTest = lockTest; } @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<100;i++){ lockTest.sub(); } } } class LockTest{ int n = 100; private final ReentrantLock lock = new ReentrantLock(); public void sub(){ lock.lock(); try{ if(n>0){ System.out.print(Thread.currentThread()+"操作"); System.out.print("\t当前n数为:"+n); n--; System.out.println("\t 操作后n数为:"+n); }else{ System.out.println(Thread.currentThread()+"结束"); Thread.currentThread().stop(); } }finally{ lock.unlock(); } } } public class Threads{ public static void main(String[] args){ LockTest lockTest = new LockTest(); LockThread lockThread = new LockThread(lockTest); for(int i=0 ;i<4;i++){ Thread s = new Thread(lockThread); s.start(); } } }
运行结果如下:
Thread[Thread-0,5,main]操作 当前n数为:100 操作后n数为:99 Thread[Thread-0,5,main]操作 当前n数为:99 操作后n数为:98 Thread[Thread-0,5,main]操作 当前n数为:98 操作后n数为:97 Thread[Thread-0,5,main]操作 当前n数为:97 操作后n数为:96 Thread[Thread-1,5,main]操作 当前n数为:96 操作后n数为:95 Thread[Thread-1,5,main]操作 当前n数为:95 操作后n数为:94 Thread[Thread-1,5,main]操作 当前n数为:94 操作后n数为:93 Thread[Thread-1,5,main]操作 当前n数为:93 操作后n数为:92 Thread[Thread-1,5,main]操作 当前n数为:92 操作后n数为:91 Thread[Thread-1,5,main]操作 当前n数为:91 操作后n数为:90 Thread[Thread-1,5,main]操作 当前n数为:90 操作后n数为:89 Thread[Thread-1,5,main]操作 当前n数为:89 操作后n数为:88 Thread[Thread-1,5,main]操作 当前n数为:88 操作后n数为:87 Thread[Thread-1,5,main]操作 当前n数为:87 操作后n数为:86 Thread[Thread-1,5,main]操作 当前n数为:86 操作后n数为:85 Thread[Thread-1,5,main]操作 当前n数为:85 操作后n数为:84 Thread[Thread-1,5,main]操作 当前n数为:84 操作后n数为:83 Thread[Thread-1,5,main]操作 当前n数为:83 操作后n数为:82 Thread[Thread-1,5,main]操作 当前n数为:82 操作后n数为:81 Thread[Thread-1,5,main]操作 当前n数为:81 操作后n数为:80 Thread[Thread-1,5,main]操作 当前n数为:80 操作后n数为:79 Thread[Thread-1,5,main]操作 当前n数为:79 操作后n数为:78 Thread[Thread-1,5,main]操作 当前n数为:78 操作后n数为:77 Thread[Thread-1,5,main]操作 当前n数为:77 操作后n数为:76 Thread[Thread-1,5,main]操作 当前n数为:76 操作后n数为:75 Thread[Thread-1,5,main]操作 当前n数为:75 操作后n数为:74 Thread[Thread-1,5,main]操作 当前n数为:74 操作后n数为:73 Thread[Thread-1,5,main]操作 当前n数为:73 操作后n数为:72 Thread[Thread-1,5,main]操作 当前n数为:72 操作后n数为:71 Thread[Thread-1,5,main]操作 当前n数为:71 操作后n数为:70 Thread[Thread-1,5,main]操作 当前n数为:70 操作后n数为:69 Thread[Thread-1,5,main]操作 当前n数为:69 操作后n数为:68 Thread[Thread-1,5,main]操作 当前n数为:68 操作后n数为:67 Thread[Thread-1,5,main]操作 当前n数为:67 操作后n数为:66 Thread[Thread-1,5,main]操作 当前n数为:66 操作后n数为:65 Thread[Thread-1,5,main]操作 当前n数为:65 操作后n数为:64 Thread[Thread-1,5,main]操作 当前n数为:64 操作后n数为:63 Thread[Thread-1,5,main]操作 当前n数为:63 操作后n数为:62 Thread[Thread-1,5,main]操作 当前n数为:62 操作后n数为:61 Thread[Thread-1,5,main]操作 当前n数为:61 操作后n数为:60 Thread[Thread-1,5,main]操作 当前n数为:60 操作后n数为:59 Thread[Thread-1,5,main]操作 当前n数为:59 操作后n数为:58 Thread[Thread-1,5,main]操作 当前n数为:58 操作后n数为:57 Thread[Thread-1,5,main]操作 当前n数为:57 操作后n数为:56 Thread[Thread-1,5,main]操作 当前n数为:56 操作后n数为:55 Thread[Thread-1,5,main]操作 当前n数为:55 操作后n数为:54 Thread[Thread-1,5,main]操作 当前n数为:54 操作后n数为:53 Thread[Thread-1,5,main]操作 当前n数为:53 操作后n数为:52 Thread[Thread-1,5,main]操作 当前n数为:52 操作后n数为:51 Thread[Thread-1,5,main]操作 当前n数为:51 操作后n数为:50 Thread[Thread-1,5,main]操作 当前n数为:50 操作后n数为:49 Thread[Thread-1,5,main]操作 当前n数为:49 操作后n数为:48 Thread[Thread-1,5,main]操作 当前n数为:48 操作后n数为:47 Thread[Thread-1,5,main]操作 当前n数为:47 操作后n数为:46 Thread[Thread-1,5,main]操作 当前n数为:46 操作后n数为:45 Thread[Thread-1,5,main]操作 当前n数为:45 操作后n数为:44 Thread[Thread-1,5,main]操作 当前n数为:44 操作后n数为:43 Thread[Thread-1,5,main]操作 当前n数为:43 操作后n数为:42 Thread[Thread-1,5,main]操作 当前n数为:42 操作后n数为:41 Thread[Thread-1,5,main]操作 当前n数为:41 操作后n数为:40 Thread[Thread-1,5,main]操作 当前n数为:40 操作后n数为:39 Thread[Thread-1,5,main]操作 当前n数为:39 操作后n数为:38 Thread[Thread-1,5,main]操作 当前n数为:38 操作后n数为:37 Thread[Thread-1,5,main]操作 当前n数为:37 操作后n数为:36 Thread[Thread-1,5,main]操作 当前n数为:36 操作后n数为:35 Thread[Thread-1,5,main]操作 当前n数为:35 操作后n数为:34 Thread[Thread-1,5,main]操作 当前n数为:34 操作后n数为:33 Thread[Thread-1,5,main]操作 当前n数为:33 操作后n数为:32 Thread[Thread-1,5,main]操作 当前n数为:32 操作后n数为:31 Thread[Thread-1,5,main]操作 当前n数为:31 操作后n数为:30 Thread[Thread-1,5,main]操作 当前n数为:30 操作后n数为:29 Thread[Thread-1,5,main]操作 当前n数为:29 操作后n数为:28 Thread[Thread-1,5,main]操作 当前n数为:28 操作后n数为:27 Thread[Thread-1,5,main]操作 当前n数为:27 操作后n数为:26 Thread[Thread-1,5,main]操作 当前n数为:26 操作后n数为:25 Thread[Thread-1,5,main]操作 当前n数为:25 操作后n数为:24 Thread[Thread-1,5,main]操作 当前n数为:24 操作后n数为:23 Thread[Thread-1,5,main]操作 当前n数为:23 操作后n数为:22 Thread[Thread-1,5,main]操作 当前n数为:22 操作后n数为:21 Thread[Thread-1,5,main]操作 当前n数为:21 操作后n数为:20 Thread[Thread-1,5,main]操作 当前n数为:20 操作后n数为:19 Thread[Thread-1,5,main]操作 当前n数为:19 操作后n数为:18 Thread[Thread-1,5,main]操作 当前n数为:18 操作后n数为:17 Thread[Thread-1,5,main]操作 当前n数为:17 操作后n数为:16 Thread[Thread-1,5,main]操作 当前n数为:16 操作后n数为:15 Thread[Thread-1,5,main]操作 当前n数为:15 操作后n数为:14 Thread[Thread-1,5,main]操作 当前n数为:14 操作后n数为:13 Thread[Thread-1,5,main]操作 当前n数为:13 操作后n数为:12 Thread[Thread-1,5,main]操作 当前n数为:12 操作后n数为:11 Thread[Thread-1,5,main]操作 当前n数为:11 操作后n数为:10 Thread[Thread-1,5,main]操作 当前n数为:10 操作后n数为:9 Thread[Thread-1,5,main]操作 当前n数为:9 操作后n数为:8 Thread[Thread-1,5,main]操作 当前n数为:8 操作后n数为:7 Thread[Thread-1,5,main]操作 当前n数为:7 操作后n数为:6 Thread[Thread-1,5,main]操作 当前n数为:6 操作后n数为:5 Thread[Thread-1,5,main]操作 当前n数为:5 操作后n数为:4 Thread[Thread-1,5,main]操作 当前n数为:4 操作后n数为:3 Thread[Thread-1,5,main]操作 当前n数为:3 操作后n数为:2 Thread[Thread-1,5,main]操作 当前n数为:2 操作后n数为:1 Thread[Thread-1,5,main]操作 当前n数为:1 操作后n数为:0 Thread[Thread-1,5,main]结束 Thread[Thread-2,5,main]结束 Thread[Thread-3,5,main]结束 Thread[Thread-0,5,main]结束
无论是使用方法一或者方法二,从运行的结果可以发现,结果都是有序运行的,而不想未同步时的顺序打乱。有关线程同步有以下需要注意的地方:
1. 实现线程同步的前提条件是多线程,并且线程执行内容要一致(引用同一线程targer对象)。若是单一线程,根本无需用到锁。
2. 同步的资源不一定放在run方法中,也可以放在其他的方法体里,让run方法调用该方法。
3. 线程同步,有可能会造成死锁,何为死锁,线程1需要线程2的锁放可进行运行,而线程2需要线程1的锁放可运行。两个线程都不让步,便会造成死锁。在程序中,由于资源被锁住的原因,线程进入阻塞状态,并且双方都不让步,便会导致了两个线程无限的等待下去。因此编写程序时应该避免死锁。
线程通信:
当两个线程执行的内容不同,但相互间有联系,这便是线程的通信。要先实现线程通信,有以下的条件:
1. 必须有两个或以上不同的线程执行体,即run方法执行的内容不一致
2. 每个线程执行体必须符合线程同步。
线程通信最经典的例子便是生产者与消费者。生产者生产一件东西,而消费者消费东西。其执行顺序时,生产1,消费1,生产2,消费2的顺序。由于生产者与消费者不仅仅只是单一线程的生产与消费,因此生产者与消费者需要当作资源锁住。如何实现线程通信,有以下的方法:
1.使用synchronized与Object.wait(),notify(),notifyAll()方法实现
2.使用lock同步锁与Condition类实现
3.使用阻塞队列BlockingQueue实现
以下是代码:
建立一个生产者与消费者都会使用到的类Request
class Requests{ private final Lock lock = new ReentrantLock(); private final Condition cond = lock.newCondition(); //创建长度为2的阻塞队列 private final BlockingQueue aQueue = new ArrayBlockingQueue(2); String url; int paramentCount; boolean bFull,lFull = false; /** * 使用synchronized同步块或同步方法进行通讯 * */ public synchronized void set_1(String url,int paramentCount){ if(bFull){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.url = url; this.paramentCount = paramentCount; System.out.println("synchronized "+Thread.currentThread()+" 放入了"+url+"----->"+paramentCount); bFull = true; notify(); } public synchronized void get_1(){ if(!bFull){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("synchronized"+Thread.currentThread()+url+"------->"+paramentCount); bFull = false; notify(); } /** * 使用lock同步锁进行通讯 * */ public void set_2(String url,int paramentCount){ lock.lock(); try{ if(lFull){ try { cond.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.url = url; this.paramentCount = paramentCount; System.out.println("lock "+Thread.currentThread()+" 放入了"+url+"----->"+paramentCount); lFull = true; cond.signal(); }finally{ lock.unlock(); } } public void get_2(){ lock.lock(); try{ if(!lFull){ try { cond.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("lock "+Thread.currentThread()+"取出来"+url+"------->"+paramentCount); lFull = false; cond.signal(); }finally{ lock.unlock(); } } /** * 使用BlockingQueue进行通讯 * */ public void set_3(String url,int paramentCount){ try { aQueue.put(url+" "+paramentCount); System.out.println("lock "+Thread.currentThread()+" 放入了数据"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void get_3(){ try { System.out.println("BlockingQueue "+Thread.currentThread()+"取出来数据:"+aQueue.take()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
在Request中一个有6个方法,set_1,set_2,set_3,get_1,get_2,get3方法。其中,set_1与get_1使用的是的synchronized与Object.wait(),notify(),notifyAll()方法得线程通信,set_2与get_2是使用lock同步锁与Condition类实现的线程通信,set_3与get_3则是使用阻塞队列BlockingQueue实现的线程通信。
生产者类:
class Productor implements Runnable{ Requests request = null; public Productor(Requests requests){ request = requests; Thread t = new Thread(this); t.start(); } @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ if(i%2 == 0){ //使用Synchronized同步块或者同步方法 // request.set_1("localhost", 1); //使用lock同步锁 // request.set_2("localhost", 1); //使用BlockingQueue阻塞队列 request.set_3("localhost", 1); }else{ //使用Synchronized同步块或者同步方法 // request.set_1("127.0.0.1", 3); // 使用lock同步锁 // request.set_2("127.0.0.1", 3); //使用BlockingQueue阻塞队列 request.set_3("127.0.0.1", 3); } } } }
消费者:
class Consumer implements Runnable{ Requests request = null; public Consumer(Requests requests){ request = requests; Thread t = new Thread(this); t.start(); } @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ //使用Synchronized同步块或者同步方法 // request.get_1(); //使用lock同步锁 // request.get_2(); //使用BlockingQueue阻塞队列 request.get_3(); } } }
使用生产者与消费者创建线程并运行:
public class Threads{ public static void main(String[] args){ Requests request = new Requests(); Productor p = new Productor(request); Consumer c = new Consumer(request); } }
由于生产者与消费者都有三种实现线程通信的方法,需要注释另外两种,仅有一种线程通信方法运行结果如下:
方法一运行效果如下:
synchronized Thread[Thread-0,5,main] 放入了localhost----->1 synchronizedThread[Thread-1,5,main]localhost------->1 synchronized Thread[Thread-0,5,main] 放入了127.0.0.1----->3 synchronizedThread[Thread-1,5,main]127.0.0.1------->3 synchronized Thread[Thread-0,5,main] 放入了localhost----->1 synchronizedThread[Thread-1,5,main]localhost------->1 synchronized Thread[Thread-0,5,main] 放入了127.0.0.1----->3 synchronizedThread[Thread-1,5,main]127.0.0.1------->3 synchronized Thread[Thread-0,5,main] 放入了localhost----->1 synchronizedThread[Thread-1,5,main]localhost------->1 synchronized Thread[Thread-0,5,main] 放入了127.0.0.1----->3 synchronizedThread[Thread-1,5,main]127.0.0.1------->3 synchronized Thread[Thread-0,5,main] 放入了localhost----->1 synchronizedThread[Thread-1,5,main]localhost------->1 synchronized Thread[Thread-0,5,main] 放入了127.0.0.1----->3 synchronizedThread[Thread-1,5,main]127.0.0.1------->3 synchronized Thread[Thread-0,5,main] 放入了localhost----->1 synchronizedThread[Thread-1,5,main]localhost------->1 synchronized Thread[Thread-0,5,main] 放入了127.0.0.1----->3 synchronizedThread[Thread-1,5,main]127.0.0.1------->3
方法二运行效果如下:
lock Thread[Thread-0,5,main] 放入了localhost----->1 lock Thread[Thread-1,5,main]取出来localhost------->1 lock Thread[Thread-0,5,main] 放入了127.0.0.1----->3 lock Thread[Thread-1,5,main]取出来127.0.0.1------->3 lock Thread[Thread-0,5,main] 放入了localhost----->1 lock Thread[Thread-1,5,main]取出来localhost------->1 lock Thread[Thread-0,5,main] 放入了127.0.0.1----->3 lock Thread[Thread-1,5,main]取出来127.0.0.1------->3 lock Thread[Thread-0,5,main] 放入了localhost----->1 lock Thread[Thread-1,5,main]取出来localhost------->1 lock Thread[Thread-0,5,main] 放入了127.0.0.1----->3 lock Thread[Thread-1,5,main]取出来127.0.0.1------->3 lock Thread[Thread-0,5,main] 放入了localhost----->1 lock Thread[Thread-1,5,main]取出来localhost------->1 lock Thread[Thread-0,5,main] 放入了127.0.0.1----->3 lock Thread[Thread-1,5,main]取出来127.0.0.1------->3 lock Thread[Thread-0,5,main] 放入了localhost----->1 lock Thread[Thread-1,5,main]取出来localhost------->1 lock Thread[Thread-0,5,main] 放入了127.0.0.1----->3 lock Thread[Thread-1,5,main]取出来127.0.0.1------->3
方法三运行效果如下:
BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:localhost 1 BlockingQueue Thread[Thread-1,5,main]取出来数据:127.0.0.1 3 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:localhost 1 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:127.0.0.1 3 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:localhost 1 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:127.0.0.1 3 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:localhost 1 BlockingQueue Thread[Thread-1,5,main]取出来数据:127.0.0.1 3 BlockingQueue Thread[Thread-0,5,main] 放入了数据 BlockingQueue Thread[Thread-1,5,main]取出来数据:localhost 1 BlockingQueue Thread[Thread-1,5,main]取出来数据:127.0.0.1 3
由于方法三实现的通信原理与方法一和方法二不同,因此不能像方法一与方法二那样一生产一消费,但也保证了消费前要生产。
全部代码:
class TestThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<100;i++){ System.out.println(Thread.currentThread()+" "+i); } } } /** * 未同步线程 * */ class TestThreads implements Runnable{ int n = 100; @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<50;i++){ System.out.print(Thread.currentThread() +" "+ i); System.out.print("\t"+Thread.currentThread().getPriority()); System.out.print("\t当前n为:"+n); n--; System.out.println("\t售后n为"+n); } } } /** * 同步线程 * */ class SynThread implements Runnable{ int n = 100; @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<50;i++){ synchronized(this){ if(n<=0){ Thread.currentThread().stop(); } System.out.print(Thread.currentThread() +" "+ i); System.out.print("\t"+Thread.currentThread().getPriority()); System.out.print("\t当前n为:"+n); n--; System.out.println("\t售后n为"+n); } } } } /** * lock同步锁 * */ class LockThread implements Runnable{ LockTest lockTest = null; public LockThread(LockTest lockTest){ this.lockTest = lockTest; } @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<100;i++){ lockTest.sub(); } } } class LockTest{ int n = 100; private final ReentrantLock lock = new ReentrantLock(); public void sub(){ lock.lock(); try{ if(n>0){ System.out.print(Thread.currentThread()+"操作"); System.out.print("\t当前n数为:"+n); n--; System.out.println("\t 操作后n数为:"+n); }else{ System.out.println(Thread.currentThread()+"结束"); Thread.currentThread().stop(); } }finally{ lock.unlock(); } } } /** * 线程通讯 * */ class Productor implements Runnable{ Requests request = null; public Productor(Requests requests){ request = requests; Thread t = new Thread(this); t.start(); } @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ if(i%2 == 0){ //使用Synchronized同步块或者同步方法 // request.set_1("localhost", 1); //使用lock同步锁 // request.set_2("localhost", 1); //使用BlockingQueue阻塞队列 request.set_3("localhost", 1); }else{ //使用Synchronized同步块或者同步方法 // request.set_1("127.0.0.1", 3); // 使用lock同步锁 // request.set_2("127.0.0.1", 3); //使用BlockingQueue阻塞队列 request.set_3("127.0.0.1", 3); } } } } class Consumer implements Runnable{ Requests request = null; public Consumer(Requests requests){ request = requests; Thread t = new Thread(this); t.start(); } @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ //使用Synchronized同步块或者同步方法 // request.get_1(); //使用lock同步锁 // request.get_2(); //使用BlockingQueue阻塞队列 request.get_3(); } } } class Requests{ private final Lock lock = new ReentrantLock(); private final Condition cond = lock.newCondition(); //创建长度为2的阻塞队列 private final BlockingQueue aQueue = new ArrayBlockingQueue(2); String url; int paramentCount; boolean bFull,lFull = false; /** * 使用synchronized同步块或同步方法进行通讯 * */ public synchronized void set_1(String url,int paramentCount){ if(bFull){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.url = url; this.paramentCount = paramentCount; System.out.println("synchronized "+Thread.currentThread()+" 放入了"+url+"----->"+paramentCount); bFull = true; notify(); } public synchronized void get_1(){ if(!bFull){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("synchronized"+Thread.currentThread()+url+"------->"+paramentCount); bFull = false; notify(); } /** * 使用lock同步锁进行通讯 * */ public void set_2(String url,int paramentCount){ lock.lock(); try{ if(lFull){ try { cond.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } this.url = url; this.paramentCount = paramentCount; System.out.println("lock "+Thread.currentThread()+" 放入了"+url+"----->"+paramentCount); lFull = true; cond.signal(); }finally{ lock.unlock(); } } public void get_2(){ lock.lock(); try{ if(!lFull){ try { cond.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("lock "+Thread.currentThread()+"取出来"+url+"------->"+paramentCount); lFull = false; cond.signal(); }finally{ lock.unlock(); } } /** * 使用BlockingQueue进行通讯 * */ public void set_3(String url,int paramentCount){ try { aQueue.put(url+" "+paramentCount); System.out.println("BlockingQueue "+Thread.currentThread()+" 放入了数据"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void get_3(){ try { System.out.println("BlockingQueue "+Thread.currentThread()+"取出来数据:"+aQueue.take()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public class Threads{ public static void main(String[] args){ /** * 线程控制1 * */ // TestThread testThread = new TestThread(); // for(int i=0;i<5;i++){ // Thread t = new Thread(testThread); // t.start(); // } /** * 线程优先级设置 * */ // TestThread testThread = new TestThread(); // Thread t_1 = new Thread(testThread); // Thread t_2 = new Thread(testThread); // Thread t_3 = new Thread(testThread); // Thread t_4 = new Thread(testThread); // Thread t_5 = new Thread(testThread); // t_5.setPriority(10); // t_5.start();t_1.start();t_2.start();t_3.start();t_4.start(); // /** // * 线程停止运行的方法 // * */ // System.out.println(Thread.currentThread()+"开始运行"); // TestThread test = new TestThread(); // Thread t_1 = new Thread(test); // Thread t_2 = new Thread(test); // Thread t_3 = new Thread(test); // try{ // t_1.start();t_2.start();t_3.start(); // t_1.join(); // }catch(Exception e){} // System.out.println(Thread.currentThread()+"停止运行"); /** * 未同步线程 * */ // TestThreads testThreads = new TestThreads(); // Thread t_1 = new Thread(testThreads); // Thread t_2 = new Thread(testThreads); // Thread t_3 = new Thread(testThreads); // t_1.start();t_2.start();t_3.start(); /** * 同步的条件:引用同个target线程对象 * */ // SynThread ft = new SynThread(); // LockTest lockTest = new LockTest(); // LockThread lockThread = new LockThread(lockTest); // for(int i=0 ;i<4;i++){ //// Thread t = new Thread(ft); //// t.start(); // Thread s = new Thread(lockThread); // s.start(); // } /** * 线程通讯 * 要实现线程通讯(两个不同对象不同run方法的线程) * 1. 同步资源 * 2. 当一个线程获取时,另外的线程停止。 * */ Requests request = new Requests(); Productor p = new Productor(request); Consumer c = new Consumer(request); } }