实现多线程
通过继承Thread类或者实现Runnable,Callable接口
继承Thread类
构造方法:Thread(),Thread(String threadname),
Thread(Runnable target),Thread(Runnable target,String threadname)
重写run()方法:包含线程运行逻辑
start()方法:开启多线程
1 | class aThread extends Thread{ |
2 | |
3 | public void run() { |
4 | System.out.println("这是run方法。。。"); |
5 | for(int i=0;i<10;i++){ |
6 | System.out.println(i+1+"只狼"); |
7 | } |
8 | } |
9 | } |
1 | public static void main(String[] args) { |
2 | Thread tt=new aThread(); |
3 | tt.start(); |
4 | System.out.println("这是主程序 。。。"); |
5 | for(int i=0;i<10;i++){ |
6 | System.out.println(i+1+"只饺子"); |
7 | } |
8 | } |
实现Runnable接口
重写run()方法,同上
1 | Thread tr=new Thread(new bThread()); |
2 | //Thread tr=new Thread(new bThread(),"trname"); |
3 | //加上线程名称,可以在run方法中通过Thread.cunrrentThread.getName()获取 |
4 | tr.start(); |
实现Runnable接口可以资源共享
1 | class bThread implements Runnable{ |
2 | int count; |
3 | |
4 | public void run() { |
5 | System.out.println("这是实现Runnable接口的run方法。。。"); |
6 | for(int i=0;i<5;i++){ |
7 | count++; |
8 | System.out.println(Thread.currentThread().getName()+count+"只狗"); |
9 | }//获取当前线程名+count值 |
10 | } |
11 | } |
12 | //主程序 |
13 | Runnable run=new bThread(); |
14 | Thread tr1=new Thread(run,"线程11111:"); |
15 | tr1.start(); |
16 | Thread tr2=new Thread(run,"线程22222:"); |
17 | tr2.start(); |
结果:

两个线程共享同一个runnable实现类的count值,各自执行,不影响对方
线程生命周期
新建:进程实例声明并创建,即new对象后
就绪:进程已准备好,可以被运行,即调用start()方法后
运行:线程被调度,分配好资源,即进入run()方法里
阻塞:被挂起或执行输入输出操作,即run()方法临时终止了执行
死亡:自然死亡:完成run()后或者抛出异常
强制死亡:执行stop()方法,线程被kill,断电,CPU爆炸等即线程结束

线程的调度
Java属于抢占式调度模型
线程优先级(1–10)
MAX_PRIORITY –10(最大)
MIN_PRIORITY –1(最小)
NORM_PRIORITY –5(默认)
getPriority():返回线程优先级
setPriority(int newPriority):改变线程优先级
注意:线程创建时继承父线程的优先级,优先级不是绝对,只是较大概率先运行优先级高的
1 | Runnable run=new bThread(); |
2 | Thread tr1=new Thread(run,"线程11111:");//设置线程名 |
3 | Thread tr2=new Thread(run,"线程22222:"); |
4 | tr1.setPriority(2); //优先级大 |
5 | tr2.setPriority(1); //优先级小 |
6 | tr1.start(); |
7 | tr2.start(); |
测了很多次终于出来的结果:
![]() |
![]() |
常用方法
启动线程start(),(stop():强制停止)
线程逻辑run(),
设置线程名setName(String name),
获取线程名getName(),
返回当前线程的引用currentThread(),
判断线程是否活着isActive()
yield():static void
线程让步,让优先级相同的先执行,但让步多长时间不固定,若没同优先级,此方法无效。
改造for循环:
1 | for(int i=0;i<5;i++){ |
2 | if(i==1){ //当i=1时让步给其他线程 |
3 | Thread.yield(); |
4 | } |
5 | count++; |
6 | System.out.println(Thread.currentThread().getName()+count+"只狗"); |
7 | } |

线程2执行完一次i++后,i=1让步给线程1,线程1也执行一次i++后再让给线程2,然后继续
可是有时候测出来不是这样,不知道为什么
join()
当某个线程调用其他线程的join方法时,该线程被阻塞,直到用join()方法加入的线程执行完为止,低优先级也可以获得执行。
在主线程for循环中i=0时插入线程1:
1 | System.out.println("这是主线程。。。"); |
2 | for(int i=0;i<3;i++){ |
3 | System.out.println(i+1+"只饺子"); |
4 | if(i==0){ |
5 | try { |
6 | tr1.join(); //先执行完tr1再继续循环 |
7 | } catch (InterruptedException e) { |
8 | e.printStackTrace(); |
9 | } |
10 | } |
11 | } |

可以看到线程1的执行过程在主线程的循环中间,而线程2不受控制,说明主线程在调用了线程1的join方法后转去执行线程1,直到结束再回来执行主线程
sleep(long mills):static void
令当前线程在指定时间段内睡眠,使其它线程有机会被执行,时间过去后恢复原样
1 | LocalDateTime start=LocalDateTime.now(); |
2 | System.out.println("即将进入睡眠。。。"); |
3 | try { |
4 | Thread.sleep(3000); |
5 | } catch (InterruptedException e) { |
6 | e.printStackTrace(); |
7 | } |
8 | System.out.println("睡眠完毕。。。"); |
9 | LocalDateTime stop=LocalDateTime.now(); |
10 | Duration duration = Duration.between(start,stop); |
11 | System.out.println("花费时间:"+duration.toMillis()+"毫秒"); |

