一、什么是线程 (多线程即在同一时间可以做多件事情)
创建多线程有3种方式,分别是继承线程类、实现Runnable接口和匿名类
理解进程(Processor)和线程(Thread)的区别
进程:启动一个LOL.exe就叫一个进程,再启动一个就叫两个进程
线程:线程是在进程内部同时做的事,比如在LOL里英雄互相击杀就是由多线程来实现的
如下代码是两对英雄同时攻击的代码(继承线程类)
思路:设计一个类KillThread继承Thread,并且重写run方法
启动线程的方法:实例化一个KillThread对象并且调用其start方法
一、Hero
package charactor;
import java.io.Serializable;
public class Hero {
public String name;
public float hp;
public int damage;
public void attackHero(Hero h){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
h.hp-=damage;
System.out.format("%sc正在攻击c%s ,%s 的血变成了 %.0f %n ",name,h.name,h.name,h.hp);
if(h.isDead()){
System.out.println(h.name+"死啦啦了");
}
}
public boolean isDead(){
return 0>=hp?true:false;
}
}
二、KillThread
package PraticeThread;
import charactor.Hero;
public class KillThread extends Thread{
private Hero h1;
private Hero h2;
public KillThread(Hero h1,Hero h2){
this.h1=h1;
this.h2=h2;
}
public void run(){
while(!h2.isDead()){
h1.attackHero(h2);
}
}
}
三、TestThread
package PraticeThread;
import charactor.Hero;
public class TestThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Hero snake=new Hero();
snake.name="蛇女";snake.hp=700;snake.damage=60;
Hero Tang=new Hero();
Tang.name="唐三";Tang.hp=800;Tang.damage=70;
Hero Xiao=new Hero();
Xiao.name="萧炎";Xiao.hp=600;Xiao.damage=90;
Hero tree=new Hero();
tree.name="树神";tree.hp=1000;tree.damage=60;
KillThread killThread1=new KillThread(snake,Tang);
killThread1.start();
KillThread killThread2=new KillThread(Xiao,tree);
killThread2.start();
}
}
如下是两对英雄同时攻击的代码(实现Runnable接口)
思路:创建battle类,实现Runnable接口
启动的时候,首先创建一个battle对象,然后再根据该battle对象创建一个线程对象并启动
二、battle
package PraticeThread;
import charactor.Hero;
public class battle implements Runnable{
private Hero h1;
private Hero h2;
public battle(Hero h1,Hero h2){
this.h1=h1;
this.h2=h2;
}
public void run(){
while(!h2.isDead()){
h1.attackHero(h2);
}
}
}
三、TestThread
package PraticeThread;
import charactor.Hero;
public class TestThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Hero snake=new Hero();
snake.name="蛇女";snake.hp=700;snake.damage=60;
Hero Tang=new Hero();
Tang.name="唐三";Tang.hp=800;Tang.damage=70;
Hero Xiao=new Hero();
Xiao.name="萧炎";Xiao.hp=600;Xiao.damage=90;
Hero tree=new Hero();
tree.name="树神";tree.hp=1000;tree.damage=60;
battle battle1=new battle(snake,Tang);
new Thread(battle1).start();
battle battle2=new battle(Xiao,tree);
new Thread(battle2).start();
}
}
如下是两对英雄互相攻击的代码(匿名类)
思路:使用匿名类,继承Thread,重写run方法,直接再run里面写业务代码
匿名类的好处是可以很方便的访问外部的局部变量
二、TestThread
package PraticeThread;
import charactor.Hero;
public class TestThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Hero snake=new Hero();
snake.name="蛇女";snake.hp=700;snake.damage=60;
Hero Tang=new Hero();
Tang.name="唐三";Tang.hp=800;Tang.damage=70;
Hero Xiao=new Hero();
Xiao.name="萧炎";Xiao.hp=600;Xiao.damage=90;
Hero tree=new Hero();
tree.name="树神";tree.hp=1000;tree.damage=60;
Thread t1=new Thread(){
public void run(){
while(!Tang.isDead()){
snake.attackHero(Tang);
}
}
};
t1.start();
Thread t2=new Thread(){
public void run(){
while(!tree.isDead()){
Xiao.attackHero(Xiao);
}
}
};
t2.start();
}
}
二、常见的线程方法
关键字 | 简介 | 用法 |
sleep | 当前线程暂停 | Thread.sleep(1000)---暂停1000毫秒其他线程不影响 |
join | 加入到当前线程中 | 匿名方法举例子 Thread t=new Thread(){方法}; try{t.join();}catch(异常) 在主线程里加入该线程,该线程运行完毕其他线程才会继续 |
setPriority | 线程优先级 | 两个线程t1和t2,优先t1获得CPU资源 t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.start();t2.start(); |
yield | 临时暂停 Thread.yield(); | 两个线程t1和t2,t1临时暂停就使得t2可以占用 Thread t1=new Thread(){ public void run(){ while(!Tang.isDead()){ Thread.yield(); snake.attackHero(Tang); } } } |
setDaemo | 守护线程:当一个进程里所有的线程都是守护线程时结束进程 就是说一家公司里生产部和销售部倒闭的话,后勤也就没意义了,这里的后勤就是守护线程 | 守护线程通常会被用来做日志,性能统计工作 线程名.setDaemo(); |
三、同步(concurrency)问题-----多个线程同时修改一个数据的时候可能导致的问题
解决的思路是:在一个线程访问期间其他线程不可以访问,因此引入synchronizedt同步 ,见如下代码
synchronized表示当前线程独占对象someObject,其他线程试图占有someObject的时候 就会等待知道当前对象释放,someObject又叫同步对象,所有的对象都可以作为同步对象, 为了达到同步效果必须使用同一个同步对象
释放同步对象的方法:synchronized块自然结束或者有异常抛出
Object someObject=new Object();
synchronized(someObject){
//此处的代码只有占有了someObject后才能执行
snake.recover();
}
解决同步问题------三种办法
假如现在两个线程,一个recover,另一个hurt,snake是对象
1、使用synchronized----实例化
final Object someObject=new Object();
synchronized(somrObject){
snake.recover();
}
2、使用hero对象作为同步对象----省了实例化
synchronized(snake){
snake.recover();
}
3、在方法前加入修饰符synchronized----直接在Hero类中方法recover前加上synchronized
pubic synchronized void recover(){
hp=hp+1;
}
四、线程安全的类---面试题?
1、HashMap和HashTable的区别
共同点:两者都实现了Map接口,都是键值对保存数据的方式
区别:HashMap可以存放null,HashTable不能存放null
HashMap不是线程安全的类,HashTable是线程安全的类
2、StringBuffer和StringBuilder的区别
Buffer是线程安全的,Builder是非线程安全的
所以说如果是单线程用StringBuilder会更快,如果是多线程就用StringBuffer保证数据的安全性
3、ArrayList和Vector的区别
Vector是线程安全的类,ArrayList是非线程安全的类
不过这里可以通过工具Collections把ArrayList转化为线程安全,见如下代码
List<Integer> t1=new ArrayList<>();
List<Integer> t2=Collections.synchronizedList(t1);
五、死锁-----当业务比较复杂,多线程应用里有可能会发生死锁
理解:1. 线程1 首先占有对象1,接着试图占有对象2
2. 线程2 首先占有对象2,接着试图占有对象1
3. 线程1 等待线程2释放对象2; 与此同时,线程2等待线程1释放对象1
总结:互相等待无人迈出第一步就只能死等下去咯
代码理解:同步里面套同步
package PraticeThread;
import charactor.Hero;
public class deadLock {
public static void main(String[] args) {
// TODO Auto-generated method stub
final Hero s=new Hero();
s.name="美杜莎";
final Hero t=new Hero();
t.name="唐山";
Thread a1=new Thread(){
public void run(){
//占有美杜莎
synchronized(s){
System.out.println("a1线程已占用美杜莎");
try{
Thread.sleep(1000);//停顿1000毫秒这样a2线程有足够的时间占有唐山
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("a1线程试图占有唐山。。。。");
synchronized(t){
System.out.println("do something");
}
}
}
};
a1.start();
Thread t2=new Thread(){
public void run(){
//占有唐山
synchronized(t){
System.out.println("a2已占用唐山");
try{Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("a2试图占有美杜莎。。。。");
synchronized(s){
System.out.println("do something");
}
}
}
};
t2.start();
}
}
六、交互-----线程之间有交互通知的需求,例如有两个线程同时处理同一个英雄,一个加血一个减 血,当减血线程发现血量=1时就停止减血,直到加血线程加了血才继续减血
总结:利用wait()方法临时释放线程并且等待和notify()方法通知一个其他线程可以继续工作的 原理,notifyAll()的意思是通知所有等待线程可以工作了
再补充一个知识点:while(true)的作用是一个无限循环
在内部用break或return退出循环,否则一直循环
一、
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public synchronized void hurt(){
if(hp==1){
//让占有this减血的线程暂时施放,并等待
try{this.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
hp-=1;
System.out.printf("%s 减血后的血量是: %.0f%n",name,hp );
}
public synchronized void recover(){
hp+=1;
System.out.printf("%s 加血后的血量: %.0f%n",name,hp);
this.notify();
}
}
二、
package PraticeThread;
import charactor.Hero;
public class jiaoHu {
public static void main(String[] args) {
// TODO Auto-generated method stub
final Hero snake=new Hero();
snake.name="美杜莎";
snake.hp=66;
Thread a1=new Thread(){
public void run(){
while(true){
snake.hurt();
try{Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
};
Thread a2=new Thread(){
public void run(){
while(true){
snake.recover();
try{Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
};
a1.start();
a2.start();
}
}
七、线程池
设计思路(类似生产者消费者模型)
1 | 准备一个任务容器 |
2 | 一次性启动10个消费者线程,刚开始所有线程都在wait在上面 |
3 | 直到外部线程往任务容器里扔了一个任务,就会有一个线程被唤醒notify,这个线程取出任务并且开始执行,执行完毕后继续等待下一次任务的到来 |
4 | 如果短时间内有较多的任务加入,那么就会有多个线程被唤醒notify然后去执行任务。整个过程都不需要创建新的线程而且循环存在的线程 |
自定义线程池没看懂,就用java自带的线程池吧,如下代码
注释:
第一个参数10表示初始化10个线程在里面工作
第二个参数15表示若是10个不够就会自动增加到最多15个
第三个参数60的意思是结合第四个参数TimeUnit.SECONDS,表示等待60秒,多出来的线程还没有接到活的话
就会回收,最少10个
第五个参数new LinkedBlockingQueue()是用来放任务的集合
package charactor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;;
public class ThreadPoll {
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadPoolExecutor thredPool=new ThreadPoolExecutor(10,15,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
thredPool.execute(new Runnable(){
public void run(){
System.out.println("任务1");
}
});
}
}
八、Lock对象---与synchronize类似也能达到同步的效果,但是需要手动释放
--Lock是一个接口,为了使用一个Lock对象,需要用到如下方法:
Lock lock=new ReentrantLock();
package PraticeThread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;;
public class lock {
public static String now(){
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
public static void log(String msg){
System.out.printf("%s %s %s %n", now(),Thread.currentThread().getName(),msg);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Lock lock=new ReentrantLock();
Thread t1=new Thread(){
public void run(){
try{
log("线程启动");
log("正在试图占有对象lock");
lock.lock();
log("占有对象lock中");
log("进行3秒的业务操作");
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}finally{
log("释放对象lock");
lock.unlock();
}
log("线程结束");
}
};
t1.setName("t1");
t1.start();
}
}
之前synchronize方式进行线程交互,用到的是wait,notify,notifyAll方法;
Lock也提供了类似解决办法:
首先通过lock对象得到一个Condition对象,然后调用Condition对象方法:await,signal,signalAll
1---import java.util.concurrent.locks.ReentrantLock;
---import java.util.concurrent.locks.Lock;
---import java.util.concurrent.locks.Condition;
2---Lock lock =new ReentrantLock();
---Condition condition=lock.newCondition();
见如下代码:
package PraticeThread;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
public class lockJh {
public static void ac(String mm,String nn){
System.out.printf("%s 变成了 %s%n",mm,nn);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Lock lock=new ReentrantLock();
Condition condition =lock.newCondition();
Thread t1=new Thread(){
public void run(){
try{
ac("赵晓雪","大尖牛");
ac("赵晓雪","屎壳郎");
lock.lock();//占有
condition.await();//临时暂停等待
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();//手动释放
}
}
};
t1.setName("tt1");//线程1起名
Thread t2=new Thread(){
public void run(){
try{
ac("赵晓雪","猴子");
ac("赵晓雪","天风元帅");
lock.lock();//占有
Thread.sleep(2000);
condition.signal();//释放其他等待线程
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();//唤醒其他线程
}
}
};
t2.setName("tt2");
t1.start();
t2.start();
}
}
九、原子访问---不可中断的操作,比如int i=5,也线程安全的
JDK6以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。