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方法
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方法所属对象)
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.同步代码块的锁是任意的对象。
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();
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。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
- 【Java多线程】多线程死锁
- Java 多线程
- java 多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA 多线程
- Java多线程
- java多线程
- JAVA 多线程
- Java 多线程
- Java 多线程
- java多线程
- Java 多线程
- Java多线程
- java 多线程
- Python中导入包中模块的方法及区别
- 贾扬清分享_深度学习框架caffe
- 旋转数组最小数字
- C#日期时间格式化
- javascript 函数指针
- Java多线程
- Android实现伸缩弹力分布菜单效果
- C#调用Axis2发布的带SoapHeader用户验证的WebService(通过测试)
- 关于uitableviewcell的accessoryType属性
- 时间处理函数
- 【Android进阶】ListView的显示与缓存机制
- LTE:系统信息(System Information)的调度
- 谈恋爱,谈什么?【转自马雪琼】
- 使用Form表单上传文件