Java多线程

来源:互联网 发布:傲世堂 java 编辑:程序博客网 时间:2024/05/02 13:18

多线程的概念

进程、线程、多线程的概念

进程:正在进行中的程序
线程:进程中一个负责程序执行的控制单元(执行路径)。一个进程中可以有多个执行路径,称之为多线程。一个进程中至少要有一个线程。
优点:解决了多部分代码同时运行的问题。
弊端:线程太多,会导致效率的降低。

Java VM启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。jvm启动不止一个线程,还有负责垃圾回收机制的线程。
所以JVM启动时,至少有两个线程:
1.执行main函数的线程,该线程的任务代码都定义在main函数中。
2.负责垃圾回收的线程。

创建线程方式一

步骤:
1.继承Thread类
2.重写Thread类中的run方法(Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码 。该存储功能就是run方法也就是说Thread类中的run方法,用于      存储线程要运行的代码)
3.调用线程的start方法,该方法有两个作用,启动线程和调用run方法
class TestWork {public static void main(String[] args) {ThreadDemo t = new ThreadDemo();t.start();  //调用start()方法,启动线程for(int x = 0 ; x < 60 ; x++) {System.out.println("main......."+x);}}}class ThreadDemo extends Thread {//继承Thread类时,要重写run方法public void run() {for(int x = 0 ; x < 60 ; x++) {System.out.println("Thread......."+x);}}}
运行结果每一次都不同?
多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序正在运行。(多核除外)cpu在做着快速切换,已达到看上去是同时运行的效果。这就是多线程的一个特性,随机性。
线程都有自己默认的名称,Thread-编号,编号从0开始
static Thread currentThread():获取当前线程对象
getName():获取线程名称
class TestWork {public static void main(String[] args) {ThreadDemo t1 = new ThreadDemo("Thread--1");ThreadDemo t2 = new ThreadDemo("Thread--2");t1.start();  //调用start()方法,启动线程t2.start();for(int x = 0 ; x < 60 ; x++) {System.out.println("main......."+x);}}}class ThreadDemo extends Thread {private String name;  //给线程自定义一个名字ThreadDemo(String name) {super(name);   //Thread类里面定义好了构造方法直接调用}public void run() {for(int x = 0 ; x < 40 ; x++)System.out.println(this.getName()+":"+x); //获取线程名字的方法等同于Thread.currentThread().getName()}}

创建线程方式二

步骤:
1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数(自定义的run方法所属对象是Runnable接口的子类对象 ,要让让线程去执行指定对象的run方法,就       必须明确该run方法所属对象)
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
优点:避免了单继承的局限性,将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
class TestWork {public static void main(String[] args) {ThreadDemo son = new ThreadDemo();  //将Runnable接口的子类对象传递给Thread类的构造函数Thread t1 = new Thread(son);Thread t2 = new Thread(son);Thread t3 = new Thread(son);Thread t4 = new Thread(son);t1.start();t2.start();t3.start();t4.start();}}class ThreadDemo implements Runnable {private int num = 100;   //定义一个变量并赋值100,public void run() {while(true) {if(num > 0)  // 对变量进行判断,大于0则执行下面的代码System.out.println(Thread.currentThread().getName()+"--num"+num--);  //num变量赋值后减1}}}

线程安全问题

线程安全问题产生的原因

class TestWork {public static void main(String[] args) {ThreadDemo son = new ThreadDemo();  //将Runnable接口的子类对象传递给Thread类的构造函数Thread t1 = new Thread(son);Thread t2 = new Thread(son);Thread t3 = new Thread(son);Thread t4 = new Thread(son);t1.start();t2.start();t3.start();t4.start();}}class ThreadDemo implements Runnable {private int num = 100;public void run() {while(true) {if(num > 0) {try {Thread.sleep(10);  //调用Thread类里面的sleep方法,让线程等待一段时间。} catch (InterruptedException e) {   //sleep方法声明了异常,但是ThreadDemo的父类没有声明,所以这里这能try/catch// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"--num"+num--);}}}}

通过对线程创建方式二里的程序多次执行发现,出现了不同线程输出num为负数的情况。因为当线程2执行num--之前,线程0,1,3都通过了if的判断,此时此刻拿着num为2的线程2执行num--后,其它线程开始一次执行,所以就出现了为负数的情况。
线程安全问题产生的原因:
当多条语句在操作同一个线程共享的数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就已经参与了进来,导致共享 数据的错误

解决方案

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行
Java对于多线程的安全问题提供了专业的解决方法,同步代码块。
格式:
synchronized(对象) {
  
  需要被同步的代码
  }
优点:解决了多线程的安全问题
缺点:多线程比较多时,较为消耗资源,每个线程都需要判断
注意:使用同步代码块必须要有两个或者以上的线程,必须多个线程使用同一个锁
class TestWork {public static void main(String[] args) {ThreadDemo son = new ThreadDemo();  //将Runnable接口的子类对象传递给Thread类的构造函数Thread t1 = new Thread(son);Thread t2 = new Thread(son);Thread t3 = new Thread(son);Thread t4 = new Thread(son);t1.start();t2.start();t3.start();t4.start();}}class ThreadDemo implements Runnable {private int num = 100;Object obj = new Object(); //建立一个同步代码块锁public void run() {while(true) {//同步代码被多个线程共享的部分synchronized(obj) {if(num > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();  }System.out.println(Thread.currentThread().getName()+"--num"+num--);}}}}}

因为有同步代码块的原因,先抢到cpu执行权的线程,进来。其它的只能等进锁的线程执行完才能进去。
创建一个银行程序,有两个储户往银行里面存钱,分别存300,每次100,存3次。
1.明确哪些代码是多线程运行代码
2.明确共享数据
3.明确多线程运行代码中哪些语句是操作共享数据的
class TestWork {public static void main(String[] args) {Cus c = new Cus();Thread t1 = new Thread(c);Thread t2 = new Thread(c);             //开启两个线程t1.start();t2.start();}}class Bank {private int sum;Object obj = new Object();    //创建一个存钱的方法public void add(int m) {synchronized(obj) {sum = sum +m;System.out.println(Thread.currentThread().getName()+"--money"+sum);}}}class Cus implements Runnable {private Bank b = new Bank();  //创建对象,以便在run方法里面调用存钱的方法public void run() {             //用for循环来设定往里存放数据的次数for(int x = 0 ; x < 3 ; x++) {b.add(100);}}}

该程序还能再到函数上加上synchronized修饰符,因为整个函数都是共享数据,称之为同步函数
public synchronized void add(int m) {//synchronized(obj) {sum = sum +m;System.out.println(Thread.currentThread().getName()+"--money"+sum);//}}
同步函数和同步代码块的区别
1.同步函数的锁是固定的this。
2.同步代码块的锁是任意的对象。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,可以实现同步
class TestWork {public static void main(String[] args) {Ticket1 c = new Ticket1();Thread t1 = new Thread(c);Thread t2 = new Thread(c);t1.start();try {Thread.sleep(10);  //让主线程等待,因为t1有可能开启了之后没有抢到cpu执行权,让主线程继续往下执行把flag改为flase,以至于两个线程还是用同一个同步代码块。}catch(Exception e) {}c.setBool(false);   //把标记改为false让t2区执行同步函数t2.start();}}class Ticket1 implements Runnable {private int tick = 100;private boolean flag = true;   //定义一个标记变量,让t1和t2分道扬镳Object obj = new Object();public void setBool(boolean flag) {this.flag = flag;}public void run() {if(flag) {while(true) {synchronized(this) {if(tick > 0) {try {Thread.sleep(10);  }catch(Exception e) {}System.out.println(Thread.currentThread().getName()+":ticket--"+tick--);}}}}else {while(true)show();}}      //定义一个同步函数,让t2进来public synchronized void show() {if(tick > 0) {try {Thread.sleep(10);  }catch(Exception e) {}System.out.println(Thread.currentThread().getName()+":ticket--"+tick--);}}}

静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

多线程下的单例设计模式

单例设计模式下的懒汉式存在安全问题,可以使用同步函数解决,但是synchronized Single getInstance()这种形式使用起来效率太低,如下改动,即可太高效率
class Single {private Single() {}private static Single s = null;public static Single getInstance() {if(s == null) {   //如果对象已经存在了,就能直接获取不需要再经过下面的代码,提高效率synchronized(Single.class) { //因为存在都通过第一个if判断的情况,那么会建立多个对象。所以必须加锁                     if(s == null)                                   s = new Single();                        }                }                return s;        } }

死锁

同步中嵌套同步,如下示例
class DeadLock implements Runnable {private boolean flag;DeadLock(boolean flag) {this.flag = flag;}public void run() {//锁里面还有一把锁if(flag) {synchronized(Lock.l1) {System.out.println(Thread.currentThread().getName()+"--l1");synchronized(Lock.l2) {System.out.println(Thread.currentThread().getName()+"--l2");}}} else {synchronized(Lock.l2) {System.out.println(Thread.currentThread().getName()+"--l2");synchronized(Lock.l1) {System.out.println(Thread.currentThread().getName()+"--l1");}}}}}//定义两把不同的锁,方便使用class Lock {static Lock l1 = new Lock();static Lock l2 = new Lock();}class Synchronized {public static void main(String[] args) {DeadLock d1 = new DeadLock(true);DeadLock d2 = new DeadLock(false);Thread t1 = new Thread(d1);Thread t2 = new Thread(d2);t1.start();t2.start();}}

为什么会造成死锁现象?
线程t1执行l2里面的代码,必须要拿到l2;线程t2要执行l1里面的代码,必须要拿到l1;当线程开启,t1首先拿到l1,t2则拿到l2,所以t1进入不到l2,t2页进入不到l1。

线程间通信

线程间通信涉及到的方法

线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。
等待/唤醒机制涉及方法:
wait():       让线程处于冻结状态,被wait的线程会被存储到线程池中
notify():     唤醒线程池中的一个线程(任何一个都有可能)
notifyAll():唤醒线程池中的所有线程。
注意:
都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,只有同步才具有锁。
为什么这些操作线程的方法定义在Object类?
 因为这些方法在操作同步中线程时,都必须要标识他们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。也就是说,等待和唤醒必须是同一个锁而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
class Resource {private String name;private String sex;private boolean flag = false;  //定义一个标记用来判断线程是否要等待public synchronized void set(String name,String sex) {if(flag)  //如果flag为true则输入线程等待try {this.wait();} catch(Exception e) {}this.name = name;this.sex = sex;flag = true;  //当线程输入了资源后,把标记改为true让输出线程输出资源this.notify();}public synchronized void out() {if(!flag)   //如果!flag是为假则线程等待try {this.wait();}catch(Exception e) {}System.out.println("name:"+name+" -- sex:"+sex);flag = false;  //输出完资源之后把flag改为falethis.notify(); //唤醒正在等待的输入线程。}}class Input implements Runnable {private Resource r;  //定义一个引用rInput(Resource r) {this.r = r;}public void run() {int x = 0;while(true) {//循环输入两个组不同的数据if(x == 0) {r.set("Jack", "男");}else {r.set("Moli", "女");}x = (x+1)%2;}}}class Output implements Runnable {private Resource r;Output(Resource r) {this.r = r;}public void run() {while(true) {r.out();}}}class TestWork {public static void main(String[] args) {Resource r = new Resource(); //创建资源对象Input in = new Input(r);  //资源对象作为参数,传入Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(out);t1.start();t2.start();}}
多个生产者以及 消费者的问题:
class Resource {private String name;private int count;private boolean flag = false;  //定义一个标记用来判断线程是否要等待public synchronized void set(String name) {if(flag)  //如果flag为true则输入线程等待try {this.wait();} catch(Exception e) {}this.name = name+count++;System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);flag = true;  //当线程输入了资源后,把标记改为true让输出线程输出资源this.notify();}public synchronized void out() {if(!flag)   //如果!flag是为假则线程等待try {this.wait();}catch(Exception e) {}System.out.println(Thread.currentThread().getName()+"--消费者--"+this.name);flag = false;  //输出完资源之后把flag改为falethis.notify(); //唤醒正在等待的输入线程。}}class Input implements Runnable {private Resource r;  //定义一个引用rInput(Resource r) {this.r = r;}public void run() {while(true) {r.set("啤酒");}}}class Output implements Runnable {private Resource r;Output(Resource r) {this.r = r;}public void run() {while(true) {r.out();}}}class TestWork {public static void main(String[] args) {Resource r = new Resource(); //创建资源对象Input in = new Input(r);  //资源对象作为参数,传入Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(in);Thread t3 = new Thread(out);Thread t4 = new Thread(out);t1.start();t2.start();t3.start();t4.start();}}
为什么有同步锁,有wait(),notify()方法还是出现了安全问题?
假设:
1.线程Thread-0获取到cpu执行权进入到锁,生产了啤酒1,将flag设置为true,重新获取到执行权回到if判断语句。由于flag已变为ture,所以执行wait方法。然后Thread-1获取到CPU执行权,同样要执行wait方法。
2.线程Thread-2获取到cpu执行权进入到锁,由于flag为true,所以消费了啤酒1,把flag改为false,然后唤醒了Thread-0,但是没有获取到执行权。回到if判断语句,!false为真, 所以Thread-2执行wait方法。接着线程Thread-3获取到了CPU执行权,同样也被wait了。
3.唤醒的Thread-0因为已经被if语句判断过了,所以直接往下面执行代码,生产啤酒2,唤醒Thread-1。同理,Thread-也直接生产啤酒3,唤醒了Thread-2,导致啤酒2没被消费。
4.以此类推,接来下被唤醒的Thread-2消费了,啤酒3。Thread-3也消费了啤酒3。
If判断语句只能判断以此,故用while()循环判断。
注意:
由上面的“假设”可以推断,在while()循环中会出现死锁的情况,故把notify()改为notifyAll()方法
class Resource {private String name;private int count;private boolean flag = false;  //定义一个标记用来判断线程是否要等待public synchronized void set(String name) {while(flag)  //如果flag为true则输入线程等待try {this.wait();} catch(Exception e) {}this.name = name+count++;System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);flag = true;  //当线程输入了资源后,把标记改为true让输出线程输出资源this.notifyAll();  //唤醒所有等待的线程,防止4个线程都出现等待的状况}public synchronized void out() {while(!flag)   //如果!flag是为假则线程等待try {this.wait();}catch(Exception e) {}System.out.println(Thread.currentThread().getName()+"--消费者--"+this.name);flag = false;  //输出完资源之后把flag改为falethis.notifyAll(); }}class Input implements Runnable {private Resource r;  //定义一个引用rInput(Resource r) {this.r = r;}public void run() {while(true) {r.set("啤酒");}}}class Output implements Runnable {private Resource r;Output(Resource r) {this.r = r;}public void run() {while(true) {r.out();}}}class TestWork {public static void main(String[] args) {Resource r = new Resource(); //创建资源对象Input in = new Input(r);  //资源对象作为参数,传入Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(in);Thread t3 = new Thread(out);Thread t4 = new Thread(out);t1.start();t2.start();t3.start();t4.start();}}

JDK1.5新特性

JDK1.5 中提供了多线程升级解决方案:
Lock接口:替代了同步代码块或者同步函数
                     lock():获取锁
                     unlock():释放锁,为了防止异常出现,导致锁无法关闭,所以锁的关闭动作要放在finally中
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些解释器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
                     await()对应wait()
                     singal()对应notify()
                     singalAll()对应notifyAll()
import java.util.concurrent.locks.*;class Resource {private String name;private int count;private boolean flag = false;  //定义一个标记用来判断线程是否要等待private Lock lock = new ReentrantLock();  //new一个对象,ReentrantLock()是Lock的子类private Condition con_in = lock.newCondition();  //创建Condition类对象,并且一个Lock可以对应多个condition对象private Condition con_ou = lock.newCondition();public  void set(String name) throws Exception {lock.lock();try {while(flag)  //如果flag为true则输入线程等待con_in.await();this.name = name+count++;System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);flag = true;  //当线程输入了资源后,把标记改为true让输出线程输出资源con_ou.signal();}finally {lock.unlock();  //释放锁是必须要关闭的资源}}public  void out() throws Exception {lock.lock();try {while(!flag)   //如果!flag是为假则线程等待con_ou.await();System.out.println(Thread.currentThread().getName()+"--消费者--"+this.name);flag = false;  //输出完资源之后把flag改为falecon_in.signal(); //唤醒正在等待的输入线程。} finally {lock.unlock();}}}class Input implements Runnable {private Resource r;  //定义一个引用rInput(Resource r) {this.r = r;}public void run() {while(true) {try {r.set("啤酒");} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}class Output implements Runnable {private Resource r;Output(Resource r) {this.r = r;}public void run() {while(true) {try {r.out();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}class TestWork {public static void main(String[] args) {Resource r = new Resource(); //创建资源对象Input in = new Input(r);  //资源对象作为参数,传入Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(in);Thread t3 = new Thread(out);Thread t4 = new Thread(out);t1.start();t2.start();t3.start();t4.start();}}

停止线程

任务中都会有循环接口,控制住循环就可以结束任务,控制循环通常用定义标记来完成。stop方法已经过时,不再使用。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();
class StopThread implements Runnable {private boolean flag = true;public synchronized void run() {while(flag) {try { wait();} catch(InterruptedException e) {System.out.println(Thread.currentThread().getName()+"exception");flag = false;}System.out.println(Thread.currentThread().getName()+"正在运行");}}public void changeFlag(){flag = false;}}class StopThreadDemo {public static void main(String[] args) {StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);t1.setDaemon(true);t1.start();t2.start();int num = 0;while(true){if(num++ == 60){//st.changeFlag();t1.interrupt();t2.interrupt();break;}System.out.println(Thread.currentThread().getName()+"正在运行"+num);}}}

线程类的其它方法

setDaemon():将调用此方法的线程标记为守护线程,该方法必须在启动线程前调用。
join():               等待调用此方法的线程结束
setPriority():   设置此线程的优先级可以加参数(MAX_PRIORITY)
toStirng():       返回该线程的字符串表现形式,包括线程名称,优先级,和线程组。
yield():             暂停当前正在执行的线程对象,并执行其他线程
class Demo implements Runnable {public void run() {for(int x=0; x<70; x++) {System.out.println(Thread.currentThread().toString()+"....."+x);Thread.yield();}}}class  JoinDemo{public static void main(String[] args) throws Exception {Demo d = new Demo();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();//t1.setPriority(Thread.MAX_PRIORITY); //将t1的优先级提高的最大t2.start();//t1.join();  //如果在这里调用了join()方法,则main线程会停下来 for(int x=0; x<80; x++) {//System.out.println("main....."+x);}System.out.println("over");}}

























0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 宿舍太吵晚上睡不着觉怎么办 脚扎了钉子肿了怎么办 龙血树叶子下垂怎么办 龙血树叶子卷曲怎么办 3岁宝宝长期便秘怎么办 4岁小儿便秘严重怎么办 3岁宝宝便秘严重怎么办 3岁宝宝一直便秘怎么办 11个月宝宝便秘怎么办 2个月的宝宝便秘怎么办 宝宝便秘拉不下来怎么办 5一6岁儿童便秘怎么办 3个月宝宝便秘怎么办 8个月宝宝便秘怎么办 孕5个月咳嗽厉害怎么办 孕8个月咳嗽厉害怎么办 拆石膏后关节僵硬怎么办 宝宝的小腿不直怎么办 鸡咳嗽有痰呼噜怎么办 风热感冒怎么办小窍门 吃完虾喝了牛奶怎么办 三文鱼头汤腥怎么办 晚上咳嗽厉害怎么办睡不着觉 刚怀孕发烧39度怎么办 刚怀孕发烧38度怎么办 怀孕10天发烧了怎么办 怀孕2个月发烧了怎么办 lol误封3年怎么办 心悦会员到期了怎么办 心悦游戏家到期怎么办 无间鬼后运气背怎么办 趣店被骗提现了怎么办 微转奇迹闪退怎么办 奇迹暖暖ios闪退怎么办 奇迹mu任务没做怎么办 外地人在北京上社保怎么办 医社保中间断了怎么办 社保断了2年怎么办 孩子的社保断了怎么办 社保断了四个月怎么办 社保辞职后断了怎么办