在即将进入睡眠语句输出后,隔了3000毫秒,睡眠完毕输出,并输出时间差,验证成功。
如果在main方法中,如果new了一个Thread对象,然后调用了.sleep方法,其实睡眠的还是主线程,因为sleep是静态方法,对象.sleep相当于是类名.sleep,没有区别。
interrupt()
依靠异常处理机制,打断休眠,抛出异常,继续运行接下来的代码
线程同步与死锁
同步问题(数据不唯一)
两个线程同时向一个账户取钱,余额出现负数:

1 | //线程逻辑 |
2 | |
3 | public void run() { |
4 | acount.get(m); |
5 | } |
6 | //取钱操作 |
7 | public void get(int m){ |
8 | String tname=Thread.currentThread().getName()+"操作---"; |
9 | System.out.println(tname+"账户原有:"+money+",提款:"+m); |
10 | if(money>=m){ //余额充足 |
11 | System.out.println("要取钱拉"); //这四条纯粹为了加大出错的概率 |
12 | System.out.println("取钱拉"); |
13 | System.out.println("钱拉"); |
14 | System.out.println("拉"); |
15 | money-=m; //余额减少 |
16 | System.out.println(tname+"提款成功,账户余额:"+money); |
17 | }else { |
18 | System.out.println(tname+"账户余额不足。。。"); |
19 | } |
20 | System.out.println(tname+"结束-----------------------"); |
21 | } |
原因:当两个线程同时对一个账户进行取钱操作时,线程1判断余额充足后,继续接下来的操作,在还没到对余额进行修改时,线程2可能在此时判断余额充足,这就导致了两个线程同时完成了余额减少操作,金额变为负数,而这是不允许出现的。
解决办法:在一个线程执行对数据的操作时,其他线程不能执行。
1,synchronized代码块(推荐使用)
将需要同步的代码放在synchronized(这里是加锁的对象){这里是代码}代码块中
这样这一段代码只有一个线程能执行,当t1执行到这段代码时,就会去找该对象的对象锁,如果找到,就进入代码块中执行,执行完后,归还对象锁。如果在t2在执行过程中也遇到了这段代码,也去找对象锁,发现该对象锁被t1持有,只能等待t1归还对象锁。
1 | public void get(){ |
2 | synchronized(被锁对象){ |
3 | //取钱 |
4 | } |
5 | } |
2,synchronized关键字
在取钱操作get()方法上加上synchronized关键字
1 | public synchronized void get(){ |
2 | //取钱 |
3 | } |
synchronized关键字锁的不是方法,而是对象。
比如新加一个同样的get2()方法,然后改造run()方法
1 | if("wechat".equals(Thread.currentThread().getName())){ |
2 | acount.get2(m); |
3 | }else { |
4 | acount.get(m); |
5 | } |
当线程为wechat时执行get2(),线程为alipay时执行get(),发现其中一个还是只能等待另一个线程结束才能执行。
当两个线程操作的对象不一样时,就锁不了。
测试结果

加上关键字后,发现微信执行完之后,支付宝才能执行,此时就达到了目的。
注意:在静态方法上加锁时,对于所有的对象都是同一个锁,这个锁就称为类锁,即该类所有对象都是同一把锁。当执行到带synchronized关键字的静态方法时,会去找类锁,哪怕操作不同对象时也要等待拥有类锁的线程执行完。
死锁问题(永远等待)
著名的哲学家吃饭问题,n个人围成圆桌,总共有n根筷子,与哲学家插空放置。哲学家想要吃饭,必须拿两根筷子,当每个哲学家拿起左边的筷子时,再去拿右边的筷子,此时筷子已经被右边的哲学家拿走,此时陷入死锁状态。
线程T1先拿到o1的对象锁,然后想接着拿o2的对象锁,而线程T2相反。由于两个线程同步执行,所以会出现,线程T1拿到了o1的锁,同时线程T2拿到了o2的锁,形成了互相等待对方执行完成的局面,而双方都不可能执行完成。
1 | class T1 implements Runnable{ |
2 | Object o1; |
3 | Object o2; |
4 | public T1(Object o1,Object o2){ |
5 | this.o1=o1; |
6 | this.o2=o2; |
7 | } |
8 | |
9 | public void run() { |
10 | synchronized (o1){ //线程T1先拿到o1对象锁 |
11 | System.out.println(Thread.currentThread().getName()+"锁住了o1"); |
12 | try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //短暂延时,引起问题 |
13 | synchronized (o2){ //接着拿o2对象锁 |
14 | } |
15 | } |
16 | } |
17 | } |
线程T2与线程T1类似,只是先拿o2再拿o1。
守护线程
所有的用户线程(以上都是)结束生命周期,守护线程才会结束,例如Java中的垃圾回收器。
守护线程一般都是无限执行的。
setDaemon(true)方法可以将用户线程设置为守护线程
定时器
Timer:每隔一段固定时间执行任务
schedule(TimeTask task,Date firstTime,long perlod):指定定时任务
task:任务,firstTime:起始时间,perlod,间隔时间
1 | class Test extends TimeTask{ |
2 | public void run(){ |
3 | System.out.pritln("...") |
4 | } |
5 | } |
总结
头发-1

